web: update to ESLint 9 (#10812)
* web: update to ESLint 9 ESLint 9 has been out for awhile now, and all of the plug-ins that we use have caught up, so it is time to bite the bullet and upgrade. This commit: - upgrades to ESLint 9, and upgrades all associated plugins - Replaces the `.eslintrc` and `.eslintignore` files with the new, "flat" configuration file, "eslint.config.mjs". - Places the previous "precommit" and "nightmare" rules in `./scripts/eslint.precommit.mjs` and `./scripts/eslint.nightmare.mjs`, respectively - Replaces the scripted wrappers for eslint (`eslint`, `eslint-precommit`) with a single executable that takes the arguments `--precommit`, which applies a stricter set of rules, and `--nightmare`, which applies an even more terrifyingly strict set of rules. - Provides the scripted wrapper `./scripts/eslint.mjs` so that eslint can be run from `bun`, if one so chooses. - Fixes *all* of the lint `eslint.config.mjs` now finds, including removing all of the `eslint` styling rules and overrides because Eslint now proudly leaves that entirely up to Prettier. To shut Dependabot up about ESLint. * Added explanation for no-console removal. * web: did not need the old and unmaintained nightmare mode; it can be configured directly.
This commit is contained in:
@ -1,9 +0,0 @@
|
||||
# don't ever lint node_modules
|
||||
node_modules
|
||||
# don't lint build output (make sure it's set to your correct build folder name)
|
||||
dist
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
||||
src/locale-codes.ts
|
||||
storybook-static/
|
||||
src/locales/**
|
||||
@ -1,38 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"plugin:custom-elements/recommended",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"project": true
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "lit", "custom-elements"],
|
||||
"ignorePatterns": ["authentik-live-tests/**"],
|
||||
"rules": {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"no-console": ["error", { "allow": ["debug", "warn", "error"] }]
|
||||
}
|
||||
}
|
||||
@ -16,15 +16,13 @@ try {
|
||||
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
} catch (exc) {
|
||||
} catch (_exc) {
|
||||
// We probably don't have a .git folder, which could happen in container builds
|
||||
}
|
||||
const rootPackage = JSON.parse(fs.readFileSync(path.join(authentikProjectRoot, "./package.json")));
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const isProdBuild = process.env.NODE_ENV === "production";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const apiBasePath = process.env.AK_API_BASE_PATH || "";
|
||||
|
||||
const envGitHashKey = "GIT_BUILD_HASH";
|
||||
@ -35,10 +33,11 @@ const definitions = {
|
||||
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
|
||||
};
|
||||
|
||||
// All is magic is just to make sure the assets are copied into the right places. This is a very stripped down version
|
||||
// of what the rollup-copy-plugin does, without any of the features we don't use, and using globSync instead of globby
|
||||
// since we already had globSync lying around thanks to Typescript. If there's a third argument in an array entry, it's
|
||||
// used to replace the internal path before concatenating it all together as the destination target.
|
||||
// All is magic is just to make sure the assets are copied into the right places. This is a very
|
||||
// stripped down version of what the rollup-copy-plugin does, without any of the features we don't
|
||||
// use, and using globSync instead of globby since we already had globSync lying around thanks to
|
||||
// Typescript. If there's a third argument in an array entry, it's used to replace the internal path
|
||||
// before concatenating it all together as the destination target.
|
||||
|
||||
const otherFiles = [
|
||||
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
|
||||
@ -67,8 +66,8 @@ for (const [source, rawdest, strip] of otherFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
// This starts the definitions used for esbuild: Our targets, our arguments, the function for running a build, and three
|
||||
// options for building: watching, building, and building the proxy.
|
||||
// This starts the definitions used for esbuild: Our targets, our arguments, the function for
|
||||
// running a build, and three options for building: watching, building, and building the proxy.
|
||||
// Ordered by largest to smallest interface to build even faster
|
||||
const interfaces = [
|
||||
["admin/AdminInterface/AdminInterface.ts", "admin"],
|
||||
@ -104,7 +103,6 @@ function getVersion() {
|
||||
|
||||
async function buildOneSource(source, dest) {
|
||||
const DIST = path.join(__dirname, "./dist", dest);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
|
||||
|
||||
try {
|
||||
@ -116,7 +114,6 @@ async function buildOneSource(source, dest) {
|
||||
outdir: DIST,
|
||||
});
|
||||
const end = Date.now();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${Date.now() - start}ms`,
|
||||
);
|
||||
@ -135,14 +132,12 @@ function debouncedBuild() {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.clear();
|
||||
buildAuthentik(interfaces);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Build the authentikUI
|
||||
|
||||
options:
|
||||
@ -154,7 +149,6 @@ options:
|
||||
}
|
||||
|
||||
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Watching ./src for changes");
|
||||
chokidar.watch("./src").on("all", (event, path) => {
|
||||
if (!["add", "change", "unlink"].includes(event)) {
|
||||
|
||||
80
web/eslint.config.mjs
Normal file
80
web/eslint.config.mjs
Normal file
@ -0,0 +1,80 @@
|
||||
import eslint from "@eslint/js";
|
||||
import tsparser from "@typescript-eslint/parser";
|
||||
import litconf from "eslint-plugin-lit";
|
||||
import wcconf from "eslint-plugin-wc";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
// You would not believe how much this change has frustrated users: ["if an ignores key is used
|
||||
// without any other keys in the configuration object, then the patterns act as global
|
||||
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/",
|
||||
// don't ever lint node_modules
|
||||
"node_modules/",
|
||||
".storybook/*",
|
||||
// don't lint build output (make sure it's set to your correct build folder name)
|
||||
// don't lint nyc coverage output
|
||||
"coverage/",
|
||||
"src/locale-codes.ts",
|
||||
"storybook-static/",
|
||||
"src/locales/",
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
wcconf.configs["flat/recommended"],
|
||||
litconf.configs["flat/recommended"],
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
files: ["src/**"],
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
globals: {
|
||||
...globals.nodeBuiltin,
|
||||
},
|
||||
},
|
||||
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
// We WANT our scripts to output to the console!
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
9810
web/package-lock.json
generated
9810
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,8 @@
|
||||
"watch": "run-s build-locales build:manifest esbuild:watch",
|
||||
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix",
|
||||
"lint:lockfile": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https",
|
||||
"lint:precommit": "bun scripts/eslint-precommit.mjs",
|
||||
"lint:precommit": "bun ./scripts/eslint.mjs --precommit",
|
||||
"lint:nightmare": "bun ./scripts/eslint.mjs --nightmare",
|
||||
"lint:spelling": "node scripts/check-spelling.mjs",
|
||||
"lit-analyse": "lit-analyzer src",
|
||||
"lit-analyse:strict": "lit-analyzer src --strict",
|
||||
@ -86,6 +87,7 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@changesets/cli": "^2.27.5",
|
||||
"@custom-elements-manifest/analyzer": "^0.10.2",
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@genesiscommunitysuccess/custom-elements-lsp": "^5.0.3",
|
||||
"@hcaptcha/types": "^1.0.4",
|
||||
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
|
||||
@ -102,11 +104,12 @@
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/guacamole-common-js": "1.5.2",
|
||||
"@types/showdown": "^2.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
||||
"@typescript-eslint/parser": "^7.16.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.1",
|
||||
"@typescript-eslint/parser": "^8.0.1",
|
||||
"@wdio/browser-runner": "^8.40.1",
|
||||
"@wdio/cli": "^8.40.0",
|
||||
"@wdio/mocha-framework": "^8.40.0",
|
||||
@ -116,14 +119,14 @@
|
||||
"chokidar": "^3.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild": "^0.23.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.8",
|
||||
"eslint-plugin-lit": "^1.11.0",
|
||||
"eslint-plugin-sonarjs": "^1.0.3",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"eslint-plugin-lit": "^1.14.0",
|
||||
"eslint-plugin-sonarjs": "^1.0.4",
|
||||
"eslint-plugin-wc": "^2.1.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"globals": "^15.9.0",
|
||||
"lit-analyzer": "^2.0.3",
|
||||
"lockfile-lint": "^4.14.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@ -140,6 +143,7 @@
|
||||
"tslib": "^2.6.3",
|
||||
"turnstile-types": "^1.2.1",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"wdio-wait-for": "^3.0.11"
|
||||
},
|
||||
|
||||
@ -60,9 +60,7 @@ if (!upToDate) {
|
||||
.map((locale) => `Locale '${locale}' has ${counts.get(locale)} missing translations`)
|
||||
.join("\n");
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Translation tables rebuilt.\n${report}\n`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Locale ./src is up-to-date");
|
||||
|
||||
@ -4,7 +4,6 @@ import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function* walkFilesystem(dir) {
|
||||
const openeddir = fs.opendirSync(dir);
|
||||
if (!openeddir) {
|
||||
|
||||
@ -12,5 +12,4 @@ const cmd = [
|
||||
"-S './src/locales/**' ./src -s",
|
||||
].join(" ");
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(execSync(cmd, { encoding: "utf8" }));
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
import { execFileSync } from "child_process";
|
||||
import { ESLint } from "eslint";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
// Code assumes this script is in the './web/scripts' folder.
|
||||
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
process.chdir(path.join(projectRoot, "./web"));
|
||||
|
||||
const eslintConfig = {
|
||||
overrideConfig: {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"plugin:custom-elements/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
"plugin:sonarjs/recommended",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
|
||||
rules: {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { avoidEscape: true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"sonarjs/cognitive-complexity": ["error", 9],
|
||||
"sonarjs/no-duplicate-string": "off",
|
||||
"sonarjs/no-nested-template-literals": "off",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updated = ["./src/", "./build.mjs", "./scripts/*.mjs"];
|
||||
|
||||
const eslint = new ESLint(eslintConfig);
|
||||
const results = await eslint.lintFiles(updated);
|
||||
const formatter = await eslint.loadFormatter("stylish");
|
||||
const resultText = formatter.format(results);
|
||||
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(resultText);
|
||||
process.exit(errors > 1 ? 1 : 0);
|
||||
@ -1,83 +0,0 @@
|
||||
import { execFileSync } from "child_process";
|
||||
import { ESLint } from "eslint";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
// Code assumes this script is in the './web/scripts' folder.
|
||||
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
process.chdir(path.join(projectRoot, "./web"));
|
||||
|
||||
const eslintConfig = {
|
||||
overrideConfig: {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"plugin:custom-elements/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
"plugin:sonarjs/recommended",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
|
||||
ignorePatterns: ["!./.storybook/**/*.ts"],
|
||||
rules: {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { avoidEscape: true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"sonarjs/cognitive-complexity": ["warn", 9],
|
||||
"sonarjs/no-duplicate-string": "off",
|
||||
"sonarjs/no-nested-template-literals": "off",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const porcelainV1 = /^(..)\s+(.*$)/;
|
||||
const gitStatus = execFileSync("git", ["status", "--porcelain", "."], { encoding: "utf8" });
|
||||
|
||||
const statuses = gitStatus.split("\n").reduce((acc, line) => {
|
||||
const match = porcelainV1.exec(line.replace("\n"));
|
||||
if (!match) {
|
||||
return acc;
|
||||
}
|
||||
const [status, path] = Array.from(match).slice(1, 3);
|
||||
return [...acc, [status, path.split("\x00")[0]]];
|
||||
}, []);
|
||||
|
||||
const isModified = /^(M|\?|\s)(M|\?|\s)/;
|
||||
const modified = (s) => isModified.test(s);
|
||||
|
||||
const isCheckable = /\.(ts|js|mjs)$/;
|
||||
const checkable = (s) => isCheckable.test(s);
|
||||
|
||||
const ignored = /\/\.storybook\//;
|
||||
const notIgnored = (s) => !ignored.test(s);
|
||||
|
||||
const updated = statuses.reduce(
|
||||
(acc, [status, filename]) =>
|
||||
modified(status) && checkable(filename) && notIgnored(filename)
|
||||
? [...acc, path.join(projectRoot, filename)]
|
||||
: acc,
|
||||
[],
|
||||
);
|
||||
|
||||
const eslint = new ESLint(eslintConfig);
|
||||
const results = await eslint.lintFiles(updated);
|
||||
const formatter = await eslint.loadFormatter("stylish");
|
||||
const resultText = formatter.format(results);
|
||||
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(resultText);
|
||||
process.exit(errors > 1 ? 1 : 0);
|
||||
@ -1,63 +1,56 @@
|
||||
#!/usr/bin/env node --max_old_space_size=65536
|
||||
import { execFileSync } from "child_process";
|
||||
import { ESLint } from "eslint";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// Code assumes this script is in the './web/scripts' folder.
|
||||
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
||||
encoding: "utf8",
|
||||
}).replace("\n", "");
|
||||
process.chdir(path.join(projectRoot, "./web"));
|
||||
function changedFiles() {
|
||||
const gitStatus = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8" });
|
||||
const gitUntracked = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
const eslintConfig = {
|
||||
fix: true,
|
||||
overrideConfig: {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"plugin:custom-elements/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
project: true,
|
||||
},
|
||||
plugins: ["@typescript-eslint", "lit", "custom-elements"],
|
||||
ignorePatterns: ["authentik-live-tests/**"],
|
||||
rules: {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { avoidEscape: true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
},
|
||||
},
|
||||
};
|
||||
const changed = gitStatus
|
||||
.split("\n")
|
||||
.filter((line) => line.trim().substring(0, 4) === "web/")
|
||||
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
|
||||
.map((line) => line.substring(4))
|
||||
.filter((line) => fs.existsSync(line));
|
||||
|
||||
const eslint = new ESLint(eslintConfig);
|
||||
const results = await eslint.lintFiles(".");
|
||||
const untracked = gitUntracked
|
||||
.split("\n")
|
||||
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
|
||||
.filter((line) => fs.existsSync(line));
|
||||
|
||||
const sourceFiles = [...changed, ...untracked].filter((line) => /^src\//.test(line));
|
||||
const scriptFiles = [...changed, ...untracked].filter(
|
||||
(line) => /^scripts\//.test(line) || !/^src\//.test(line),
|
||||
);
|
||||
|
||||
return [...sourceFiles, ...scriptFiles];
|
||||
}
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
const projectRoot = path.join(__dirname, "..");
|
||||
process.chdir(projectRoot);
|
||||
|
||||
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
|
||||
|
||||
const [configFile, files] = hasFlag(["-n", "--nightmare"])
|
||||
? [path.join(__dirname, "eslint.nightmare.mjs"), changedFiles()]
|
||||
: hasFlag(["-p", "--precommit"])
|
||||
? [path.join(__dirname, "eslint.precommit.mjs"), changedFiles()]
|
||||
: [path.join(projectRoot, "eslint.config.mjs"), ["."]];
|
||||
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: configFile,
|
||||
warnIgnored: false,
|
||||
});
|
||||
|
||||
const results = await eslint.lintFiles(files);
|
||||
const formatter = await eslint.loadFormatter("stylish");
|
||||
const resultText = formatter.format(results);
|
||||
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(resultText);
|
||||
process.exit(errors > 1 ? 1 : 0);
|
||||
|
||||
201
web/scripts/eslint.nightmare.mjs
Normal file
201
web/scripts/eslint.nightmare.mjs
Normal file
@ -0,0 +1,201 @@
|
||||
import eslint from "@eslint/js";
|
||||
import tsparser from "@typescript-eslint/parser";
|
||||
import litconf from "eslint-plugin-lit";
|
||||
import sonar from "eslint-plugin-sonarjs";
|
||||
import wcconf from "eslint-plugin-wc";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
// You would not believe how much this change has frustrated users: ["if an ignores key is used
|
||||
// without any other keys in the configuration object, then the patterns act as global
|
||||
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/",
|
||||
// don't ever lint node_modules
|
||||
"node_modules/",
|
||||
".storybook/*",
|
||||
// don't lint build output (make sure it's set to your correct build folder name)
|
||||
// don't lint nyc coverage output
|
||||
"coverage/",
|
||||
"src/locale-codes.ts",
|
||||
"storybook-static/",
|
||||
"src/locales/",
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
wcconf.configs["flat/recommended"],
|
||||
litconf.configs["flat/recommended"],
|
||||
...tseslint.configs.recommended,
|
||||
sonar.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
files: ["src/**"],
|
||||
rules: {
|
||||
"accessor-pairs": "error",
|
||||
"array-callback-return": "error",
|
||||
"block-scoped-var": "error",
|
||||
"consistent-return": "error",
|
||||
"consistent-this": ["error", "that"],
|
||||
"curly": ["error", "all"],
|
||||
"dot-notation": [
|
||||
"error",
|
||||
{
|
||||
allowKeywords: true,
|
||||
},
|
||||
],
|
||||
"eqeqeq": "error",
|
||||
"func-names": "error",
|
||||
"guard-for-in": "error",
|
||||
"max-depth": ["error", 4],
|
||||
"max-nested-callbacks": ["error", 4],
|
||||
"max-params": ["error", 5],
|
||||
"new-cap": "error",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-condition": "error",
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty": "error",
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-function": "error",
|
||||
"no-labels": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implicit-globals": "error",
|
||||
"no-inner-declarations": ["error", "functions"],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-iterator": "error",
|
||||
"no-invalid-this": "error",
|
||||
"no-label-var": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-magic-numbers": ["error", { ignore: [0, 1, -1] }],
|
||||
"no-multi-str": "error",
|
||||
"no-negated-condition": "error",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-octal": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-restricted-syntax": ["error", "WithStatement"],
|
||||
"no-script-url": "error",
|
||||
"no-self-assign": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-undef": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-use-before-define": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"radix": "error",
|
||||
"require-yield": "error",
|
||||
"strict": ["error", "global"],
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "error",
|
||||
"vars-on-top": "error",
|
||||
"yoda": ["error", "never"],
|
||||
|
||||
"no-unused-vars": "off",
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
"sonarjs/cognitive-complexity": ["off", 9],
|
||||
"sonarjs/no-duplicate-string": "off",
|
||||
"sonarjs/no-nested-template-literals": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
globals: {
|
||||
...globals.nodeBuiltin,
|
||||
},
|
||||
},
|
||||
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
84
web/scripts/eslint.precommit.mjs
Normal file
84
web/scripts/eslint.precommit.mjs
Normal file
@ -0,0 +1,84 @@
|
||||
import eslint from "@eslint/js";
|
||||
import tsparser from "@typescript-eslint/parser";
|
||||
import litconf from "eslint-plugin-lit";
|
||||
import sonar from "eslint-plugin-sonarjs";
|
||||
import wcconf from "eslint-plugin-wc";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
// You would not believe how much this change has frustrated users: ["if an ignores key is used
|
||||
// without any other keys in the configuration object, then the patterns act as global
|
||||
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/",
|
||||
// don't ever lint node_modules
|
||||
"node_modules/",
|
||||
".storybook/*",
|
||||
// don't lint build output (make sure it's set to your correct build folder name)
|
||||
// don't lint nyc coverage output
|
||||
"coverage/",
|
||||
"src/locale-codes.ts",
|
||||
"storybook-static/",
|
||||
"src/locales/",
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
wcconf.configs["flat/recommended"],
|
||||
litconf.configs["flat/recommended"],
|
||||
...tseslint.configs.recommended,
|
||||
sonar.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
files: ["src/**"],
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
"sonarjs/cognitive-complexity": ["off", 9],
|
||||
"sonarjs/no-duplicate-string": "off",
|
||||
"sonarjs/no-nested-template-literals": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
globals: {
|
||||
...globals.nodeBuiltin,
|
||||
},
|
||||
},
|
||||
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -154,6 +154,8 @@ class Stage<T extends FlowInfoChallenge> {
|
||||
}
|
||||
}
|
||||
|
||||
const IS_INVALID = "is-invalid";
|
||||
|
||||
class IdentificationStage extends Stage<IdentificationChallenge> {
|
||||
render() {
|
||||
this.html(`
|
||||
@ -173,7 +175,7 @@ class IdentificationStage extends Stage<IdentificationChallenge> {
|
||||
${
|
||||
this.challenge.passwordFields
|
||||
? `<div class="form-label-group my-3 has-validation">
|
||||
<input type="password" class="form-control ${this.error("password").length > 0 ? "is-invalid" : ""}" name="password" placeholder="Password">
|
||||
<input type="password" class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||
${this.renderInputError("password")}
|
||||
</div>`
|
||||
: ""
|
||||
@ -197,7 +199,7 @@ class PasswordStage extends Stage<PasswordChallenge> {
|
||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||
<div class="form-label-group my-3 has-validation">
|
||||
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? "is-invalid" : ""}" name="password" placeholder="Password">
|
||||
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||
${this.renderInputError("password")}
|
||||
</div>
|
||||
<button class="btn btn-primary w-100 py-2" type="submit">Continue</button>
|
||||
@ -309,12 +311,10 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
user.id = this.u8arr(this.b64enc(this.u8arr(stringId)));
|
||||
const challenge = this.u8arr(credentialCreateOptions.challenge.toString());
|
||||
|
||||
const transformedCredentialCreateOptions = Object.assign({}, credentialCreateOptions, {
|
||||
return Object.assign({}, credentialCreateOptions, {
|
||||
challenge,
|
||||
user,
|
||||
});
|
||||
|
||||
return transformedCredentialCreateOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,12 +354,10 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
},
|
||||
);
|
||||
|
||||
const transformedCredentialRequestOptions = Object.assign({}, credentialRequestOptions, {
|
||||
return Object.assign({}, credentialRequestOptions, {
|
||||
challenge,
|
||||
allowCredentials,
|
||||
});
|
||||
|
||||
return transformedCredentialRequestOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -407,14 +405,11 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
}
|
||||
|
||||
renderChallengePicker() {
|
||||
const challenges = this.challenge.deviceChallenges.filter((challenge) => {
|
||||
if (challenge.deviceClass === "webauthn") {
|
||||
if (!this.checkWebAuthnSupport()) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return challenge;
|
||||
});
|
||||
const challenges = this.challenge.deviceChallenges.filter((challenge) =>
|
||||
challenge.deviceClass === "webauthn" && !this.checkWebAuthnSupport()
|
||||
? undefined
|
||||
: challenge,
|
||||
);
|
||||
this.html(`<form id="picker-form">
|
||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||
@ -467,7 +462,7 @@ class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge>
|
||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||
<div class="form-label-group my-3 has-validation">
|
||||
<input type="text" autofocus class="form-control ${this.error("code").length > 0 ? "is-invalid" : ""}" name="code" placeholder="Please enter your code" autocomplete="one-time-code">
|
||||
<input type="text" autofocus class="form-control ${this.error("code").length > 0 ? IS_INVALID : ""}" name="code" placeholder="Please enter your code" autocomplete="one-time-code">
|
||||
${this.renderInputError("code")}
|
||||
</div>
|
||||
<button class="btn btn-primary w-100 py-2" type="submit">Continue</button>
|
||||
|
||||
@ -93,7 +93,10 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
|
||||
// a browser reflow, which may trigger some other styling the application is monitoring,
|
||||
// triggering a re-render which triggers a browser reflow, ad infinitum. But we've been
|
||||
// living with that since jQuery, and it's both well-known and fortunately rare.
|
||||
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.remove("pf-m-expanded", "pf-m-collapsed");
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
|
||||
}
|
||||
|
||||
@ -153,7 +156,7 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
|
||||
? { ".activeWhen": attributes }
|
||||
: (attributes ?? {});
|
||||
if (path) {
|
||||
properties["path"] = path;
|
||||
properties.path = path;
|
||||
}
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
|
||||
@ -26,11 +26,6 @@ const selectStyles = css`
|
||||
*/
|
||||
@customElement("ak-multi-select")
|
||||
export class AkMultiSelect extends AkControlElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.dataset.akControl = "true";
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [PFBase, PFForm, PFFormControl, selectStyles];
|
||||
}
|
||||
@ -94,6 +89,11 @@ export class AkMultiSelect extends AkControlElement {
|
||||
return this.values;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.dataset.akControl = "true";
|
||||
}
|
||||
|
||||
renderHelp() {
|
||||
return [
|
||||
this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing,
|
||||
|
||||
@ -8,8 +8,11 @@ import { classMap } from "lit/directives/class-map.js";
|
||||
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
const statusNames = ["error", "warning", "info"] as const;
|
||||
type StatusName = (typeof statusNames)[number];
|
||||
// The 'const ... as const' construction will throw a compilation error if the const variable is
|
||||
// only ever used to generate the type information, so the `_` (ignore unused variable) prefix must
|
||||
// be used here.
|
||||
const _statusNames = ["error", "warning", "info"] as const;
|
||||
type StatusName = (typeof _statusNames)[number];
|
||||
|
||||
const statusToDetails = new Map<StatusName, [string, string]>([
|
||||
["error", ["pf-m-red", "fa-times"]],
|
||||
|
||||
@ -73,9 +73,10 @@ export class CodeMirrorTextarea<T> extends AKElement {
|
||||
}
|
||||
|
||||
@property()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
|
||||
set value(v: T | string) {
|
||||
if (v === null || v === undefined) return;
|
||||
if (v === null || v === undefined) {
|
||||
return;
|
||||
}
|
||||
// Value might be an object if within an iron-form, as that calls the getter of value
|
||||
// in the beginning and the calls this setter on reset
|
||||
let textValue = v;
|
||||
@ -114,7 +115,7 @@ export class CodeMirrorTextarea<T> extends AKElement {
|
||||
default:
|
||||
return this.getInnerValue();
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e: unknown) {
|
||||
return this.getInnerValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +136,6 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
||||
constructor() {
|
||||
super();
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.dataset.akControl = "true";
|
||||
}
|
||||
|
||||
onClick(ev: Event) {
|
||||
@ -173,6 +172,7 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.dataset.akControl = "true";
|
||||
if (this.name && !this.internals) {
|
||||
this.internals = this.attachInternals();
|
||||
}
|
||||
|
||||
@ -56,7 +56,6 @@ const container = (testItem: TemplateResult) =>
|
||||
const displayMessage = (result: any) => {
|
||||
const doc = new DOMParser().parseFromString(`<li><i>Event</i>: ${result}</li>`, "text/xml");
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.appendChild(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -42,7 +42,6 @@ const container = (testItem: TemplateResult) =>
|
||||
const displayMessage = (result: any) => {
|
||||
const doc = new DOMParser().parseFromString(`<p><i>Content</i>: ${result}</p>`, "text/xml");
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.replaceChildren(doc.firstChild!);
|
||||
};
|
||||
|
||||
@ -51,7 +50,6 @@ const displayMessage2 = (result: any) => {
|
||||
console.debug("Huh.");
|
||||
const doc = new DOMParser().parseFromString(`<p><i>Behavior</i>: ${result}</p>`, "text/xml");
|
||||
const target = document.querySelector("#action-button-message-pad-2");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.replaceChildren(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -52,7 +52,6 @@ const displayMessage = (result: any) => {
|
||||
"text/xml",
|
||||
);
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.appendChild(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -6,25 +6,33 @@ import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-dropdown")
|
||||
export class DropdownButton extends AKElement {
|
||||
menu: HTMLElement | null;
|
||||
menu: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.menu = this.querySelector<HTMLElement>(".pf-c-dropdown__menu");
|
||||
this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!this.menu) return;
|
||||
this.menu.hidden = !this.menu.hidden;
|
||||
});
|
||||
});
|
||||
window.addEventListener(EVENT_REFRESH, this.clickHandler);
|
||||
}
|
||||
|
||||
clickHandler = (): void => {
|
||||
if (!this.menu) return;
|
||||
if (!this.menu) {
|
||||
return;
|
||||
}
|
||||
this.menu.hidden = true;
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.menu = this.querySelector<HTMLElement>(".pf-c-dropdown__menu");
|
||||
this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!this.menu) {
|
||||
return;
|
||||
}
|
||||
this.menu.hidden = !this.menu.hidden;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(EVENT_REFRESH, this.clickHandler);
|
||||
|
||||
@ -51,7 +51,6 @@ const displayMessage = (result: any) => {
|
||||
"text/xml",
|
||||
);
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.appendChild(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -98,7 +98,6 @@ export class ModalOrchestrationController implements ReactiveController {
|
||||
// Pop off modals until you find the first live one, schedule it to be closed, and make that
|
||||
// cleaned list the current state. Since this is our *only* state object, this has the
|
||||
// effect of creating a new "knownModals" collection with some semantics.
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const modal = knownModals.pop();
|
||||
if (!modal) {
|
||||
|
||||
@ -48,7 +48,9 @@ function assignValue(element: HTMLNamedElement, value: unknown, json: KeyUnknown
|
||||
for (let index = 0; index < nameElements.length - 1; index++) {
|
||||
const nameEl = nameElements[index];
|
||||
// Ensure all nested structures exist
|
||||
if (!(nameEl in parent)) parent[nameEl] = {};
|
||||
if (!(nameEl in parent)) {
|
||||
parent[nameEl] = {};
|
||||
}
|
||||
parent = parent[nameEl] as { [key: string]: unknown };
|
||||
}
|
||||
parent[nameElements[nameElements.length - 1]] = value;
|
||||
@ -103,7 +105,7 @@ export function serializeForm<T extends KeyUnknown>(
|
||||
} else if (
|
||||
inputElement.tagName.toLowerCase() === "input" &&
|
||||
"type" in inputElement.dataset &&
|
||||
inputElement.dataset["type"] === "datetime-local"
|
||||
inputElement.dataset.type === "datetime-local"
|
||||
) {
|
||||
// Workaround for Firefox <93, since 92 and older don't support
|
||||
// datetime-local fields
|
||||
@ -122,6 +124,9 @@ export function serializeForm<T extends KeyUnknown>(
|
||||
return json as unknown as T;
|
||||
}
|
||||
|
||||
const HTTP_BAD_REQUEST = 400;
|
||||
const HTTP_INTERNAL_SERVICE_ERROR = 500;
|
||||
|
||||
/**
|
||||
* Form
|
||||
*
|
||||
@ -188,7 +193,7 @@ export abstract class Form<T> extends AKElement {
|
||||
*/
|
||||
get isInViewport(): boolean {
|
||||
const rect = this.getBoundingClientRect();
|
||||
return !(rect.x + rect.y + rect.width + rect.height === 0);
|
||||
return rect.x + rect.y + rect.width + rect.height !== 0;
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
@ -275,7 +280,6 @@ export abstract class Form<T> extends AKElement {
|
||||
}
|
||||
return serializeForm(elements) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and send the form to the destination. The `send()` method must be overridden for
|
||||
* this to work. If processing the data results in an error, we catch the error, distribute
|
||||
@ -304,9 +308,14 @@ export abstract class Form<T> extends AKElement {
|
||||
} catch (ex) {
|
||||
if (ex instanceof ResponseError) {
|
||||
let msg = ex.response.statusText;
|
||||
if (ex.response.status > 399 && ex.response.status < 500) {
|
||||
if (
|
||||
ex.response.status >= HTTP_BAD_REQUEST &&
|
||||
ex.response.status < HTTP_INTERNAL_SERVICE_ERROR
|
||||
) {
|
||||
const errorMessage = ValidationErrorFromJSON(await ex.response.json());
|
||||
if (!errorMessage) return errorMessage;
|
||||
if (!errorMessage) {
|
||||
return errorMessage;
|
||||
}
|
||||
if (errorMessage instanceof Error) {
|
||||
throw errorMessage;
|
||||
}
|
||||
@ -318,7 +327,9 @@ export abstract class Form<T> extends AKElement {
|
||||
elements.forEach((element) => {
|
||||
element.requestUpdate();
|
||||
const elementName = element.name;
|
||||
if (!elementName) return;
|
||||
if (!elementName) {
|
||||
return;
|
||||
}
|
||||
if (camelToSnake(elementName) in errorMessage) {
|
||||
element.errorMessages = errorMessage[camelToSnake(elementName)];
|
||||
element.invalid = true;
|
||||
|
||||
@ -20,7 +20,8 @@ import {
|
||||
SearchSelectSelectEvent,
|
||||
SearchSelectSelectMenuEvent,
|
||||
} from "./SearchSelectEvents.js";
|
||||
import type { SearchOptions, SearchTuple } from "./types.js";
|
||||
import type { SearchOptions } from "./types.js";
|
||||
import { optionsToOptionsMap } from "./utils.js";
|
||||
|
||||
/**
|
||||
* @class SearchSelectView
|
||||
@ -225,8 +226,8 @@ export class SearchSelectView extends AKElement {
|
||||
}
|
||||
|
||||
updated() {
|
||||
if (!(this.inputRef?.value && this.inputRef?.value?.value === this.displayValue)) {
|
||||
this.inputRef.value && (this.inputRef.value.value = this.displayValue);
|
||||
if (this.inputRef?.value && this.inputRef?.value?.value !== this.displayValue) {
|
||||
this.inputRef.value.value = this.displayValue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,21 +265,6 @@ export class SearchSelectView extends AKElement {
|
||||
}
|
||||
}
|
||||
|
||||
type Pair = [string, string];
|
||||
const justThePair = ([key, label]: SearchTuple): Pair => [key, label];
|
||||
|
||||
function optionsToOptionsMap(options: SearchOptions): Map<string, string> {
|
||||
const pairs: Pair[] = Array.isArray(options)
|
||||
? options.map(justThePair)
|
||||
: options.grouped
|
||||
? options.options.reduce(
|
||||
(acc: Pair[], { options }): Pair[] => [...acc, ...options.map(justThePair)],
|
||||
[] as Pair[],
|
||||
)
|
||||
: options.options.map(justThePair);
|
||||
return new Map(pairs);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-search-select-view": SearchSelectView;
|
||||
|
||||
@ -97,11 +97,6 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
|
||||
@state()
|
||||
error?: APIErrorTypes;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.dataset.akControl = "true";
|
||||
}
|
||||
|
||||
toForm(): unknown {
|
||||
if (!this.objects) {
|
||||
throw new PreventFormSubmit(msg("Loading options..."));
|
||||
@ -113,9 +108,9 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
|
||||
return this.toForm();
|
||||
}
|
||||
|
||||
updateData() {
|
||||
async updateData() {
|
||||
if (this.isFetchingData) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.isFetchingData = true;
|
||||
return this.fetchObjects(this.query)
|
||||
@ -140,6 +135,7 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.dataset.akControl = "true";
|
||||
this.updateData();
|
||||
this.addEventListener(EVENT_REFRESH, this.updateData);
|
||||
}
|
||||
|
||||
@ -29,7 +29,6 @@ const metadata: Meta<SearchSelectMenu> = {
|
||||
|
||||
export default metadata;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const onClick = (event: SearchSelectSelectMenuEvent) => {
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
target!.innerHTML = "";
|
||||
|
||||
16
web/src/elements/forms/SearchSelect/utils.ts
Normal file
16
web/src/elements/forms/SearchSelect/utils.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { SearchOptions, SearchTuple } from "./types.js";
|
||||
|
||||
type Pair = [string, string];
|
||||
const justThePair = ([key, label]: SearchTuple): Pair => [key, label];
|
||||
|
||||
export function optionsToOptionsMap(options: SearchOptions): Map<string, string> {
|
||||
const pairs: Pair[] = Array.isArray(options)
|
||||
? options.map(justThePair)
|
||||
: options.grouped
|
||||
? options.options.reduce(
|
||||
(acc: Pair[], { options }): Pair[] => [...acc, ...options.map(justThePair)],
|
||||
[] as Pair[],
|
||||
)
|
||||
: options.options.map(justThePair);
|
||||
return new Map(pairs);
|
||||
}
|
||||
@ -4,44 +4,44 @@
|
||||
/**
|
||||
* The locale code that templates in this source code are written in.
|
||||
*/
|
||||
export const sourceLocale = `en`;
|
||||
export const sourceLocale = "en";
|
||||
|
||||
/**
|
||||
* The other locale codes that this application is localized into. Sorted
|
||||
* lexicographically.
|
||||
*/
|
||||
export const targetLocales = [
|
||||
`de`,
|
||||
`en`,
|
||||
`es`,
|
||||
`fr`,
|
||||
`ko`,
|
||||
`nl`,
|
||||
`pl`,
|
||||
`pseudo-LOCALE`,
|
||||
`tr`,
|
||||
`zh_TW`,
|
||||
`zh-CN`,
|
||||
`zh-Hans`,
|
||||
`zh-Hant`,
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"ko",
|
||||
"nl",
|
||||
"pl",
|
||||
"pseudo-LOCALE",
|
||||
"tr",
|
||||
"zh_TW",
|
||||
"zh-CN",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* All valid project locale codes. Sorted lexicographically.
|
||||
*/
|
||||
export const allLocales = [
|
||||
`de`,
|
||||
`en`,
|
||||
`en`,
|
||||
`es`,
|
||||
`fr`,
|
||||
`ko`,
|
||||
`nl`,
|
||||
`pl`,
|
||||
`pseudo-LOCALE`,
|
||||
`tr`,
|
||||
`zh_TW`,
|
||||
`zh-CN`,
|
||||
`zh-Hans`,
|
||||
`zh-Hant`,
|
||||
"de",
|
||||
"en",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"ko",
|
||||
"nl",
|
||||
"pl",
|
||||
"pseudo-LOCALE",
|
||||
"tr",
|
||||
"zh_TW",
|
||||
"zh-CN",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
] as const;
|
||||
|
||||
@ -104,7 +104,7 @@ export class LibraryPage extends AKElement {
|
||||
searchUpdated(event: LibraryPageSearchUpdated) {
|
||||
event.stopPropagation();
|
||||
const apps = event.apps;
|
||||
if (!(apps.length > 0)) {
|
||||
if (apps.length <= 0) {
|
||||
throw new Error(
|
||||
"LibaryPageSearchUpdated had empty results body. This must not happen.",
|
||||
);
|
||||
@ -116,7 +116,9 @@ export class LibraryPage extends AKElement {
|
||||
@bound
|
||||
launchRequest(event: LibraryPageSearchSelected) {
|
||||
event.stopPropagation();
|
||||
this.selectedApp?.launchUrl && window.location.assign(this.selectedApp?.launchUrl);
|
||||
if (this.selectedApp?.launchUrl) {
|
||||
window.location.assign(this.selectedApp?.launchUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@bound
|
||||
|
||||
@ -117,7 +117,6 @@ const customStyles = css`
|
||||
|
||||
@customElement("ak-interface-user-presentation")
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
class UserInterfacePresentation extends AKElement {
|
||||
static get styles() {
|
||||
return [
|
||||
@ -169,7 +168,7 @@ class UserInterfacePresentation extends AKElement {
|
||||
}
|
||||
|
||||
get isFullyConfigured() {
|
||||
return !!(this.uiConfig && this.me && this.brand);
|
||||
return Boolean(this.uiConfig && this.me && this.brand);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -455,7 +454,7 @@ export class UserInterface extends EnterpriseAwareInterface {
|
||||
}
|
||||
|
||||
get isFullyConfigured() {
|
||||
return !!(this.uiConfig && this.me);
|
||||
return Boolean(this.uiConfig && this.me);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
Reference in New Issue
Block a user