mirror of
https://github.com/goauthentik/authentik
synced 2026-05-14 02:46:29 +02:00
* web: fix esbuild issue with style sheets
Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).
Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.
In standard mode, the following warning appears on the console when running a Flow:
```
Autofocus processing was blocked because a document already has a focused element.
```
In compatibility mode, the following **error** appears on the console when running a Flow:
```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
at initDomMutationObservers (crawler-inject.js:1106:18)
at crawler-inject.js:1114:24
at Array.forEach (<anonymous>)
at initDomMutationObservers (crawler-inject.js:1114:10)
at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```
Despite this error, nothing seems to be broken and flows work as anticipated.
* web: just a few minor bugfixes and lintfixes
While investigating the viability of using ESLint 9, I found a few bugs.
The one major bug was found in the error handling code, where a comparison was
automatically invalid and would never realize "true."
A sequence used in our Storybook support code to generate unique IDs for
applications and providers had an annoying ambiguity:
```
new Array(length).fill(" ")
```
Lint states (and I agree):
> It's not clear whether the argument is meant to be the length of the array or
> the only element. If the argument is the array's length, consider using
> `Array.from({ length: n })`. If the argument is the only element, use
> `[element]`."
It's the former, and I intended as much.
Aside from those, a few over-wrought uses of the spread operator were removed.
* Fat-finger error. Thank gnu I double-check my PRs before I move them out of draft!
160 lines
6.0 KiB
TypeScript
160 lines
6.0 KiB
TypeScript
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
|
|
|
import { CSSResult, css } from "lit";
|
|
|
|
export function getCookie(name: string): string {
|
|
let cookieValue = "";
|
|
if (document.cookie && document.cookie !== "") {
|
|
const cookies = document.cookie.split(";");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
const cookie = cookies[i].trim();
|
|
// Does this cookie string begin with the name we want?
|
|
if (cookie.substring(0, name.length + 1) === name + "=") {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
export function convertToSlug(text: string): string {
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/ /g, "-")
|
|
.replace(/[^\w-]+/g, "");
|
|
}
|
|
|
|
/**
|
|
* Truncate a string based on maximum word count
|
|
*/
|
|
export function truncateWords(string: string, length = 10): string {
|
|
string = string || "";
|
|
const array = string.trim().split(" ");
|
|
const ellipsis = array.length > length ? "..." : "";
|
|
|
|
return array.slice(0, length).join(" ") + ellipsis;
|
|
}
|
|
|
|
/**
|
|
* Truncate a string based on character count
|
|
*/
|
|
export function truncate(string: string, length = 10): string {
|
|
return string.length > length ? `${string.substring(0, length)}...` : string;
|
|
}
|
|
|
|
export function camelToSnake(key: string): string {
|
|
const result = key.replace(/([A-Z])/g, " $1");
|
|
return result.split(" ").join("_").toLowerCase();
|
|
}
|
|
|
|
const capitalize = (key: string) => (key.length === 0 ? "" : key[0].toUpperCase() + key.slice(1));
|
|
|
|
export function snakeToCamel(key: string) {
|
|
const [start, ...rest] = key.split("_");
|
|
return [start, ...rest.map(capitalize)].join("");
|
|
}
|
|
|
|
export function groupBy<T>(objects: T[], callback: (obj: T) => string): Array<[string, T[]]> {
|
|
const m = new Map<string, T[]>();
|
|
objects.forEach((obj) => {
|
|
const group = callback(obj);
|
|
if (!m.has(group)) {
|
|
m.set(group, []);
|
|
}
|
|
const tProviders = m.get(group) || [];
|
|
tProviders.push(obj);
|
|
});
|
|
return Array.from(m).sort();
|
|
}
|
|
|
|
export function first<T>(...args: Array<T | undefined | null>): T {
|
|
for (let index = 0; index < args.length; index++) {
|
|
const element = args[index];
|
|
if (element !== undefined && element !== null) {
|
|
return element;
|
|
}
|
|
}
|
|
throw new SentryIgnoredError(`No compatible arg given: ${args}`);
|
|
}
|
|
|
|
// Taken from python's string module
|
|
export const ascii_lowercase = "abcdefghijklmnopqrstuvwxyz";
|
|
export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
export const ascii_letters = ascii_lowercase + ascii_uppercase;
|
|
export const digits = "0123456789";
|
|
export const hexdigits = digits + "abcdef" + "ABCDEF";
|
|
export const octdigits = "01234567";
|
|
export const punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
|
|
|
export function randomString(len: number, charset: string): string {
|
|
const chars = [];
|
|
const array = new Uint8Array(len);
|
|
self.crypto.getRandomValues(array);
|
|
for (let index = 0; index < len; index++) {
|
|
chars.push(charset[Math.floor(charset.length * (array[index] / Math.pow(2, 8)))]);
|
|
}
|
|
return chars.join("");
|
|
}
|
|
|
|
export function dateTimeLocal(date: Date): string {
|
|
// So for some reason, the datetime-local input field requires ISO Datetime as value
|
|
// But the standard javascript date.toISOString() returns everything with seconds and
|
|
// milliseconds, which the input field doesn't like (on chrome, on firefox its fine)
|
|
// On chrome, setting .valueAsNumber works, but that causes an error on firefox, so go
|
|
// figure.
|
|
// Additionally, toISOString always returns the date without timezone, which we would like
|
|
// to include for better usability
|
|
const tzOffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
|
const localISOTime = new Date(date.getTime() - tzOffset).toISOString().slice(0, -1);
|
|
const parts = localISOTime.split(":");
|
|
return `${parts[0]}:${parts[1]}`;
|
|
}
|
|
|
|
// Lit is extremely well-typed with regard to CSS, and Storybook's `build` does not currently have a
|
|
// coherent way of importing CSS-as-text into CSSStyleSheet. It works well when Storybook is running
|
|
// in `dev,` but in `build` it fails. Storied components will have to map their textual CSS imports
|
|
// using the function below.
|
|
type AdaptableStylesheet = Readonly<string | CSSResult | CSSStyleSheet>;
|
|
type AdaptedStylesheets = CSSStyleSheet | CSSStyleSheet[];
|
|
|
|
const isCSSResult = (v: unknown): v is CSSResult =>
|
|
v instanceof CSSResult && v.styleSheet !== undefined;
|
|
|
|
// prettier-ignore
|
|
export const _adaptCSS = (sheet: AdaptableStylesheet): CSSStyleSheet =>
|
|
(typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, []).styleSheet
|
|
: isCSSResult(sheet) ? sheet.styleSheet
|
|
: sheet) as CSSStyleSheet;
|
|
|
|
// Overloaded function definitions inform consumers that if you pass it an array, expect an array in
|
|
// return; if you pass it a scaler, expect a scalar in return.
|
|
|
|
export function adaptCSS(sheet: AdaptableStylesheet): CSSStyleSheet;
|
|
export function adaptCSS(sheet: AdaptableStylesheet[]): CSSStyleSheet[];
|
|
export function adaptCSS(sheet: AdaptableStylesheet | AdaptableStylesheet[]): AdaptedStylesheets {
|
|
return Array.isArray(sheet) ? sheet.map(_adaptCSS) : _adaptCSS(sheet);
|
|
}
|
|
|
|
const _timeUnits = new Map<Intl.RelativeTimeFormatUnit, number>([
|
|
["year", 24 * 60 * 60 * 1000 * 365],
|
|
["month", (24 * 60 * 60 * 1000 * 365) / 12],
|
|
["day", 24 * 60 * 60 * 1000],
|
|
["hour", 60 * 60 * 1000],
|
|
["minute", 60 * 1000],
|
|
["second", 1000],
|
|
]);
|
|
|
|
export function getRelativeTime(d1: Date, d2: Date = new Date()): string {
|
|
const rtf = new Intl.RelativeTimeFormat("default", { numeric: "auto" });
|
|
const elapsed = d1.getTime() - d2.getTime();
|
|
|
|
// "Math.abs" accounts for both "past" & "future" scenarios
|
|
for (const [key, value] of _timeUnits) {
|
|
if (Math.abs(elapsed) > value || key == "second") {
|
|
return rtf.format(Math.round(elapsed / value), key);
|
|
}
|
|
}
|
|
return rtf.format(Math.round(elapsed / 1000), "second");
|
|
}
|