From cd3433815219495337efea8e2bbd848d45a0918f Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Date: Thu, 2 Jul 2026 23:18:52 -0300 Subject: [PATCH] chore: add eslint/prettier, lint script, CI lint, tighten tsconfig - eslint (typescript-eslint) + prettier config with a lint script. - Add a Lint step to the CI workflow (runs on PRs and master). - Tighten tsconfig: noImplicitReturns, noFallthroughCasesInSwitch. Closes #4 --- .eslintrc.cjs | 38 + .github/workflows/ci.yml | 3 + .prettierignore | 6 + .prettierrc.json | 8 + package-lock.json | 1733 +++++++++++++++++ package.json | 12 +- sample-vault/.obsidian/plugins/leif/main.js | 630 +++--- .../.obsidian/plugins/leif/styles.css | 25 + tsconfig.json | 2 + 9 files changed, 2228 insertions(+), 229 deletions(-) create mode 100644 .eslintrc.cjs create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..324952e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,38 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + ecmaVersion: 2020, + sourceType: "module" + }, + plugins: ["@typescript-eslint"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + env: { + browser: true, + es2020: true, + node: true + }, + rules: { + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" } + ], + "@typescript-eslint/no-non-null-assertion": "off", + "no-inner-declarations": "off", + "no-case-declarations": "off" + }, + ignorePatterns: [ + "node_modules/", + "dist/", + "coverage/", + "sample-vault/", + "esbuild.config.mjs", + "main.js" + ] +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a50c6d..255d277 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Lint + run: npm run lint + - name: Run tests run: npm test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..dd7efa9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +coverage/ +sample-vault/ +main.js +*.md diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..fff5ef0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "none", + "printWidth": 100, + "tabWidth": 2, + "useTabs": false +} diff --git a/package-lock.json b/package-lock.json index f3dba55..17f075a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,14 @@ "license": "MIT", "devDependencies": { "@types/node": "^24.0.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "esbuild": "^0.25.5", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.1.2", "jsdom": "^29.1.1", "obsidian": "^1.8.10", + "prettier": "^3.9.4", "typescript": "^5.8.3", "vitest": "^3.2.4" } @@ -688,6 +693,93 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@exodus/bytes": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", @@ -706,6 +798,68 @@ } } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -721,6 +875,44 @@ "license": "MIT", "peer": true }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", @@ -1165,6 +1357,206 @@ "@types/estree": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitest/expect": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.6.tgz", @@ -1280,6 +1672,89 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1290,6 +1765,13 @@ "node": ">=12" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -1300,6 +1782,29 @@ "require-from-string": "^2.0.2" } }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1310,6 +1815,16 @@ "node": ">=8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -1327,6 +1842,23 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -1337,6 +1869,33 @@ "node": ">= 16" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -1345,6 +1904,21 @@ "license": "MIT", "peer": true }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -1408,6 +1982,39 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", @@ -1470,6 +2077,197 @@ "@esbuild/win32-x64": "0.25.12" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1480,6 +2278,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -1490,6 +2298,67 @@ "node": ">=12.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1508,6 +2377,78 @@ } } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1523,6 +2464,119 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -1536,6 +2590,105 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -1543,6 +2696,13 @@ "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -1550,6 +2710,29 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdom": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", @@ -1591,6 +2774,74 @@ } } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -1625,6 +2876,59 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -1661,6 +2965,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/obsidian": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.13.1.tgz", @@ -1676,6 +2987,79 @@ "@codemirror/view": "6.38.6" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -1689,6 +3073,46 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1755,6 +3179,32 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", + "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1765,6 +3215,27 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1775,6 +3246,44 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", @@ -1820,6 +3329,30 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -1833,6 +3366,42 @@ "node": ">=v12.22.7" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -1840,6 +3409,16 @@ "dev": true, "license": "ISC" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1864,6 +3443,32 @@ "dev": true, "license": "MIT" }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", @@ -1885,6 +3490,19 @@ "license": "MIT", "peer": true }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -1892,6 +3510,13 @@ "dev": true, "license": "MIT" }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -1973,6 +3598,19 @@ "dev": true, "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -1999,6 +3637,45 @@ "node": ">=20" } }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -2030,6 +3707,16 @@ "dev": true, "license": "MIT" }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/vite": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", @@ -2741,6 +4428,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -2758,6 +4461,23 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -2774,6 +4494,19 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 16edf43..4630e50 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,23 @@ "build": "node esbuild.config.mjs production", "dev": "node esbuild.config.mjs", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "tsc --noEmit" }, "devDependencies": { "@types/node": "^24.0.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "esbuild": "^0.25.5", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.1.2", "jsdom": "^29.1.1", "obsidian": "^1.8.10", + "prettier": "^3.9.4", "typescript": "^5.8.3", "vitest": "^3.2.4" } diff --git a/sample-vault/.obsidian/plugins/leif/main.js b/sample-vault/.obsidian/plugins/leif/main.js index e1c899a..4e0d231 100644 --- a/sample-vault/.obsidian/plugins/leif/main.js +++ b/sample-vault/.obsidian/plugins/leif/main.js @@ -463,113 +463,6 @@ var ContestState = class { } }; -// src/infrastructure/persistence/EntityRepository.ts -var EntityRepository = class { - constructor(dataStore, entityKey) { - this.dataStore = dataStore; - this.entityKey = entityKey; - } - /** - * Finds an entity by ID. - * - * @param id - The entity ID - * @returns The found entity - * @throws {NotFoundError} If the entity is not found - */ - async findById(id) { - const data = await this.dataStore.load(); - const entities = data[this.entityKey]; - const entity = entities.find((e) => e.id === id); - if (!entity) { - throw new NotFoundError(String(this.entityKey), id); - } - return entity; - } - /** - * Finds all entities. - * - * @returns Array of all entities - */ - async findAll() { - const data = await this.dataStore.load(); - return data[this.entityKey]; - } - /** - * Checks if an entity exists by ID. - * - * @param id - The entity ID - * @returns True if the entity exists, false otherwise - */ - async exists(id) { - const data = await this.dataStore.load(); - const entities = data[this.entityKey]; - return entities.some((e) => e.id === id); - } - /** - * Creates a new entity. - * - * @param entity - The entity to create - * @returns The created entity - * @throws {AlreadyExistsError} If an entity with the same ID already exists - */ - async create(entity) { - const data = await this.dataStore.load(); - const entities = data[this.entityKey]; - if (entities.some((e) => e.id === entity.id)) { - throw new AlreadyExistsError(String(this.entityKey), entity.id); - } - entities.push(entity); - await this.dataStore.save(data); - return entity; - } - /** - * Updates an existing entity using an updater function. - * - * @param id - The entity ID - * @param updater - Function that takes the entity and returns the updated version - * @returns The updated entity - * @throws {NotFoundError} If the entity is not found - */ - async update(id, updater) { - const data = await this.dataStore.load(); - const entities = data[this.entityKey]; - const index = entities.findIndex((e) => e.id === id); - if (index === -1) { - throw new NotFoundError(String(this.entityKey), id); - } - const updated = updater(entities[index]); - entities[index] = updated; - await this.dataStore.save(data); - return updated; - } - /** - * Deletes an entity by ID. - * - * @param id - The entity ID - * @throws {NotFoundError} If the entity is not found - */ - async delete(id) { - const data = await this.dataStore.load(); - const entities = data[this.entityKey]; - const index = entities.findIndex((e) => e.id === id); - if (index === -1) { - throw new NotFoundError(String(this.entityKey), id); - } - entities.splice(index, 1); - await this.dataStore.save(data); - } - /** - * Replaces all entities in the collection. - * - * @param entities - The new entities to store - */ - async replaceAll(entities) { - const data = await this.dataStore.load(); - data[this.entityKey] = entities; - await this.dataStore.save(data); - } -}; - // src/application/validation/InputValidators.ts var ValidationResult = { ok() { @@ -722,9 +615,9 @@ var UpdateContestWallValidator = class { // src/application/use-cases/CreateContestUseCase.ts var CreateContestUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); + this.contestRepository = repositoryFactory.for("contests"); } async execute(input) { const validation = new CreateContestValidator().validate(input); @@ -767,10 +660,10 @@ var StudyItem = class { // src/application/use-cases/CreateStudyItemUseCase.ts var CreateStudyItemUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.subjectRepository = new EntityRepository(dataStore, "subjects"); - this.studyItemRepository = new EntityRepository(dataStore, "studyItems"); + this.subjectRepository = repositoryFactory.for("subjects"); + this.studyItemRepository = repositoryFactory.for("studyItems"); } async execute(input) { const validation = new CreateStudyItemValidator().validate(input); @@ -820,10 +713,10 @@ var Subject = class { // src/application/use-cases/CreateSubjectUseCase.ts var CreateSubjectUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); - this.subjectRepository = new EntityRepository(dataStore, "subjects"); + this.contestRepository = repositoryFactory.for("contests"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const validation = new CreateSubjectValidator().validate(input); @@ -869,10 +762,10 @@ var Topic = class { // src/application/use-cases/CreateTopicUseCase.ts var CreateTopicUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.topicRepository = new EntityRepository(dataStore, "topics"); - this.subjectRepository = new EntityRepository(dataStore, "subjects"); + this.topicRepository = repositoryFactory.for("topics"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const validation = new CreateTopicValidator().validate(input); @@ -992,10 +885,10 @@ var ListSubjectsForActiveContestUseCase = class { // src/application/use-cases/ReorderSubjectsUseCase.ts var ReorderSubjectsUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); - this.subjectRepository = new EntityRepository(dataStore, "subjects"); + this.contestRepository = repositoryFactory.for("contests"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const validation = new ReorderSubjectsValidator().validate(input); @@ -1032,12 +925,12 @@ var ReorderSubjectsUseCase = class { // src/application/use-cases/RegisterStudySessionUseCase.ts var RegisterStudySessionUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); - this.subjectRepository = new EntityRepository(dataStore, "subjects"); - this.topicRepository = new EntityRepository(dataStore, "topics"); - this.sessionRepository = new EntityRepository(dataStore, "studySessions"); + this.contestRepository = repositoryFactory.for("contests"); + this.subjectRepository = repositoryFactory.for("subjects"); + this.topicRepository = repositoryFactory.for("topics"); + this.sessionRepository = repositoryFactory.for("studySessions"); } async execute(input) { const validation = new RegisterStudySessionValidator().validate(input); @@ -1095,9 +988,9 @@ var RegisterStudySessionUseCase = class { // src/application/use-cases/SetSubjectActiveStateUseCase.ts var SetSubjectActiveStateUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.subjectRepository = new EntityRepository(dataStore, "subjects"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const validation = new SetSubjectActiveStateValidator().validate(input); @@ -1113,9 +1006,9 @@ var SetSubjectActiveStateUseCase = class { // src/application/use-cases/SetActiveContestUseCase.ts var SetActiveContestUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); + this.contestRepository = repositoryFactory.for("contests"); } async execute(input) { const validation = new SetActiveContestValidator().validate(input); @@ -1133,9 +1026,9 @@ var SetActiveContestUseCase = class { // src/application/use-cases/UpdateContestWallUseCase.ts var UpdateContestWallUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); + this.contestRepository = repositoryFactory.for("contests"); } async execute(input) { const validation = new UpdateContestWallValidator().validate(input); @@ -1151,9 +1044,9 @@ var UpdateContestWallUseCase = class { // src/application/use-cases/UpdateSubjectConfigurationUseCase.ts var UpdateSubjectConfigurationUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.subjectRepository = new EntityRepository(dataStore, "subjects"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const validation = new UpdateSubjectConfigurationValidator().validate(input); @@ -1168,11 +1061,128 @@ var UpdateSubjectConfigurationUseCase = class { } }; -// src/application/use-cases/LinkQuestionNotebookUseCase.ts -var LinkQuestionNotebookUseCase = class { +// src/infrastructure/persistence/EntityRepository.ts +var EntityRepository = class { + constructor(dataStore, entityKey) { + this.dataStore = dataStore; + this.entityKey = entityKey; + } + /** + * Finds an entity by ID. + * + * @param id - The entity ID + * @returns The found entity + * @throws {NotFoundError} If the entity is not found + */ + async findById(id) { + const data = await this.dataStore.load(); + const entities = data[this.entityKey]; + const entity = entities.find((e) => e.id === id); + if (!entity) { + throw new NotFoundError(String(this.entityKey), id); + } + return entity; + } + /** + * Finds all entities. + * + * @returns Array of all entities + */ + async findAll() { + const data = await this.dataStore.load(); + return data[this.entityKey]; + } + /** + * Checks if an entity exists by ID. + * + * @param id - The entity ID + * @returns True if the entity exists, false otherwise + */ + async exists(id) { + const data = await this.dataStore.load(); + const entities = data[this.entityKey]; + return entities.some((e) => e.id === id); + } + /** + * Creates a new entity. + * + * @param entity - The entity to create + * @returns The created entity + * @throws {AlreadyExistsError} If an entity with the same ID already exists + */ + async create(entity) { + const data = await this.dataStore.load(); + const entities = data[this.entityKey]; + if (entities.some((e) => e.id === entity.id)) { + throw new AlreadyExistsError(String(this.entityKey), entity.id); + } + entities.push(entity); + await this.dataStore.save(data); + return entity; + } + /** + * Updates an existing entity using an updater function. + * + * @param id - The entity ID + * @param updater - Function that takes the entity and returns the updated version + * @returns The updated entity + * @throws {NotFoundError} If the entity is not found + */ + async update(id, updater) { + const data = await this.dataStore.load(); + const entities = data[this.entityKey]; + const index = entities.findIndex((e) => e.id === id); + if (index === -1) { + throw new NotFoundError(String(this.entityKey), id); + } + const updated = updater(entities[index]); + entities[index] = updated; + await this.dataStore.save(data); + return updated; + } + /** + * Deletes an entity by ID. + * + * @param id - The entity ID + * @throws {NotFoundError} If the entity is not found + */ + async delete(id) { + const data = await this.dataStore.load(); + const entities = data[this.entityKey]; + const index = entities.findIndex((e) => e.id === id); + if (index === -1) { + throw new NotFoundError(String(this.entityKey), id); + } + entities.splice(index, 1); + await this.dataStore.save(data); + } + /** + * Replaces all entities in the collection. + * + * @param entities - The new entities to store + */ + async replaceAll(entities) { + const data = await this.dataStore.load(); + data[this.entityKey] = entities; + await this.dataStore.save(data); + } +}; + +// src/infrastructure/persistence/EntityRepositoryFactory.ts +var EntityRepositoryFactory = class { constructor(dataStore) { this.dataStore = dataStore; - this.topicRepository = new EntityRepository(dataStore, "topics"); + } + for(key) { + return new EntityRepository(this.dataStore, key); + } +}; + +// src/application/use-cases/LinkQuestionNotebookUseCase.ts +var LinkQuestionNotebookUseCase = class { + constructor(dataStore, repositoryFactory) { + this.dataStore = dataStore; + this.topicRepository = repositoryFactory.for("topics"); } async execute(input) { const validation = new LinkQuestionNotebookValidator().validate(input); @@ -1490,14 +1500,15 @@ var DEMO_CONTESTS = [ } ]; async function seedTceSpDemo(dataStore) { - const createContest = new CreateContestUseCase(dataStore); - const createSubject = new CreateSubjectUseCase(dataStore); - const createItem = new CreateStudyItemUseCase(dataStore); - const createTopic = new CreateTopicUseCase(dataStore); - const linkNotebook = new LinkQuestionNotebookUseCase(dataStore); - const updateWall = new UpdateContestWallUseCase(dataStore); - const registerSession = new RegisterStudySessionUseCase(dataStore); - const setActive = new SetActiveContestUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + const createContest = new CreateContestUseCase(dataStore, repositoryFactory); + const createSubject = new CreateSubjectUseCase(dataStore, repositoryFactory); + const createItem = new CreateStudyItemUseCase(dataStore, repositoryFactory); + const createTopic = new CreateTopicUseCase(dataStore, repositoryFactory); + const linkNotebook = new LinkQuestionNotebookUseCase(dataStore, repositoryFactory); + const updateWall = new UpdateContestWallUseCase(dataStore, repositoryFactory); + const registerSession = new RegisterStudySessionUseCase(dataStore, repositoryFactory); + const setActive = new SetActiveContestUseCase(dataStore, repositoryFactory); const seededContests = []; for (const contestSpec of DEMO_CONTESTS) { const contest = await createContest.execute({ id: contestSpec.id, name: contestSpec.name }); @@ -1595,20 +1606,21 @@ async function seedTceSpDemo(dataStore) { // src/ui/commands/registerCommands.ts function registerCommands(plugin, dataStore) { - const createContest = new CreateContestUseCase(dataStore); - const createSubject = new CreateSubjectUseCase(dataStore); - const createStudyItem = new CreateStudyItemUseCase(dataStore); - const createTopic = new CreateTopicUseCase(dataStore); - const updateContestWall = new UpdateContestWallUseCase(dataStore); - const registerStudySession = new RegisterStudySessionUseCase(dataStore); - const setActiveContest = new SetActiveContestUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + const createContest = new CreateContestUseCase(dataStore, repositoryFactory); + const createSubject = new CreateSubjectUseCase(dataStore, repositoryFactory); + const createStudyItem = new CreateStudyItemUseCase(dataStore, repositoryFactory); + const createTopic = new CreateTopicUseCase(dataStore, repositoryFactory); + const updateContestWall = new UpdateContestWallUseCase(dataStore, repositoryFactory); + const registerStudySession = new RegisterStudySessionUseCase(dataStore, repositoryFactory); + const setActiveContest = new SetActiveContestUseCase(dataStore, repositoryFactory); const advanceCycle = new AdvanceCycleUseCase(dataStore); const getActiveCycleSnapshot = new GetActiveCycleSnapshotUseCase(dataStore); const getActiveContestSummary = new GetActiveContestSummaryUseCase(dataStore); const listSubjectsForActiveContest = new ListSubjectsForActiveContestUseCase(dataStore); - const reorderSubjects = new ReorderSubjectsUseCase(dataStore); - const setSubjectActiveState = new SetSubjectActiveStateUseCase(dataStore); - const updateSubjectConfiguration = new UpdateSubjectConfigurationUseCase(dataStore); + const reorderSubjects = new ReorderSubjectsUseCase(dataStore, repositoryFactory); + const setSubjectActiveState = new SetSubjectActiveStateUseCase(dataStore, repositoryFactory); + const updateSubjectConfiguration = new UpdateSubjectConfigurationUseCase(dataStore, repositoryFactory); plugin.addCommand({ id: "leif-show-active-contest", name: "Show active contest", @@ -1864,22 +1876,33 @@ var import_obsidian9 = require("obsidian"); // src/application/use-cases/DeleteContestUseCase.ts var DeleteContestUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); + this.contestRepository = repositoryFactory.for("contests"); } async execute(input) { const contest = await this.contestRepository.findById(input.contestId); - await this.contestRepository.delete(input.contestId); + const data = await this.dataStore.load(); + const subjectIds = new Set(contest.subjectIds); + data.contests = data.contests.filter((c) => c.id !== input.contestId); + data.contestStates = data.contestStates.filter((s) => s.contestId !== input.contestId); + data.subjects = data.subjects.filter((s) => s.contestId !== input.contestId); + data.studyItems = data.studyItems.filter((i) => !subjectIds.has(i.subjectId)); + data.topics = data.topics.filter((t) => !subjectIds.has(t.subjectId)); + data.studySessions = data.studySessions.filter((s) => s.contestId !== input.contestId); + if (data.activeContestId === input.contestId) { + data.activeContestId = null; + } + await this.dataStore.save(data); return contest; } }; // src/application/use-cases/UpdateContestUseCase.ts var UpdateContestUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.contestRepository = new EntityRepository(dataStore, "contests"); + this.contestRepository = repositoryFactory.for("contests"); } async execute(input) { return await this.contestRepository.update(input.contestId, (contest) => ({ @@ -2174,6 +2197,7 @@ var DomHelpers = class { const button = document.createElement("button"); button.type = "button"; button.className = options.className || "leif-icon-button"; + button.setAttribute("aria-label", title); button.appendChild(this.createIcon(icon, "leif-icon-button-icon")); if (options.dataset) { Object.entries(options.dataset).forEach(([key, value]) => { @@ -2322,13 +2346,19 @@ var DomHelpers = class { } /** * Creates a modal overlay with a centered card. + * Implements role=dialog, aria-modal, a focus trap and Escape-to-close. * Returns { open, close } functions. */ static createModal(options) { const overlay = this.createElement("div", "leif-modal-overlay"); const card = this.createElement("div", "leif-modal-card"); + card.setAttribute("role", "dialog"); + card.setAttribute("aria-modal", "true"); + const titleId = `leif-modal-title-${Math.random().toString(36).slice(2, 9)}`; + card.setAttribute("aria-labelledby", titleId); const header = this.createElement("div", "leif-modal-header"); const title = this.createElement("h3", "leif-modal-title"); + title.id = titleId; title.textContent = options.title; const closeButton = this.createIconButton("x", "Fechar", { onClick: () => close() @@ -2340,23 +2370,65 @@ var DomHelpers = class { const footer = this.createElement("div", "leif-modal-footer"); const cancelButton = this.createButton("Cancelar", { className: "leif-button", + dataset: { leifConfirm: "cancel" }, onClick: () => close() }); const submitButton = this.createButton(options.submitLabel ?? "Criar", { className: "leif-primary-button", + dataset: { leifConfirm: "submit" }, onClick: () => options.onSubmit() }); footer.appendChild(cancelButton); footer.appendChild(submitButton); card.append(header, body, footer); overlay.appendChild(card); + let previouslyFocused = null; + let focusTrapHandler = null; + let escapeHandler = null; + const focusable = () => { + const elements = card.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + return Array.from(elements).filter((element) => !element.hasAttribute("disabled")); + }; const open = () => { + previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null; document.body.appendChild(overlay); + const first = focusable()[0] ?? card; + first.focus(); + focusTrapHandler = (event) => { + if (event.key !== "Tab") return; + const elements = focusable(); + if (elements.length === 0) { + event.preventDefault(); + return; + } + const firstEl = elements[0]; + const lastEl = elements[elements.length - 1]; + if (event.shiftKey && document.activeElement === firstEl) { + event.preventDefault(); + lastEl.focus(); + } else if (!event.shiftKey && document.activeElement === lastEl) { + event.preventDefault(); + firstEl.focus(); + } + }; + escapeHandler = (event) => { + if (event.key === "Escape") { + event.preventDefault(); + close(); + } + }; + card.addEventListener("keydown", focusTrapHandler); + overlay.addEventListener("keydown", escapeHandler); }; const close = () => { + if (focusTrapHandler) card.removeEventListener("keydown", focusTrapHandler); + if (escapeHandler) overlay.removeEventListener("keydown", escapeHandler); if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } + previouslyFocused?.focus(); options.onCancel?.(); }; overlay.addEventListener("click", (event) => { @@ -2366,6 +2438,33 @@ var DomHelpers = class { }); return { open, close }; } + /** + * Creates an accessible confirmation dialog (replaces native window.confirm). + * Resolves the returned promise with true when confirmed, false when cancelled. + */ + static confirm(options) { + return new Promise((resolve) => { + let resolved = false; + const settle = (value) => { + if (resolved) return; + resolved = true; + resolve(value); + }; + const message = this.createElement("p", "leif-modal-message"); + message.textContent = options.message; + const modal = this.createModal({ + title: options.title, + content: message, + submitLabel: options.confirmLabel ?? "Confirmar", + onSubmit: () => { + settle(true); + modal.close(); + }, + onCancel: () => settle(false) + }); + modal.open(); + }); + } /** * Creates a visual progress bar with a fill and a label. * @param readed - Pages readed @@ -2407,10 +2506,11 @@ var ContestsTab = class { this.dataStore = dataStore; this.onUpdate = onUpdate; this.editingContestId = null; - this.createContestUseCase = new CreateContestUseCase(dataStore); - this.setActiveContestUseCase = new SetActiveContestUseCase(dataStore); - this.updateContestUseCase = new UpdateContestUseCase(dataStore); - this.deleteContestUseCase = new DeleteContestUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + this.createContestUseCase = new CreateContestUseCase(dataStore, repositoryFactory); + this.setActiveContestUseCase = new SetActiveContestUseCase(dataStore, repositoryFactory); + this.updateContestUseCase = new UpdateContestUseCase(dataStore, repositoryFactory); + this.deleteContestUseCase = new DeleteContestUseCase(dataStore, repositoryFactory); } async render(container, data) { const header = DomHelpers.createElement("div", "leif-section-header"); @@ -2480,13 +2580,17 @@ var ContestsTab = class { actions.appendChild( DomHelpers.createIconButton("delete", "Excluir", { onClick: async () => { - if (confirm(`Excluir "${contest.name}"?`)) { - try { - await this.deleteContestUseCase.execute({ contestId: contest.id }); - await this.onUpdate(); - } catch (error) { - this.notifyError(error, "N\xE3o foi poss\xEDvel excluir o concurso."); - } + const confirmed = await DomHelpers.confirm({ + title: "Excluir concurso", + message: `Excluir "${contest.name}"?`, + confirmLabel: "Excluir" + }); + if (!confirmed) return; + try { + await this.deleteContestUseCase.execute({ contestId: contest.id }); + await this.onUpdate(); + } catch (error) { + this.notifyError(error, "N\xE3o foi poss\xEDvel excluir o concurso."); } } }) @@ -2576,11 +2680,12 @@ var CycleTab = class { this.dataStore = dataStore; this.onUpdate = onUpdate; this.editingSubjectId = null; - this.createSubjectUseCase = new CreateSubjectUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + this.createSubjectUseCase = new CreateSubjectUseCase(dataStore, repositoryFactory); this.listSubjectsForActiveContestUseCase = new ListSubjectsForActiveContestUseCase(dataStore); - this.reorderSubjectsUseCase = new ReorderSubjectsUseCase(dataStore); - this.setSubjectActiveStateUseCase = new SetSubjectActiveStateUseCase(dataStore); - this.updateSubjectConfigurationUseCase = new UpdateSubjectConfigurationUseCase(dataStore); + this.reorderSubjectsUseCase = new ReorderSubjectsUseCase(dataStore, repositoryFactory); + this.setSubjectActiveStateUseCase = new SetSubjectActiveStateUseCase(dataStore, repositoryFactory); + this.updateSubjectConfigurationUseCase = new UpdateSubjectConfigurationUseCase(dataStore, repositoryFactory); } /** * Renders the cycle tab content. @@ -2953,9 +3058,9 @@ var import_obsidian5 = require("obsidian"); // src/application/use-cases/AddStudyItemResourceReferenceUseCase.ts var AddStudyItemResourceReferenceUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.studyItemRepository = new EntityRepository(dataStore, "studyItems"); + this.studyItemRepository = repositoryFactory.for("studyItems"); } async execute(input) { const validation = new AddStudyItemResourceReferenceValidator().validate(input); @@ -2971,22 +3076,27 @@ var AddStudyItemResourceReferenceUseCase = class { // src/application/use-cases/DeleteStudyItemUseCase.ts var DeleteStudyItemUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.itemRepository = new EntityRepository(dataStore, "studyItems"); + this.itemRepository = repositoryFactory.for("studyItems"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const item = await this.itemRepository.findById(input.itemId); await this.itemRepository.delete(input.itemId); + await this.subjectRepository.update(item.subjectId, (subject) => ({ + ...subject, + itemIds: subject.itemIds.filter((id) => id !== input.itemId) + })); return item; } }; // src/application/use-cases/UpdateStudyItemUseCase.ts var UpdateStudyItemUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.itemRepository = new EntityRepository(dataStore, "studyItems"); + this.itemRepository = repositoryFactory.for("studyItems"); } async execute(input) { if (!input.itemId?.trim()) { @@ -3022,11 +3132,12 @@ var ItemsTab = class { this.selectedSubjectId = null; this.editingItemId = null; this.expandedItemId = null; - this.createStudyItemUseCase = new CreateStudyItemUseCase(dataStore); - this.addStudyItemResourceReferenceUseCase = new AddStudyItemResourceReferenceUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + this.createStudyItemUseCase = new CreateStudyItemUseCase(dataStore, repositoryFactory); + this.addStudyItemResourceReferenceUseCase = new AddStudyItemResourceReferenceUseCase(dataStore, repositoryFactory); this.getActiveContestProgressDashboardUseCase = new GetActiveContestProgressDashboardUseCase(dataStore); - this.deleteStudyItemUseCase = new DeleteStudyItemUseCase(dataStore); - this.updateStudyItemUseCase = new UpdateStudyItemUseCase(dataStore); + this.deleteStudyItemUseCase = new DeleteStudyItemUseCase(dataStore, repositoryFactory); + this.updateStudyItemUseCase = new UpdateStudyItemUseCase(dataStore, repositoryFactory); } async render(container, data) { const header = DomHelpers.createElement("div", "leif-section-header"); @@ -3120,13 +3231,17 @@ var ItemsTab = class { actions.appendChild( DomHelpers.createIconButton("delete", "Excluir", { onClick: async () => { - if (confirm(`Excluir "${item.title}"?`)) { - try { - await this.deleteStudyItemUseCase.execute({ itemId: item.id }); - await this.onUpdate(); - } catch (error) { - this.notifyError(error, "N\xE3o foi poss\xEDvel excluir o item."); - } + const confirmed = await DomHelpers.confirm({ + title: "Excluir item", + message: `Excluir "${item.title}"?`, + confirmLabel: "Excluir" + }); + if (!confirmed) return; + try { + await this.deleteStudyItemUseCase.execute({ itemId: item.id }); + await this.onUpdate(); + } catch (error) { + this.notifyError(error, "N\xE3o foi poss\xEDvel excluir o item."); } } }) @@ -3324,10 +3439,10 @@ var import_obsidian6 = require("obsidian"); // src/application/use-cases/DeleteStudySessionUseCase.ts var DeleteStudySessionUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.sessionRepository = new EntityRepository(dataStore, "studySessions"); - this.topicRepository = new EntityRepository(dataStore, "topics"); + this.sessionRepository = repositoryFactory.for("studySessions"); + this.topicRepository = repositoryFactory.for("topics"); } async execute(input) { const validation = new DeleteStudySessionValidator().validate(input); @@ -3361,9 +3476,9 @@ var DeleteStudySessionUseCase = class { // src/application/use-cases/UpdateStudySessionUseCase.ts var UpdateStudySessionUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.sessionRepository = new EntityRepository(dataStore, "studySessions"); + this.sessionRepository = repositoryFactory.for("studySessions"); } async execute(input) { if (!input.sessionId?.trim()) { @@ -3396,11 +3511,12 @@ var SessionsTab = class { this.dataStore = dataStore; this.onUpdate = onUpdate; this.editingSessionId = null; - this.registerStudySessionUseCase = new RegisterStudySessionUseCase(dataStore); - this.deleteStudySessionUseCase = new DeleteStudySessionUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + this.registerStudySessionUseCase = new RegisterStudySessionUseCase(dataStore, repositoryFactory); + this.deleteStudySessionUseCase = new DeleteStudySessionUseCase(dataStore, repositoryFactory); this.getActiveContestSummaryUseCase = new GetActiveContestSummaryUseCase(dataStore); this.listSubjectsForActiveContestUseCase = new ListSubjectsForActiveContestUseCase(dataStore); - this.updateStudySessionUseCase = new UpdateStudySessionUseCase(dataStore); + this.updateStudySessionUseCase = new UpdateStudySessionUseCase(dataStore, repositoryFactory); this.advanceCycleUseCase = new AdvanceCycleUseCase(dataStore); this.getActiveCycleSnapshotUseCase = new GetActiveCycleSnapshotUseCase(dataStore); } @@ -3526,13 +3642,17 @@ var SessionsTab = class { DomHelpers.createIconButton("delete", "Excluir", { dataset: { sessionDeleteId: session.id }, onClick: async () => { - if (confirm("Excluir esta sess\xE3o?")) { - try { - await this.deleteStudySessionUseCase.execute({ sessionId: session.id }); - await this.onUpdate(); - } catch (error) { - this.notifyError(error, "N\xE3o foi poss\xEDvel excluir a sess\xE3o."); - } + const confirmed = await DomHelpers.confirm({ + title: "Excluir sess\xE3o", + message: "Excluir esta sess\xE3o?", + confirmLabel: "Excluir" + }); + if (!confirmed) return; + try { + await this.deleteStudySessionUseCase.execute({ sessionId: session.id }); + await this.onUpdate(); + } catch (error) { + this.notifyError(error, "N\xE3o foi poss\xEDvel excluir a sess\xE3o."); } } }) @@ -3725,22 +3845,27 @@ var import_obsidian7 = require("obsidian"); // src/application/use-cases/DeleteTopicUseCase.ts var DeleteTopicUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.topicRepository = new EntityRepository(dataStore, "topics"); + this.topicRepository = repositoryFactory.for("topics"); + this.subjectRepository = repositoryFactory.for("subjects"); } async execute(input) { const topic = await this.topicRepository.findById(input.topicId); await this.topicRepository.delete(input.topicId); + await this.subjectRepository.update(topic.subjectId, (subject) => ({ + ...subject, + topicIds: subject.topicIds.filter((id) => id !== input.topicId) + })); return topic; } }; // src/application/use-cases/UpdateTopicUseCase.ts var UpdateTopicUseCase = class { - constructor(dataStore) { + constructor(dataStore, repositoryFactory) { this.dataStore = dataStore; - this.topicRepository = new EntityRepository(dataStore, "topics"); + this.topicRepository = repositoryFactory.for("topics"); } async execute(input) { if (!input.topicId?.trim()) { @@ -3783,10 +3908,11 @@ var TopicsTab = class { this.selectedSubjectId = null; this.editingTopicId = null; this.expandedTopicId = null; - this.createTopicUseCase = new CreateTopicUseCase(dataStore); - this.deleteTopicUseCase = new DeleteTopicUseCase(dataStore); - this.linkQuestionNotebookUseCase = new LinkQuestionNotebookUseCase(dataStore); - this.updateTopicUseCase = new UpdateTopicUseCase(dataStore); + const repositoryFactory = new EntityRepositoryFactory(dataStore); + this.createTopicUseCase = new CreateTopicUseCase(dataStore, repositoryFactory); + this.deleteTopicUseCase = new DeleteTopicUseCase(dataStore, repositoryFactory); + this.linkQuestionNotebookUseCase = new LinkQuestionNotebookUseCase(dataStore, repositoryFactory); + this.updateTopicUseCase = new UpdateTopicUseCase(dataStore, repositoryFactory); } async render(container, data) { const header = DomHelpers.createElement("div", "leif-section-header"); @@ -3870,13 +3996,17 @@ var TopicsTab = class { actions.appendChild( DomHelpers.createIconButton("delete", "Excluir", { onClick: async () => { - if (confirm(`Excluir "${topic.name}"?`)) { - try { - await this.deleteTopicUseCase.execute({ topicId: topic.id }); - await this.onUpdate(); - } catch (error) { - this.notifyError(error, "N\xE3o foi poss\xEDvel excluir o assunto."); - } + const confirmed = await DomHelpers.confirm({ + title: "Excluir assunto", + message: `Excluir "${topic.name}"?`, + confirmLabel: "Excluir" + }); + if (!confirmed) return; + try { + await this.deleteTopicUseCase.execute({ topicId: topic.id }); + await this.onUpdate(); + } catch (error) { + this.notifyError(error, "N\xE3o foi poss\xEDvel excluir o assunto."); } } }) @@ -4062,7 +4192,7 @@ var WallTab = class { constructor(dataStore, onUpdate) { this.dataStore = dataStore; this.onUpdate = onUpdate; - this.updateContestWallUseCase = new UpdateContestWallUseCase(dataStore); + this.updateContestWallUseCase = new UpdateContestWallUseCase(dataStore, new EntityRepositoryFactory(dataStore)); } async render(container, data) { container.appendChild(DomHelpers.createSectionTitle("Mural")); @@ -4257,20 +4387,32 @@ var LeifView = class extends import_obsidian9.ItemView { this.headerActions = DomHelpers.createElement("div", "leif-header-actions"); header.append(titleGroup, this.headerActions); this.tabBar = DomHelpers.createElement("nav", "leif-tab-bar"); - TABS2.forEach((tab) => { + this.tabBar.setAttribute("role", "tablist"); + this.tabBar.setAttribute("aria-label", "Se\xE7\xF5es do Leif"); + TABS2.forEach((tab, index) => { const button = DomHelpers.createButton(tab.label, { dataset: { tab: tab.id }, className: "leif-tab-button", onClick: async () => { - this.activeTab = tab.id; - this.updateTabButtonStyles(); - await this.refresh(); + await this.selectTab(tab.id); } }); + button.setAttribute("role", "tab"); + button.id = `leif-tab-${tab.id}`; + button.tabIndex = tab.id === this.activeTab ? 0 : -1; + button.setAttribute("aria-selected", String(tab.id === this.activeTab)); + button.setAttribute("aria-controls", "leif-tabpanel"); + button.addEventListener("keydown", (event) => { + this.handleTabKeyDown(event, index); + }); this.tabButtons.set(tab.id, button); this.tabBar.appendChild(button); }); this.activeTabContainer = DomHelpers.createElement("section", "leif-body"); + this.activeTabContainer.id = "leif-tabpanel"; + this.activeTabContainer.setAttribute("role", "tabpanel"); + this.activeTabContainer.setAttribute("tabindex", "0"); + this.activeTabContainer.setAttribute("aria-labelledby", `leif-tab-${this.activeTab}`); this.shell.append(header, this.tabBar, this.activeTabContainer); this.contentEl.appendChild(this.shell); } @@ -4294,13 +4436,45 @@ var LeifView = class extends import_obsidian9.ItemView { this.activeTabContainer.innerHTML = ""; await this.renderActiveTab(this.activeTabContainer, data); } + /** + * Selects a tab, updating aria state and re-rendering. + */ + async selectTab(tabId) { + this.activeTab = tabId; + this.updateTabButtonStyles(); + await this.refresh(); + } + /** + * Moves focus between tabs with Arrow keys and activates on Enter/Space. + */ + handleTabKeyDown(event, index) { + const tabIds = TABS2.map((tab) => tab.id); + if (event.key === "ArrowRight") { + event.preventDefault(); + const next = tabIds[(index + 1) % tabIds.length]; + void this.selectTab(next); + this.tabButtons.get(next)?.focus(); + } else if (event.key === "ArrowLeft") { + event.preventDefault(); + const prev = tabIds[(index - 1 + tabIds.length) % tabIds.length]; + void this.selectTab(prev); + this.tabButtons.get(prev)?.focus(); + } else if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + void this.selectTab(tabIds[index]); + } + } /** * Updates the active class on tab buttons. */ updateTabButtonStyles() { this.tabButtons.forEach((button, tabId) => { - button.className = this.activeTab === tabId ? "leif-tab-button is-active" : "leif-tab-button"; + const isActive = this.activeTab === tabId; + button.className = isActive ? "leif-tab-button is-active" : "leif-tab-button"; + button.tabIndex = isActive ? 0 : -1; + button.setAttribute("aria-selected", String(isActive)); }); + this.activeTabContainer?.setAttribute("aria-labelledby", `leif-tab-${this.activeTab}`); } async renderActiveTab(container, data) { switch (this.activeTab) { diff --git a/sample-vault/.obsidian/plugins/leif/styles.css b/sample-vault/.obsidian/plugins/leif/styles.css index 0287a45..9f81145 100644 --- a/sample-vault/.obsidian/plugins/leif/styles.css +++ b/sample-vault/.obsidian/plugins/leif/styles.css @@ -98,6 +98,17 @@ white-space: nowrap; } +.leif-tab-button:focus-visible, +.leif-button:focus-visible, +.leif-primary-button:focus-visible, +.leif-icon-button:focus-visible, +.leif-input:focus-visible, +.leif-select:focus-visible, +.leif-textarea:focus-visible { + outline: 2px solid var(--text-accent, #7c3aed); + outline-offset: 2px; +} + .leif-icon-button { display: inline-flex; align-items: center; @@ -714,3 +725,17 @@ grid-template-columns: 1fr; } } + +.leif-modal-message { + margin: 0; + color: var(--text-normal); + line-height: 1.5; +} + +.leif-modal-card:focus-visible { + outline: none; +} + +.leif-modal-card [role="dialog"]:focus-visible { + outline: none; +} diff --git a/tsconfig.json b/tsconfig.json index 8003810..b04b27d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,8 @@ "lib": ["ES2020", "DOM"], "strict": true, "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true,