From 0ef83b09fcd38f01bca1e939bd7078d1844ae4b5 Mon Sep 17 00:00:00 2001 From: sumbad Date: Tue, 20 Oct 2026 23:25:26 +0800 Subject: [PATCH 1/8] fix: clear community-review warnings for 0.11.0 (eslint strictness, deprecated APIs, CSS lint) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prefer-create-el: Obsidian helpers in noteProperty/NotepadView, narrow DetachedDoc helper in export/postprocess, harness uses its own shim (7 shim implementation sites remain as the documented exception) - TFile casts -> instanceof guards; unsafe-* -> typed guards + local NodeFs interface in noteStorage/umVault (no typeof import node:fs) - no-explicit-any (texto/core covariance) and empty Commands interfaces (declaration merging) suppressed pointwise with descriptions; eslint config aligned with review (empty interfaces no longer allowed) - deprecated APIs: messageEl fallback (Notice), caretPositionFromPoint with caretRangeFromPoint fallback, declarative getSettingDefinitions (display() kept as pre-1.13 fallback), window.requestAnimationFrame, window.crypto instead of globalThis; writeAssetDescriptors removed - CSS: all 24 !important refactored to specificity/order/exclusions (table borders inline styles now plain, drag-handle reveal rules carry :not(.is-grabbed)/:not(.is-dragging)), column-gap -> gap, standard scrollbar props dropped in favor of ::-webkit-scrollbar, intentional text-decoration/display:contents closed with described stylelint disables, note.css regenerated by scripts/generate-note-css.mjs with duplicate-property dedup - tooling: stylelint + lint:css gate (browserslist Chrome 124 = Obsidian 1.6.x), lint:src gate with --max-warnings 0 Gates: lint:src 0, lint:css 0, typecheck, 629 tests, build — pass --- eslint.config.mts | 24 +- package-lock.json | 1473 ++++++++++++++++- package.json | 4 + scripts/generate-note-css.mjs | 128 ++ src/NotepadView.ts | 21 +- src/browser-harness/harness.ts | 8 +- src/export/assets/README.md | 38 +- src/export/assets/note.css | 13 +- src/export/postprocess.ts | 37 +- src/export/preview.ts | 6 +- src/main.ts | 49 +- src/storage/noteStorage.ts | 12 +- src/storage/um/umContainer.ts | 11 - src/storage/um/umIds.ts | 4 +- src/storage/um/umNotepad.ts | 2 +- src/storage/um/umVault.ts | 12 +- src/styles/drag-handle.css | 18 +- src/styles/editor.css | 3 + src/styles/heading-folding.css | 28 +- src/styles/mobile.css | 7 +- src/styles/texto-editor.css | 13 +- src/styles/typography-v2.css | 5 + src/texto/core/@types/AnyConfig.ts | 3 + src/texto/core/@types/index.ts | 6 + src/texto/core/Extension.ts | 2 + src/texto/core/Mark.ts | 5 + src/texto/core/Node.ts | 6 + src/texto/core/extensions/commands.ts | 1 + .../helpers/setAttachmentViewProps.ts | 7 +- src/texto/extensions/attachment/index.ts | 1 + src/texto/extensions/blockquote/index.ts | 1 + src/texto/extensions/bold/index.ts | 1 + src/texto/extensions/bullet-list/index.ts | 1 + src/texto/extensions/code-block-hljs/index.ts | 1 + .../extensions/code-block-hljs/style.css | 1 - src/texto/extensions/code/index.ts | 1 + src/texto/extensions/color/index.ts | 1 + src/texto/extensions/hard-break/index.ts | 1 + src/texto/extensions/highlight/index.ts | 1 + src/texto/extensions/history/index.ts | 1 + src/texto/extensions/horizontal-rule/index.ts | 1 + src/texto/extensions/image/index.ts | 1 + src/texto/extensions/image/style.css | 7 +- src/texto/extensions/italic/index.ts | 1 + src/texto/extensions/list-keymap/index.ts | 1 + src/texto/extensions/mermaid/index.ts | 1 + src/texto/extensions/mermaid/mermaid.ts | 11 +- src/texto/extensions/mermaid/style.css | 2 +- .../mermaid/view/mermaid.element.tsx | 1 + .../note-property/noteProperty.plugin.ts | 68 +- src/texto/extensions/note-property/style.css | 5 +- src/texto/extensions/ordered-list/index.ts | 1 + src/texto/extensions/paragraph/index.ts | 1 + src/texto/extensions/strike/index.ts | 1 + .../extensions/table/bordersCommands.test.ts | 8 +- src/texto/extensions/table/helpers/borders.ts | 9 +- .../table/helpers/safeStyle.test.ts | 6 +- src/texto/extensions/table/index.ts | 1 + src/texto/extensions/table/style.css | 36 +- .../extensions/task-item-folding/index.ts | 1 + src/texto/extensions/task-item/style.css | 19 +- src/texto/extensions/task-list/index.ts | 1 + src/texto/extensions/text-style/index.ts | 1 + src/texto/extensions/underline/index.ts | 1 + stylelint.config.mjs | 24 + 65 files changed, 1978 insertions(+), 188 deletions(-) create mode 100644 scripts/generate-note-css.mjs create mode 100644 stylelint.config.mjs diff --git a/eslint.config.mts b/eslint.config.mts index a02618e..f5105ed 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -36,7 +36,11 @@ export default defineConfig( }, parserOptions: { projectService: { - allowDefaultProject: ["eslint.config.mts", "playwright.config.ts"], + allowDefaultProject: [ + "eslint.config.mts", + "stylelint.config.mjs", + "playwright.config.ts", + ], }, tsconfigRootDir: import.meta.dirname, }, @@ -84,19 +88,11 @@ export default defineConfig( "@typescript-eslint/no-misused-promises": "warn", "@typescript-eslint/no-floating-promises": "warn", "@typescript-eslint/no-unnecessary-type-assertion": "warn", - "@typescript-eslint/no-empty-object-type": [ - "warn", - // Extension command types use `declare global { interface Commands extends … {} }` - // as declaration-merging augmentation points — these must stay interfaces. - { allowInterfaces: "with-single-extends" }, - ], - "@typescript-eslint/no-empty-interface": [ - "warn", - // Same declaration-merging augmentation points (one `declare global - // { interface Commands extends … {} }` per extension); a type alias - // cannot merge into the global interface, so keep them. - { allowSingleExtends: true }, - ], + // No option overrides: the review bot sees empty interfaces (the + // `declare global { interface Commands extends … {} }` merging points + // cannot be type aliases), so the local gate must see them too. + "@typescript-eslint/no-empty-object-type": "warn", + "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-redundant-type-constituents": "warn", "@typescript-eslint/no-this-alias": "warn", "@typescript-eslint/no-unsafe-function-type": "warn", diff --git a/package-lock.json b/package-lock.json index 3e216ed..5d04737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,8 @@ "lightningcss": "1.33.0", "obsidian": "1.13.1", "playwright-core": "1.63.0", + "stylelint": "^16.26.1", + "stylelint-no-unsupported-browser-features": "^8.1.2", "typescript": "5.9.3", "typescript-eslint": "8.66.0", "vite": "8.2.0", @@ -787,6 +789,67 @@ "specificity": "bin/cli.js" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@chevrotain/types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", @@ -958,6 +1021,40 @@ "node": ">=20.19.0" } }, + "node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/JounQin" + } + }, "node_modules/@emnapi/core": { "version": "2.0.0-alpha.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", @@ -1356,6 +1453,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", @@ -1433,6 +1537,44 @@ "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, + "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/@oxc-project/types": { "version": "0.142.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", @@ -2105,6 +2247,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "26.1.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", @@ -2613,6 +2762,16 @@ "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", @@ -2653,6 +2812,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/array-includes": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", @@ -2676,6 +2845,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -2796,6 +2975,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "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", @@ -2806,6 +2995,16 @@ "node": ">=12" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -2889,6 +3088,19 @@ "concat-map": "0.0.1" } }, + "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/browserslist": { "version": "4.28.7", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", @@ -2923,6 +3135,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -3031,6 +3267,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3051,6 +3302,13 @@ "dev": true, "license": "MIT" }, + "node_modules/colord": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz", + "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -3086,6 +3344,13 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -3095,6 +3360,33 @@ "layout-base": "^1.0.0" } }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/crelt": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", @@ -3118,6 +3410,27 @@ "node": ">= 8" } }, + "node_modules/css-functions-list": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/css-tokenize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-tokenize/-/css-tokenize-1.0.1.tgz", + "integrity": "sha512-gLmmbJdwH9HLY4bcA17lnZ8GgPwEXRbvxBJGHnkiB6gLhRpTzjkjtMIvz7YORGW/Ptv2oMk8b5g+u7mRD6Dd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^1.0.33" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -3132,6 +3445,19 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cytoscape": { "version": "3.34.3", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.3.tgz", @@ -3807,6 +4133,19 @@ "node": ">=8" } }, + "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": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3820,6 +4159,29 @@ "node": ">=0.10.0" } }, + "node_modules/doiuse": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/doiuse/-/doiuse-6.0.6.tgz", + "integrity": "sha512-XuPRslcWHhQJ+WjCjimRUcNfhZvOiC0610FsY6WeSlzXvoZYtm6iOpR9K0N4wRoM/lP4i7LatT+IhltAzouSOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "css-tokenize": "^1.0.1", + "duplexify": "^4.1.3", + "multimatch": "^5.0.0", + "postcss": "^8.5.6", + "source-map": "^0.7.6", + "yargs": "^17.7.2" + }, + "bin": { + "doiuse": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/dompurify": { "version": "3.4.16", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", @@ -3844,6 +4206,44 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.399", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", @@ -3851,6 +4251,13 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/empathic": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", @@ -3861,6 +4268,16 @@ "node": ">=14" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.24.5", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", @@ -3888,18 +4305,38 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/es-abstract": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", @@ -4804,6 +5241,36 @@ "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", @@ -4844,6 +5311,26 @@ "strictdom": "^1.0.1" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "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", @@ -4881,6 +5368,19 @@ "node": ">=16.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", @@ -5014,6 +5514,16 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -5097,6 +5607,47 @@ "node": ">=10.13.0" } }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "17.9.0", "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", @@ -5127,6 +5678,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -5234,6 +5813,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -5256,6 +5848,13 @@ "node": ">=12.0.0" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -5269,6 +5868,19 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5328,6 +5940,20 @@ "node": ">=0.8.19" } }, + "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/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -5370,6 +5996,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -5529,6 +6162,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -5588,6 +6231,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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-number-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", @@ -5605,6 +6258,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.1.0.tgz", + "integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "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", @@ -5900,6 +6563,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-migrate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", @@ -6080,6 +6750,23 @@ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -6373,6 +7060,13 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/linkifyjs": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", @@ -6417,6 +7111,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6472,6 +7173,17 @@ "node": ">= 0.4" } }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -6479,6 +7191,29 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/mermaid": { "version": "11.17.2", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz", @@ -6509,6 +7244,33 @@ "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, + "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": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -6556,6 +7318,26 @@ "dev": true, "license": "MIT" }, + "node_modules/multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -6611,6 +7393,16 @@ "node": ">=18" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6763,6 +7555,16 @@ "node": ">=12.20.0" } }, + "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", @@ -6857,6 +7659,25 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -6903,10 +7724,20 @@ "dev": true, "license": "MIT" }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "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", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -7014,6 +7845,61 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.7.tgz", + "integrity": "sha512-lFcugRCQXWManq7nyZ0dDtir52wc7gKYJfWpSuz4GxvnGc7dtlpXcZYX4A0asatzTuqpQ3ngDdyD+QG/szeXWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.1.0.tgz", + "integrity": "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.7.tgz", + "integrity": "sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7175,6 +8061,47 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, + "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/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7182,6 +8109,26 @@ "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -7236,6 +8183,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -7300,6 +8257,17 @@ "node": ">=0.12" } }, + "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/robust-predicates": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", @@ -7358,6 +8326,30 @@ "points-on-path": "^0.2.1" } }, + "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/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -7634,6 +8626,57 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map-js": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", @@ -7672,12 +8715,41 @@ "node": ">= 0.4" } }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/strictdom": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==", "license": "MIT" }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -7777,6 +8849,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -7808,6 +8893,204 @@ "license": "MIT", "peer": true }, + "node_modules/stylelint": { + "version": "16.26.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz", + "integrity": "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-syntax-patches-for-csstree": "^1.0.19", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.5", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-no-unsupported-browser-features": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/stylelint-no-unsupported-browser-features/-/stylelint-no-unsupported-browser-features-8.1.2.tgz", + "integrity": "sha512-31569cdF9kDuccpnGk6DPuE9pB2ogrDxL3g0xDEVZ4pw9sq6AoZQMtaG+FOTmpwkNa+ilCwDnAQ1GlAX80p9wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.26.3", + "doiuse": "^6.0.6", + "postcss": "^8.4.32" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": ">=16.0.2" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.13.tgz", + "integrity": "sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/stylis": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", @@ -7827,6 +9110,23 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -7840,6 +9140,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -7864,6 +9170,47 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/tagged-tag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", @@ -7962,6 +9309,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/toml-eslint-parser": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz", @@ -8306,6 +9666,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", @@ -8673,6 +10040,45 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -8690,6 +10096,16 @@ "dev": true, "license": "MIT" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -8743,6 +10159,35 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 5941175..0ad46b3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "test:e2e:ui": "npm run build:harness && playwright test --ui", "lint": "eslint .", "lint:fix": "eslint . --fix", + "lint:src": "eslint src --ignore-pattern '**/*.test.ts' --ignore-pattern 'src/browser-harness/**' --max-warnings 0", + "lint:css": "stylelint \"src/**/*.css\" --max-warnings 0", "format": "biome format --write .", "format:check": "biome format ." }, @@ -71,6 +73,8 @@ "lightningcss": "1.33.0", "obsidian": "1.13.1", "playwright-core": "1.63.0", + "stylelint": "^16.26.1", + "stylelint-no-unsupported-browser-features": "^8.1.2", "typescript": "5.9.3", "typescript-eslint": "8.66.0", "vite": "8.2.0", diff --git a/scripts/generate-note-css.mjs b/scripts/generate-note-css.mjs new file mode 100644 index 0000000..b30462c --- /dev/null +++ b/scripts/generate-note-css.mjs @@ -0,0 +1,128 @@ +#!/usr/bin/env node +/** Regenerates `src/export/assets/note.css` — the self-contained stylesheet + * written next to index.html by "Export as website". + * + * Composition (see src/export/assets/README.md): + * 1. header comment + stylelint gate note (this script); + * 2. @font-face rules with the fonts inlined as data URIs (reused from + * the current file — re-derive them from the blog assets when the + * fonts change, see README); + * 3. the deployed inscriptum stylesheet (part 2), fetched from + * https://inscriptum.js.org/css/note.css (or `--from `), with + * duplicate property declarations removed (keep-last — cascade + * equivalent, satisfies the stylelint duplicate-properties gate); + * 4. the hand-maintained "Export additions" section, preserved verbatim + * from the current file. + * + * The browser-feature warnings (text-decoration / display:contents / + * multicolumn) are intentionally kept: they are load-bearing styles of the + * published site, which targets more browsers than Obsidian's Chromium. + * The generated header carries a file-level stylelint-disable with this + * justification, so `npm run lint:css` stays clean without touching the + * upstream source. + * + * Usage: + * node scripts/generate-note-css.mjs # fetch deployed CSS + * node scripts/generate-note-css.mjs --from old.css # use a local copy + */ +import { readFile, writeFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +const DEPLOYED_URL = "https://inscriptum.js.org/css/note.css"; +const TARGET = fileURLToPath( + new URL("../src/export/assets/note.css", import.meta.url), +); + +function argValue(flag) { + const i = process.argv.indexOf(flag); + return i >= 0 ? process.argv[i + 1] : undefined; +} + +async function fetchDeployed() { + const from = argValue("--from"); + if (from) return readFile(from, "utf8"); + const res = await fetch(DEPLOYED_URL); + if (!res.ok) { + throw new Error( + `${DEPLOYED_URL} responded ${res.status} — pass --from with a locally saved copy`, + ); + } + return res.text(); +} + +/** Splits the current generated file into its parts, by line shape: the + * fonts line starts with "@font-face", the deployed blob is the single + * line starting with "/*! normalize.css", everything after it is the + * hand-maintained additions. */ +function extractParts(current) { + const lines = current.split("\n"); + const fontsIdx = lines.findIndex((l) => l.startsWith("@font-face")); + const deployedIdx = lines.findIndex((l) => l.startsWith("/*! normalize.css")); + if (fontsIdx < 0 || deployedIdx < 0 || deployedIdx < fontsIdx) { + throw new Error( + "cannot find the @font-face / deployed-css lines in the current note.css", + ); + } + return { + fonts: lines[fontsIdx], + additions: lines.slice(deployedIdx + 1).join("\n").replace(/^\n+/, ""), + }; +} + +/** Removes duplicate property declarations inside each block, keeping the + * LAST occurrence (cascade-equivalent: the last declaration wins), and + * drops legacy IE star-hack declarations (*prop — invalid in every modern + * browser and flagged as duplicates by the stylelint gate). Custom + * properties participate too — a repeat in the same block is always + * overridden by the later one. Blocks whose text contains quotes with + * semicolons are skipped (conservative guard). */ +function dedupProperties(css) { + let removed = 0; + const out = css.replace(/\{[^{}]*\}/g, (block) => { + if (/("[^"]*;[^"]*")|('[^']*;[^']*')/.test(block)) return block; + const inner = block.slice(1, -1); + const decls = inner.split(";"); + const lastIndex = new Map(); + decls.forEach((d, i) => { + const prop = d.slice(0, d.indexOf(":")).trim().toLowerCase(); + if (prop) lastIndex.set(prop, i); + }); + const kept = decls.filter((d, i) => { + const colon = d.indexOf(":"); + if (colon < 0) return d.trim() !== ""; + const prop = d.slice(0, colon).trim().toLowerCase(); + if (prop.startsWith("*")) { + removed += 1; + return false; + } + const isLast = lastIndex.get(prop) === i; + if (!isLast) removed += 1; + return isLast; + }); + return `{${kept.join(";")}}`; + }); + return { out, removed }; +} + +const [current, deployed] = await Promise.all([ + readFile(TARGET, "utf8"), + fetchDeployed(), +]); +const { fonts, additions } = extractParts(current); +const { out: deduped, removed } = dedupProperties(deployed.replace(/\s+$/, "")); + +const header = `/* inscriptum export stylesheet — GENERATED by scripts/generate-note-css.mjs; do not edit. + * Self-contained: fonts are embedded as data URIs (reused from the previous + * build; see README for the font re-inline procedure). Part 2 is the deployed + * inscriptum note.css with duplicate declarations removed (keep-last). + * Regenerate: node scripts/generate-note-css.mjs */ +/* stylelint-disable plugin/no-unsupported-browser-features -- the deployed site stylesheet intentionally targets more browsers than Obsidian's Chromium: text-decoration styling, display:contents and multicolumn are load-bearing design there, and the exported pages are viewed outside Obsidian. */ + +`; + +const result = `${header}${fonts}\n${deduped}\n\n${additions}`; +await writeFile(TARGET, result); +console.log( + `note.css regenerated: ${removed} duplicate declaration(s) removed, ` + + `${deduped.length} chars of deployed css`, +); diff --git a/src/NotepadView.ts b/src/NotepadView.ts index 781a431..6b4faf0 100644 --- a/src/NotepadView.ts +++ b/src/NotepadView.ts @@ -109,7 +109,7 @@ function createCustomElement(baseTag: string, make: () => T): T { try { return make(); } catch { - return document.createElement(elTag(baseTag)) as unknown as T; + return createEl(elTag(baseTag) as keyof HTMLElementTagNameMap) as unknown as T; } } @@ -838,7 +838,7 @@ export class NotepadView extends FileView { } else { title.setText("This page was created in a newer version"); text.setText( - "Update Inscriptum to open and edit it. The content is kept unchanged in the file.", + "Update inscriptum to open and edit it. The content is kept unchanged in the file.", ); } } @@ -1387,9 +1387,12 @@ export class NotepadView extends FileView { } const editorRef = handle.editorRef; + // Image/attachment tools save relative to the open note; without a bound + // file there is nothing to bind the context to. + if (!(this.file instanceof TFile)) return null; const ctx: ImageToolContext = { app: this.app, - noteFile: this.file as TFile, + noteFile: this.file, }; const isMobile = Platform.isMobile; // The schema family picks the editor profile (spec 8.6): title pages @@ -1592,7 +1595,11 @@ export class NotepadView extends FileView { }, attachment: { onFileSelected: (file, update, cancel) => { - void attachSelectedFile(app, this.file as TFile, file, update, cancel); + if (!(this.file instanceof TFile)) { + cancel(); + return; + } + void attachSelectedFile(app, this.file, file, update, cancel); }, onClick: (attrs) => { openAttachmentFile(app, attrs.data); @@ -3039,7 +3046,11 @@ export class NotepadView extends FileView { withAction.addAction("undo", title, cb); return; } - const btn = notice.noticeEl.createEl("button", { + // `messageEl` arrived in Obsidian 1.8.7; runtimes older than that + // (minAppVersion is 1.6.6) only carry the deprecated `noticeEl`. The + // local type reads both without referencing the deprecated declaration. + const container = notice as { messageEl?: HTMLElement; noticeEl: HTMLElement }; + const btn = (container.messageEl ?? container.noticeEl).createEl("button", { text: title, cls: "notepad-notice-action", }); diff --git a/src/browser-harness/harness.ts b/src/browser-harness/harness.ts index 6e4f1bd..a737246 100644 --- a/src/browser-harness/harness.ts +++ b/src/browser-harness/harness.ts @@ -226,7 +226,7 @@ function appContainer(): HTMLElement { if (existing != null) { return existing; } - const app = document.createElement("div"); + const app = createDiv(); app.id = "app"; document.body.appendChild(app); return app; @@ -241,10 +241,8 @@ function mountEditor(doc: unknown): string { const app = appContainer(); app.replaceChildren(); - const note = document.createElement("div"); - note.className = "note"; - const article = document.createElement("article"); - article.className = "texto-editor"; + const note = createDiv("note"); + const article = createEl("article", { cls: "texto-editor" }); note.appendChild(article); app.appendChild(note); diff --git a/src/export/assets/README.md b/src/export/assets/README.md index e7c67c7..3c30cd3 100644 --- a/src/export/assets/README.md +++ b/src/export/assets/README.md @@ -7,20 +7,28 @@ Composition: -1. `@font-face` rules (copied from the inscriptum notepad's injected style, - URLs replaced with data URIs). -2. The deployed inscriptum article stylesheet (`/css/note.css`) — normalize, - typography variables, `article`-scoped content styles, night-owl code - highlight theme, task list / table / figure styles. +1. Generated-file header with the stylelint gate note (why the deployed + part's intentional browser-feature usages are exempt). +2. `@font-face` rules with the fonts inlined as data URIs. +3. The deployed inscriptum stylesheet (`https://inscriptum.js.org/css/note.css`) + with duplicate property declarations removed (keep-last — cascade + equivalent) and legacy IE star-hacks dropped; this satisfies the + project's `npm run lint:css` gate for the generated file. +4. The hand-maintained `Export additions` section (float clearing, image + layouts, the code block chrome, the export-only table rules) — preserved + verbatim from the previous build. -To regenerate (e.g. after the source design changes): +## Regenerating -1. Download the current deployed stylesheet: - `curl -o /tmp/note.css https://inscriptum.js.org/css/note.css` -2. Re-run the generator snippet from the repository history - (scripts/…): it extracts the `@font-face` rules from any exported note in - `blog/src/public/note/*.html`, inlines the four font files from - `blog/src/public/fonts/` as data URIs and concatenates the deployed CSS. -3. Re-apply the hand-maintained `Export additions` section at the bottom of - the file (float clearing, image layouts and the code block chrome) — the - generator only produces parts 1–2. +```sh +node scripts/generate-note-css.mjs # fetch the deployed CSS +node scripts/generate-note-css.mjs --from old.css # offline: use a local copy +``` + +The script reuses the embedded `@font-face` rules and the `Export additions` +section from the current file. When the source design (blog) changes, +re-fetch; when the fonts change, re-derive the `@font-face` block manually: +extract the `@font-face` rules from any exported note in +`blog/src/public/note/*.html`, inline the four font files from +`blog/src/public/fonts/` as data URIs, and replace part 2 in `note.css` +before regenerating. diff --git a/src/export/assets/note.css b/src/export/assets/note.css index 074e9c5..0b39c95 100644 --- a/src/export/assets/note.css +++ b/src/export/assets/note.css @@ -1,11 +1,12 @@ -/* inscriptum export stylesheet. - * Self-contained: fonts are embedded as data URIs. - * Article styles follow the deployed inscriptum note.css so exported - * pages look the same when hosted anywhere. Regenerate with the script - * in src/export/assets/README.md when the source design changes. */ +/* inscriptum export stylesheet — GENERATED by scripts/generate-note-css.mjs; do not edit. + * Self-contained: fonts are embedded as data URIs (reused from the previous + * build; see README for the font re-inline procedure). Part 2 is the deployed + * inscriptum note.css with duplicate declarations removed (keep-last). + * Regenerate: node scripts/generate-note-css.mjs */ +/* stylelint-disable plugin/no-unsupported-browser-features -- the deployed site stylesheet intentionally targets more browsers than Obsidian's Chromium: text-decoration styling, display:contents and multicolumn are load-bearing design there, and the exported pages are viewed outside Obsidian. */ @font-face{font-family:"Inter";font-style:normal;font-weight:100 900;font-display:swap;src:url(data:font/woff2;base64,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) format("woff2");unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Inter";font-style:normal;font-weight:100 900;font-display:swap;src:url(data:font/woff2;base64,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) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"JetBrainsMono";font-style:normal;font-weight:400;font-display:swap;src:url(data:font/woff;base64,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) format("woff");unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"JetBrainsMono";font-style:normal;font-weight:400;font-display:swap;src:url(data:font/woff;base64,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) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:rgba(0,0,0,0)}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--light-grey: #e1e1e1;--dark-grey: #333;--primary-color: #33c3f0;--secondary-color: #555555;--border-color: #bbb;--link-color: #1eaedb;--font-color: #222;--color-contrast-low: hsl(240, 1%, 83%);--color-contrast-medium: hsl(240, 1%, 48%);--color-contrast-high: hsl(240, 4%, 20%);--font-family: Inter, sans-serif;--ipad-min-width: 1366;--container-width: 960px}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:"Inter",sans-serif;color:#222}a{color:#1eaedb}a:hover{color:#1b9cc5}hr{margin-top:3rem;margin-bottom:3.5rem;border-width:0;border-top:1px solid #e1e1e1}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:2rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-0.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-0.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-0.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-0.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-0.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media(min-width: 550px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}p{margin-top:0}.navbar{display:none}@media(min-width: 750px){.navbar+.docs-section{border-top-width:0}.navbar,.navbar-spacer{display:block;width:100%;height:6.5rem;background:#fff;z-index:99;border-top:1px solid #eee;border-bottom:1px solid #eee}.navbar-spacer{display:none}.navbar>.container{width:100%}.navbar-list{list-style:none;margin-bottom:0;padding-left:0;margin-top:0}.navbar-item{position:relative;float:left;margin-bottom:0}.navbar-link{text-transform:uppercase;font-size:11px;font-weight:600;letter-spacing:.2rem;margin-right:35px;text-decoration:none;line-height:6.5rem;color:#222}.navbar-link.active{color:#33c3f0}.has-docked-nav .navbar{position:fixed;top:0;left:0}.has-docked-nav .navbar-spacer{display:block}.has-docked-nav .navbar>.container{width:80%}}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#011627;color:#d6deeb}.hljs-keyword{color:#c792ea;font-style:italic}.hljs-built_in{color:#addb67;font-style:italic}.hljs-type{color:#82aaff}.hljs-literal{color:#ff5874}.hljs-number{color:#f78c6c}.hljs-regexp{color:#5ca7e4}.hljs-string{color:#ecc48d}.hljs-subst{color:#d3423e}.hljs-symbol{color:#82aaff}.hljs-class{color:#ffcb8b}.hljs-function{color:#82aaff}.hljs-title{color:#dcdcaa;font-style:italic}.hljs-params{color:#7fdbca}.hljs-comment{color:#637777;font-style:italic}.hljs-doctag{color:#7fdbca}.hljs-meta,.hljs-meta .hljs-keyword{color:#82aaff}.hljs-meta .hljs-string{color:#ecc48d}.hljs-section{color:#82b1ff}.hljs-attr,.hljs-name,.hljs-tag{color:#7fdbca}.hljs-attribute{color:#80cbc4}.hljs-variable{color:#addb67}.hljs-bullet{color:#d9f5dd}.hljs-code{color:#80cbc4}.hljs-emphasis{color:#c792ea;font-style:italic}.hljs-strong{color:#addb67;font-weight:700}.hljs-formula{color:#c792ea}.hljs-link{color:#ff869a}.hljs-quote{color:#697098;font-style:italic}.hljs-selector-tag{color:#ff6363}.hljs-selector-id{color:#fad430}.hljs-selector-class{color:#addb67;font-style:italic}.hljs-selector-attr,.hljs-selector-pseudo{color:#c792ea;font-style:italic}.hljs-template-tag{color:#c792ea}.hljs-template-variable{color:#addb67}.hljs-addition{color:#addb67;font-style:italic}.hljs-deletion{color:rgba(239,83,80,.5647058824);font-style:italic}article{max-width:732px;margin:4rem auto;font-family:Georgia,Times,Times New Roman,serif;font-weight:400;font-style:normal;font-size:18px;line-height:30px;color:rgba(12,10,10,.8)}article ::selection,article :not(pre) ::selection{background:rgba(95,95,95,.1843137255)}article section{margin:3rem 0}article section p:empty::after,article section .l:empty::after{content:" "}article h1,article h2,article h3,article h4{font-family:Inter,sans-serif;font-weight:600;font-style:normal;letter-spacing:initial}article h1{font-size:32px;margin:21px 21px 12px;line-height:34px}article h2{font-size:30px;margin:21px 21px 12px;line-height:34px}article h3{font-size:28px;margin:18px 21px 9px}article h4{font-size:24px;margin:18px 21px 7px}article p{margin:0 21px 12px;word-wrap:break-word;white-space:pre-wrap}article blockquote{margin:18px 21px 16px 0;padding-left:15px;position:relative;font-style:italic;word-wrap:break-word;border-left:3px solid #000}article aside{font-size:21px;color:rgba(0,0,0,.6);margin:18px 21px 16px;padding:0 18px;text-align:center;font-style:italic}article hr{width:50%;margin:30px auto;border:none;font-size:2px;text-align:right;overflow:visible}article hr:after{content:"";display:block;border-top:1px solid #c9cdd1;margin:0 3px;position:relative;top:1px}article [contenteditable=false]{white-space:normal}article ul[data-type=taskList]{list-style:none;padding:0;margin:0 21px 12px}article ul[data-type=taskList] li{display:block}article ul[data-type=taskList] li>label{float:left;margin-right:.8rem}article ul[data-type=taskList] li>div{display:contents}article ul[data-type=taskList] li p{margin:0}article mark{background-color:rgba(250,245,148,.7058823529)}article :not(pre)>code{background-color:rgba(97,97,97,.1);color:#616161;font-family:JetBrainsMono,monospace;font-variant-ligatures:normal;padding:.16rem .48rem;border-radius:.4rem;font-size:1.44rem}article a[href]{text-decoration:none;transition:all .2s ease-in-out;color:rgba(0,0,0,.8);border-bottom:.1rem rgba(0,0,0,.425) solid}article a[href]:hover,article a[href]:focus{border-bottom:.1rem rgba(0,0,0,.8) solid}article *[data-placeholder].empty{position:relative}article *[data-placeholder].empty:before{position:absolute;left:0;right:0;content:attr(data-placeholder);color:rgba(0,0,0,.44);font-weight:normal;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;pointer-events:none}article *[data-label]{position:relative}article *[data-label]:after{position:absolute;content:attr(data-label);color:rgba(0,0,0,.44);border-right:1px solid rgba(0,0,0,.15);right:100%;top:-6px;bottom:-6px;padding:6px 12px;font-weight:normal;margin:0 21px;opacity:0;visibility:hidden;transition:opacity .15s ease}article .focused *[data-label].empty:after{position:absolute;content:attr(data-label);color:rgba(0,0,0,.44);border-right:1px solid rgba(0,0,0,.15);right:100%;top:-6px;bottom:-6px;padding:6px 12px;font-weight:normal;margin:0 21px;opacity:0;visibility:hidden;transition:opacity .15s ease}article *[data-label].focused:after{opacity:1;visibility:visible}article *[role=definition]{font-family:Inter,sans-serif;font-size:15px;line-height:18px;color:#79828b;margin:12px 21px;font-style:normal}article *[role=definition] a{color:#79828b}article *[role=definition] a[href]{border-bottom:none;border-bottom:.1em solid rgba(0,0,0,.4)}article *[role=definition].empty{display:block}article *[role=definition] time:before{content:"•";padding:0 7px}article img{max-width:100%;vertical-align:top}article .iframe_wrap{max-width:100%;vertical-align:top;max-width:100%;vertical-align:top;position:relative}article .iframe_wrap iframe{position:absolute;width:1px;height:1px;min-width:100%;min-height:100%;*width:100%;*height:100%;top:0;left:0}article figure{display:flex;flex-direction:column;margin:0 21px 16px;padding:0;text-align:center;position:relative;user-select:inherit}article figure.ProseMirror-selectednode img{box-shadow:0 0 0 3px rgba(0,0,0,.4)}article figure.ProseMirror-selectednode img:hover{box-shadow:0 0 0 3px rgba(0,0,0,.4)}article figure img:hover{box-shadow:0 0 0 3px rgba(0,0,0,.2)}article figcaption{font-family:Inter,sans-serif;font-size:15px;color:#79828b;padding:12px 21px 0;line-height:18px;vertical-align:top;text-align:center}article .figure_wrapper{position:relative;display:contents}article .figure_wrapper img{opacity:1;transition:opacity .15s ease;max-height:690px;object-fit:contain;image-orientation:none;width:100%}article .figure_wrapper.loading img{opacity:.3}article .figure_wrapper .file_progress{position:absolute;z-index:1;bottom:0;left:0;right:0;padding:3px;background-color:rgba(255,255,255,.7);transition:opacity .15s ease;visibility:hidden;opacity:0}article .figure_wrapper.loading .file_progress{visibility:visible;opacity:1}article .figure_wrapper .file_progress_bar{height:2px;background-color:#333;transition:width .1s linear;width:0}article figcaption[data-placeholder].empty{position:relative;overflow:hidden;width:calc(100% - 170px)}article figcaption[data-placeholder].empty:before{display:none}article figcaption[data-placeholder].empty:after{position:absolute;content:attr(data-placeholder);color:rgba(0,0,0,.44);font-weight:normal;font-style:italic;top:0px;padding-top:12px}article pre{background:#232b2b;border-radius:.3rem;box-shadow:0 .5rem 1rem rgba(0,0,0,.4);border-left:6px solid #516363;margin:1.6rem auto;white-space:pre-wrap;color:#c7d3de;counter-reset:step;counter-increment:step 0;position:relative;font-family:JetBrainsMono,monospace;font-variant-ligatures:normal;font-size:1.44rem;padding:1.2rem 1.6rem}article pre code{font-family:JetBrainsMono,monospace;font-variant-ligatures:normal;font-size:1.44rem;line-height:2.4rem;white-space:pre;margin-left:4rem;display:block;overflow-x:auto;scrollbar-color:#516363}article pre code::-webkit-scrollbar{cursor:pointer;height:.6rem}article pre code::-webkit-scrollbar-thumb{cursor:pointer;background:#516363;border-radius:.5rem}article pre code::-webkit-scrollbar-thumb:hover{background:#465555}article pre ::selection{background:rgba(219,219,219,.1764705882)}article pre div.l:before{color:#516363;position:absolute;left:15px;content:counter(step);counter-increment:step}article table{font-family:Georgia,Times,Times New Roman,serif;font-weight:400;font-style:normal;font-size:18px;line-height:30px;color:rgba(12,10,10,.8);border-collapse:collapse;margin:0;overflow:hidden;table-layout:fixed;width:100%;margin:1.6rem 0}article table td,article table th{border:2px solid #ced4da;box-sizing:border-box;min-width:1.6rem;padding:3px 5px;position:relative;vertical-align:top}article table td>*,article table th>*{margin-bottom:0}article table th{background-color:#f1f3f5;font-weight:bold;text-align:left}article table .selectedCell:after{background:rgba(200,200,255,.4);content:"";left:0;right:0;top:0;bottom:0;pointer-events:none;position:absolute;z-index:2}article table .column-resize-handle{background-color:#adf;bottom:-2px;position:absolute;right:-2px;pointer-events:none;top:0;width:4px}article table p{margin:0} +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:rgba(0,0,0,0)}abbr[title]{border-bottom:none;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--light-grey: #e1e1e1;--dark-grey: #333;--primary-color: #33c3f0;--secondary-color: #555555;--border-color: #bbb;--link-color: #1eaedb;--font-color: #222;--color-contrast-low: hsl(240, 1%, 83%);--color-contrast-medium: hsl(240, 1%, 48%);--color-contrast-high: hsl(240, 4%, 20%);--font-family: Inter, sans-serif;--ipad-min-width: 1366;--container-width: 960px}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:"Inter",sans-serif;color:#222}a{color:#1eaedb}a:hover{color:#1b9cc5}hr{margin-top:3rem;margin-bottom:3.5rem;border-width:0;border-top:1px solid #e1e1e1}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:2rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-0.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-0.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-0.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-0.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-0.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media(min-width: 550px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}p{margin-top:0}.navbar{display:none}@media(min-width: 750px){.navbar+.docs-section{border-top-width:0}.navbar,.navbar-spacer{display:block;width:100%;height:6.5rem;background:#fff;z-index:99;border-top:1px solid #eee;border-bottom:1px solid #eee}.navbar-spacer{display:none}.navbar>.container{width:100%}.navbar-list{list-style:none;margin-bottom:0;padding-left:0;margin-top:0}.navbar-item{position:relative;float:left;margin-bottom:0}.navbar-link{text-transform:uppercase;font-size:11px;font-weight:600;letter-spacing:.2rem;margin-right:35px;text-decoration:none;line-height:6.5rem;color:#222}.navbar-link.active{color:#33c3f0}.has-docked-nav .navbar{position:fixed;top:0;left:0}.has-docked-nav .navbar-spacer{display:block}.has-docked-nav .navbar>.container{width:80%}}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#011627;color:#d6deeb}.hljs-keyword{color:#c792ea;font-style:italic}.hljs-built_in{color:#addb67;font-style:italic}.hljs-type{color:#82aaff}.hljs-literal{color:#ff5874}.hljs-number{color:#f78c6c}.hljs-regexp{color:#5ca7e4}.hljs-string{color:#ecc48d}.hljs-subst{color:#d3423e}.hljs-symbol{color:#82aaff}.hljs-class{color:#ffcb8b}.hljs-function{color:#82aaff}.hljs-title{color:#dcdcaa;font-style:italic}.hljs-params{color:#7fdbca}.hljs-comment{color:#637777;font-style:italic}.hljs-doctag{color:#7fdbca}.hljs-meta,.hljs-meta .hljs-keyword{color:#82aaff}.hljs-meta .hljs-string{color:#ecc48d}.hljs-section{color:#82b1ff}.hljs-attr,.hljs-name,.hljs-tag{color:#7fdbca}.hljs-attribute{color:#80cbc4}.hljs-variable{color:#addb67}.hljs-bullet{color:#d9f5dd}.hljs-code{color:#80cbc4}.hljs-emphasis{color:#c792ea;font-style:italic}.hljs-strong{color:#addb67;font-weight:700}.hljs-formula{color:#c792ea}.hljs-link{color:#ff869a}.hljs-quote{color:#697098;font-style:italic}.hljs-selector-tag{color:#ff6363}.hljs-selector-id{color:#fad430}.hljs-selector-class{color:#addb67;font-style:italic}.hljs-selector-attr,.hljs-selector-pseudo{color:#c792ea;font-style:italic}.hljs-template-tag{color:#c792ea}.hljs-template-variable{color:#addb67}.hljs-addition{color:#addb67;font-style:italic}.hljs-deletion{color:rgba(239,83,80,.5647058824);font-style:italic}article{max-width:732px;margin:4rem auto;font-family:Georgia,Times,Times New Roman,serif;font-weight:400;font-style:normal;font-size:18px;line-height:30px;color:rgba(12,10,10,.8)}article ::selection,article :not(pre) ::selection{background:rgba(95,95,95,.1843137255)}article section{margin:3rem 0}article section p:empty::after,article section .l:empty::after{content:" "}article h1,article h2,article h3,article h4{font-family:Inter,sans-serif;font-weight:600;font-style:normal;letter-spacing:initial}article h1{font-size:32px;margin:21px 21px 12px;line-height:34px}article h2{font-size:30px;margin:21px 21px 12px;line-height:34px}article h3{font-size:28px;margin:18px 21px 9px}article h4{font-size:24px;margin:18px 21px 7px}article p{margin:0 21px 12px;word-wrap:break-word;white-space:pre-wrap}article blockquote{margin:18px 21px 16px 0;padding-left:15px;position:relative;font-style:italic;word-wrap:break-word;border-left:3px solid #000}article aside{font-size:21px;color:rgba(0,0,0,.6);margin:18px 21px 16px;padding:0 18px;text-align:center;font-style:italic}article hr{width:50%;margin:30px auto;border:none;font-size:2px;text-align:right;overflow:visible}article hr:after{content:"";display:block;border-top:1px solid #c9cdd1;margin:0 3px;position:relative;top:1px}article [contenteditable=false]{white-space:normal}article ul[data-type=taskList]{list-style:none;padding:0;margin:0 21px 12px}article ul[data-type=taskList] li{display:block}article ul[data-type=taskList] li>label{float:left;margin-right:.8rem}article ul[data-type=taskList] li>div{display:contents}article ul[data-type=taskList] li p{margin:0}article mark{background-color:rgba(250,245,148,.7058823529)}article :not(pre)>code{background-color:rgba(97,97,97,.1);color:#616161;font-family:JetBrainsMono,monospace;font-variant-ligatures:normal;padding:.16rem .48rem;border-radius:.4rem;font-size:1.44rem}article a[href]{text-decoration:none;transition:all .2s ease-in-out;color:rgba(0,0,0,.8);border-bottom:.1rem rgba(0,0,0,.425) solid}article a[href]:hover,article a[href]:focus{border-bottom:.1rem rgba(0,0,0,.8) solid}article *[data-placeholder].empty{position:relative}article *[data-placeholder].empty:before{position:absolute;left:0;right:0;content:attr(data-placeholder);color:rgba(0,0,0,.44);font-weight:normal;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;pointer-events:none}article *[data-label]{position:relative}article *[data-label]:after{position:absolute;content:attr(data-label);color:rgba(0,0,0,.44);border-right:1px solid rgba(0,0,0,.15);right:100%;top:-6px;bottom:-6px;padding:6px 12px;font-weight:normal;margin:0 21px;opacity:0;visibility:hidden;transition:opacity .15s ease}article .focused *[data-label].empty:after{position:absolute;content:attr(data-label);color:rgba(0,0,0,.44);border-right:1px solid rgba(0,0,0,.15);right:100%;top:-6px;bottom:-6px;padding:6px 12px;font-weight:normal;margin:0 21px;opacity:0;visibility:hidden;transition:opacity .15s ease}article *[data-label].focused:after{opacity:1;visibility:visible}article *[role=definition]{font-family:Inter,sans-serif;font-size:15px;line-height:18px;color:#79828b;margin:12px 21px;font-style:normal}article *[role=definition] a{color:#79828b}article *[role=definition] a[href]{border-bottom:.1em solid rgba(0,0,0,.4)}article *[role=definition].empty{display:block}article *[role=definition] time:before{content:"•";padding:0 7px}article img{max-width:100%;vertical-align:top}article .iframe_wrap{max-width:100%;vertical-align:top;position:relative}article .iframe_wrap iframe{position:absolute;width:1px;height:1px;min-width:100%;min-height:100%;top:0;left:0}article figure{display:flex;flex-direction:column;margin:0 21px 16px;padding:0;text-align:center;position:relative;user-select:inherit}article figure.ProseMirror-selectednode img{box-shadow:0 0 0 3px rgba(0,0,0,.4)}article figure.ProseMirror-selectednode img:hover{box-shadow:0 0 0 3px rgba(0,0,0,.4)}article figure img:hover{box-shadow:0 0 0 3px rgba(0,0,0,.2)}article figcaption{font-family:Inter,sans-serif;font-size:15px;color:#79828b;padding:12px 21px 0;line-height:18px;vertical-align:top;text-align:center}article .figure_wrapper{position:relative;display:contents}article .figure_wrapper img{opacity:1;transition:opacity .15s ease;max-height:690px;object-fit:contain;image-orientation:none;width:100%}article .figure_wrapper.loading img{opacity:.3}article .figure_wrapper .file_progress{position:absolute;z-index:1;bottom:0;left:0;right:0;padding:3px;background-color:rgba(255,255,255,.7);transition:opacity .15s ease;visibility:hidden;opacity:0}article .figure_wrapper.loading .file_progress{visibility:visible;opacity:1}article .figure_wrapper .file_progress_bar{height:2px;background-color:#333;transition:width .1s linear;width:0}article figcaption[data-placeholder].empty{position:relative;overflow:hidden;width:calc(100% - 170px)}article figcaption[data-placeholder].empty:before{display:none}article figcaption[data-placeholder].empty:after{position:absolute;content:attr(data-placeholder);color:rgba(0,0,0,.44);font-weight:normal;font-style:italic;top:0px;padding-top:12px}article pre{background:#232b2b;border-radius:.3rem;box-shadow:0 .5rem 1rem rgba(0,0,0,.4);border-left:6px solid #516363;margin:1.6rem auto;white-space:pre-wrap;color:#c7d3de;counter-reset:step;counter-increment:step 0;position:relative;font-family:JetBrainsMono,monospace;font-variant-ligatures:normal;font-size:1.44rem;padding:1.2rem 1.6rem}article pre code{font-family:JetBrainsMono,monospace;font-variant-ligatures:normal;font-size:1.44rem;line-height:2.4rem;white-space:pre;margin-left:4rem;display:block;overflow-x:auto;scrollbar-color:#516363}article pre code::-webkit-scrollbar{cursor:pointer;height:.6rem}article pre code::-webkit-scrollbar-thumb{cursor:pointer;background:#516363;border-radius:.5rem}article pre code::-webkit-scrollbar-thumb:hover{background:#465555}article pre ::selection{background:rgba(219,219,219,.1764705882)}article pre div.l:before{color:#516363;position:absolute;left:15px;content:counter(step);counter-increment:step}article table{font-family:Georgia,Times,Times New Roman,serif;font-weight:400;font-style:normal;font-size:18px;line-height:30px;color:rgba(12,10,10,.8);border-collapse:collapse;overflow:hidden;table-layout:fixed;width:100%;margin:1.6rem 0}article table td,article table th{border:2px solid #ced4da;box-sizing:border-box;min-width:1.6rem;padding:3px 5px;position:relative;vertical-align:top}article table td>*,article table th>*{margin-bottom:0}article table th{background-color:#f1f3f5;font-weight:bold;text-align:left}article table .selectedCell:after{background:rgba(200,200,255,.4);content:"";left:0;right:0;top:0;bottom:0;pointer-events:none;position:absolute;z-index:2}article table .column-resize-handle{background-color:#adf;bottom:-2px;position:absolute;right:-2px;pointer-events:none;top:0;width:4px}article table p{margin:0} /* 3. Tables with the "No borders" preset (the Table extension renders data-borders="none" on the ): suppress the default cell grid. diff --git a/src/export/postprocess.ts b/src/export/postprocess.ts index fbe8b80..6bb52e9 100644 --- a/src/export/postprocess.ts +++ b/src/export/postprocess.ts @@ -95,7 +95,20 @@ const SVG_NS = "http://www.w3.org/2000/svg"; type IconShape = { tag: string; attrs: Record }; -function svgIcon(doc: Document, shapes: IconShape[]): SVGElement { +/** Export post-processing builds DOM inside a detached DOMParser document, + * where Obsidian's `createEl` helpers are not available. The narrow type + * expresses that (a full `Document` is assignable to it); element creation + * goes through `createElIn`. */ +type DetachedDoc = Pick; + +function createElIn( + doc: DetachedDoc, + tag: K, +): HTMLElementTagNameMap[K] { + return doc.createElement(tag); +} + +function svgIcon(doc: DetachedDoc, shapes: IconShape[]): SVGElement { const svg = doc.createElementNS(SVG_NS, "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("fill", "none"); @@ -129,8 +142,8 @@ const CHECK_ICON: IconShape[] = [ { tag: "path", attrs: { d: "M5 12l5 5l10 -10" } }, ]; -function iconSpan(doc: Document, cls: string, icon: IconShape[]): HTMLElement { - const span = doc.createElement("span"); +function iconSpan(doc: DetachedDoc, cls: string, icon: IconShape[]): HTMLElement { + const span = createElIn(doc, "span"); span.className = cls; span.appendChild(svgIcon(doc, icon)); return span; @@ -144,20 +157,20 @@ function wrapCodeBlock(parsed: Document, pre: Element): void { const code = pre.querySelector("code"); if (code == null) return; - const block = parsed.createElement("div"); + const block = createElIn(parsed, "div"); block.className = "hljs-codeblock"; if (code.getAttribute("wrap") === "true") block.classList.add("is-wrapped"); - const controls = parsed.createElement("div"); + const controls = createElIn(parsed, "div"); controls.className = "hljs-codeblock__controls"; - const lang = parsed.createElement("span"); + const lang = createElIn(parsed, "span"); lang.className = "hljs-codeblock__lang"; lang.textContent = languageLabel(code.getAttribute("class")); controls.appendChild(lang); - const actions = parsed.createElement("div"); + const actions = createElIn(parsed, "div"); actions.className = "hljs-codeblock__actions"; - const copyButton = parsed.createElement("button"); + const copyButton = createElIn(parsed, "button"); copyButton.setAttribute("type", "button"); copyButton.className = "hljs-codeblock__btn hljs-codeblock__btn--copy"; copyButton.title = "Copy code"; @@ -216,12 +229,12 @@ export async function postProcessForExport( } if (!images.includes(vaultPath)) images.push(vaultPath); - const figure = parsed.createElement("figure"); + const figure = createElIn(parsed, "figure"); figure.setAttribute("data-align", el.dataset["align"] ?? "left"); const style = figureWidthStyle(el.dataset["width"] ?? null); if (style !== "") figure.setAttribute("style", style); - const img = parsed.createElement("img"); + const img = createElIn(parsed, "img"); img.setAttribute("src", `images/${fileName}`); const alt = el.dataset["filename"]; if (alt) img.setAttribute("alt", alt); @@ -283,11 +296,11 @@ export async function postProcessForExport( svg: new XMLSerializer().serializeToString(svgRoot), }); - const figure = parsed.createElement("figure"); + const figure = createElIn(parsed, "figure"); figure.className = "mermaid-figure"; const align = el.dataset["align"]; if (align && align !== "left") figure.setAttribute("data-align", align); - const img = parsed.createElement("img"); + const img = createElIn(parsed, "img"); img.setAttribute("src", `images/${name}`); img.setAttribute("alt", "Diagram"); figure.appendChild(img); diff --git a/src/export/preview.ts b/src/export/preview.ts index 999f7af..a06d026 100644 --- a/src/export/preview.ts +++ b/src/export/preview.ts @@ -93,8 +93,10 @@ export function extractPreview(doc: JSONContent): NotePreview { } const image = findFirstNode(doc, "image"); - const previewImageId = - typeof image?.attrs?.data?.id === "string" ? image.attrs.data.id : null; + // PM stores node attrs as `Record`; narrow to the one field + // the preview needs instead of letting `any` leak into the id. + const imageData = image?.attrs?.data as { id?: unknown } | undefined; + const previewImageId = typeof imageData?.id === "string" ? imageData.id : null; return { title, description: escapeDescription(description), previewImageId }; } diff --git a/src/main.ts b/src/main.ts index ab7b99c..8ded66a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ import { PluginSettingTab, setIcon, Setting, + type SettingDefinitionItem, TFolder, WorkspaceLeaf, } from "obsidian"; @@ -189,7 +190,10 @@ export default class NotesPlugin extends Plugin { } async loadSettings(): Promise { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + // loadData() is `any` by plugin API contract; narrow it before merging so + // a malformed data.json cannot smuggle untyped values into settings. + const stored = (await this.loadData()) as Partial | null; + this.settings = Object.assign({}, DEFAULT_SETTINGS, stored); setWriteLogEnabled(this.settings.writeLog); } @@ -517,6 +521,47 @@ class InscriptumSettingTab extends PluginSettingTab { this.plugin = plugin; } + /** Declarative settings (Obsidian 1.13.0+): rows become searchable and + * render from these definitions. Kept in sync with `display()`, which + * older runtimes (minAppVersion 1.6.6) render instead — on 1.13.0+ the + * framework skips `display()` when this returns a non-empty array. */ + getSettingDefinitions(): SettingDefinitionItem[] { + return [ + { + control: { + type: "text", + key: "exportFolder", + defaultValue: DEFAULT_SETTINGS.exportFolder, + }, + name: "Export folder", + desc: "Vault folder where the 'export as website' command writes its output. Each note becomes a self-contained subfolder (index.html, note.css, images) that can be hosted anywhere or opened directly from disk.", + }, + { + control: { + type: "toggle", + key: "writeLog", + defaultValue: DEFAULT_SETTINGS.writeLog, + }, + name: "Diagnostic log", + desc: "When turned on, inscriptum keeps a technical journal of its note-saving activity in a hidden file named .inscriptum-log.jsonl in your vault. If a note is ever damaged or lost, this journal helps figure out what happened. Leave it off in everyday use and turn it on when troubleshooting a problem.", + }, + ]; + } + + async setControlValue(key: string, value: unknown): Promise { + // Same normalize-on-write semantics the imperative display() applies. + if (key === "exportFolder") { + if (typeof value === "string") { + this.plugin.settings.exportFolder = normalizePath( + value.trim() || DEFAULT_SETTINGS.exportFolder, + ); + } + } else if (key === "writeLog" && typeof value === "boolean") { + this.plugin.settings.writeLog = value; + } + await this.plugin.saveSettings(); + } + display(): void { const { containerEl } = this; containerEl.empty(); @@ -524,7 +569,7 @@ class InscriptumSettingTab extends PluginSettingTab { new Setting(containerEl) .setName("Export folder") .setDesc( - "Vault folder where the 'Export as website' command writes its output. Each note becomes a self-contained subfolder (index.html, note.css, images) that can be hosted anywhere or opened directly from disk.", + "Vault folder where the 'export as website' command writes its output. Each note becomes a self-contained subfolder (index.html, note.css, images) that can be hosted anywhere or opened directly from disk.", ) .addText((text) => text diff --git a/src/storage/noteStorage.ts b/src/storage/noteStorage.ts index cef547b..0905502 100644 --- a/src/storage/noteStorage.ts +++ b/src/storage/noteStorage.ts @@ -181,12 +181,20 @@ async function replaceFile( } } +/** The one node fs method the atomic-replace fast path needs, declared + * structurally: `typeof import("node:fs")` needs @types/node to resolve and + * does not in every linting environment (the Obsidian review bot), while + * `require("fs")` only exists on desktop anyway. */ +interface NodeFs { + renameSync(from: string, to: string): void; +} + /** The node fs module on desktop Obsidian; null on mobile / when blocked. */ -function getNodeFs(): typeof import("node:fs") | null { +function getNodeFs(): NodeFs | null { try { const req = (window as { require?: (id: string) => unknown }).require; if (typeof req !== "function") return null; - return req("fs") as typeof import("node:fs"); + return req("fs") as NodeFs; } catch { return null; } diff --git a/src/storage/um/umContainer.ts b/src/storage/um/umContainer.ts index 9cedf93..e3e507c 100644 --- a/src/storage/um/umContainer.ts +++ b/src/storage/um/umContainer.ts @@ -261,17 +261,6 @@ export function readAssetDescriptors( return out; } -function writeAssetDescriptors( - manifest: UmManifest, - assets: UmAssetDescriptor[], -): void { - if (assets.length === 0) { - delete manifest.assets; - } else { - manifest.assets = assets; - } -} - /** Serialize the container deterministically: fixed timestamps, fixed entry * order, pretty-printed JSON. Assets and unknown entries are stored * uncompressed (level 0) — images are already compressed and unknown diff --git a/src/storage/um/umIds.ts b/src/storage/um/umIds.ts index e2841e1..3fc1ef5 100644 --- a/src/storage/um/umIds.ts +++ b/src/storage/um/umIds.ts @@ -18,8 +18,8 @@ let lastRandom: number[] = []; function randomBits(length: number): number[] { const out: number[] = new Array(length); const bytes = new Uint8Array(length); - if (typeof globalThis.crypto?.getRandomValues === "function") { - globalThis.crypto.getRandomValues(bytes); + if (typeof window.crypto?.getRandomValues === "function") { + window.crypto.getRandomValues(bytes); } else { for (let i = 0; i < length; i++) { bytes[i] = Math.floor(Math.random() * 256); diff --git a/src/storage/um/umNotepad.ts b/src/storage/um/umNotepad.ts index e228441..35ccbf9 100644 --- a/src/storage/um/umNotepad.ts +++ b/src/storage/um/umNotepad.ts @@ -304,7 +304,7 @@ export class UmNotepad { // The "(copy)" suffix must also land in the copy document's title // source (header node or first line), or the title mirror wipes it on // the next load. A non-empty base title guarantees a writable line. - const copyDoc: JSONContent = JSON.parse(JSON.stringify(sourceDoc)); + const copyDoc = JSON.parse(JSON.stringify(sourceDoc)) as JSONContent; if (descriptor.title) { const titled = descriptor.schema === UM_SCHEMA_TITLE diff --git a/src/storage/um/umVault.ts b/src/storage/um/umVault.ts index 25ce362..8ff0543 100644 --- a/src/storage/um/umVault.ts +++ b/src/storage/um/umVault.ts @@ -144,12 +144,20 @@ async function replaceFile( await adapter.writeBinary(targetPath, data); } +/** The one node fs method the atomic-replace fast path needs, declared + * structurally: `typeof import("node:fs")` needs @types/node to resolve and + * does not in every linting environment (the Obsidian review bot), while + * `require("fs")` only exists on desktop anyway. */ +interface NodeFs { + renameSync(from: string, to: string): void; +} + /** The node fs module on desktop Obsidian; null on mobile / when blocked. */ -function getNodeFs(): typeof import("node:fs") | null { +function getNodeFs(): NodeFs | null { try { const req = (window as { require?: (id: string) => unknown }).require; if (typeof req !== "function") return null; - return req("fs") as typeof import("node:fs"); + return req("fs") as NodeFs; } catch { return null; } diff --git a/src/styles/drag-handle.css b/src/styles/drag-handle.css index 8225762..badee74 100644 --- a/src/styles/drag-handle.css +++ b/src/styles/drag-handle.css @@ -94,11 +94,11 @@ - is-dragging: the drag is armed — grabbing cursor, accent tint. The class lands on the closest .texto-*-fold-chevron / -chevron-host element (either the host or the inner span, depending on hit target). - The hiding is !important on purpose: while the pointer is held on the - chevron, Chrome latches :hover to it, and the hover-reveal rules (equal - specificity, in later stylesheets — task-item/style.css is imported - after this file) would otherwise keep the chevron visible under the - dots overlay. */ + The reveal rules carry :not(.is-grabbed)/:not(.is-dragging) exclusions: + while the pointer is held on the chevron, Chrome latches :hover to it, + and the hover-reveal rules (equal specificity, in later stylesheets — + task-item/style.css is imported after this file) would otherwise keep + the chevron visible under the dots overlay. */ .note .texto-heading-fold-chevron-host.is-hover:not(.is-grabbed):not(.is-dragging), .note @@ -110,8 +110,8 @@ .note .texto-heading-fold-chevron-host.is-grabbed, .note .texto-heading-fold-chevron-host .texto-heading-fold-chevron.is-grabbed, .note .texto-task-fold-chevron.is-grabbed { - opacity: 0 !important; - pointer-events: none !important; /* the dots-handle overlay owns the pointer now */ + opacity: 0; + pointer-events: none; /* the dots-handle overlay owns the pointer now */ } .note .texto-heading-fold-chevron-host.is-dragging, @@ -119,8 +119,8 @@ .note .texto-task-fold-chevron.is-dragging { cursor: grabbing; color: var(--text-normal, #dadada); - opacity: 0 !important; - pointer-events: none !important; + opacity: 0; + pointer-events: none; } /* prosemirror-dropcursor overlay (see extensions/dropcursor) — used by diff --git a/src/styles/editor.css b/src/styles/editor.css index baea69a..2726837 100644 --- a/src/styles/editor.css +++ b/src/styles/editor.css @@ -245,7 +245,9 @@ .texto-editor .ProseMirror a { color: var(--text-accent, var(--interactive-accent, #7c6af7)); text-decoration: underline; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-thickness: 1px; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-color: color-mix( in srgb, var(--text-accent, var(--interactive-accent, #7c6af7)) 40%, @@ -260,6 +262,7 @@ .texto-editor .ProseMirror a:hover { color: var(--text-accent-hover, var(--text-accent, #7c6af7)); + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-thickness: 1.5px; } diff --git a/src/styles/heading-folding.css b/src/styles/heading-folding.css index 0cc02fc..bcd7cd6 100644 --- a/src/styles/heading-folding.css +++ b/src/styles/heading-folding.css @@ -11,9 +11,12 @@ - node decorations `texto-folded-content` on hidden body blocks. ═══════════════════════════════════════════════════════════════ */ -/* Hide folded section bodies. */ -.note .texto-folded-content { - display: none !important; +/* Hide folded section bodies. Specificity (0,3,0): extension stylesheets + imported after this file (code-block-hljs, mermaid, table, task-item) + also set `display` on block wrappers — the extra .ProseMirror class + keeps this rule ahead of them regardless of file order. */ +.note .ProseMirror .texto-folded-content { + display: none; } /* The chevron host sits inside the heading's inline content, pulled out to @@ -64,14 +67,17 @@ /* Show the chevron on heading hover (Obsidian behavior), on its own hover, and always while collapsed. */ -.note h1:hover > .texto-heading-fold-chevron-host, -.note h2:hover > .texto-heading-fold-chevron-host, -.note h3:hover > .texto-heading-fold-chevron-host, -.note h4:hover > .texto-heading-fold-chevron-host, -.note h5:hover > .texto-heading-fold-chevron-host, -.note h6:hover > .texto-heading-fold-chevron-host, -.note .texto-heading-fold-chevron-host:hover, -.note .is-folded > .texto-heading-fold-chevron-host { +/* The :not(.is-grabbed)/:not(.is-dragging) exclusions let the + hold-grab/drag hide rules in drag-handle.css win without !important — + while grabbing, Chrome latches :hover and the folded state stays on. */ +.note h1:hover > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging), +.note h2:hover > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging), +.note h3:hover > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging), +.note h4:hover > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging), +.note h5:hover > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging), +.note h6:hover > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging), +.note .texto-heading-fold-chevron-host:hover:not(.is-grabbed):not(.is-dragging), +.note .is-folded > .texto-heading-fold-chevron-host:not(.is-grabbed):not(.is-dragging) { opacity: 1; } diff --git a/src/styles/mobile.css b/src/styles/mobile.css index c08d7f8..e6795c5 100644 --- a/src/styles/mobile.css +++ b/src/styles/mobile.css @@ -163,10 +163,14 @@ background: transparent; overflow-x: auto; overflow-y: hidden; - scrollbar-width: none; -webkit-overflow-scrolling: touch; } +.note-view-container.is-mobile .note-toolbar::-webkit-scrollbar, +.notepad-view-container.is-mobile .note-toolbar::-webkit-scrollbar { + display: none; +} + /* Stop scroll chaining: when the horizontal scroller reaches its edge the gesture must not hand over to the Obsidian workspace, whose horizontal swipes open the file sidebar / info drawer (and dismiss the keyboard). @@ -323,7 +327,6 @@ .notepad-view-container.is-mobile .mobile-sel-bar .bubble-menu-media-bar { flex-wrap: nowrap; overflow-x: auto; - scrollbar-width: none; -webkit-overflow-scrolling: touch; } .note-view-container.is-mobile diff --git a/src/styles/texto-editor.css b/src/styles/texto-editor.css index d867eb2..4fdf8db 100644 --- a/src/styles/texto-editor.css +++ b/src/styles/texto-editor.css @@ -26,12 +26,15 @@ white-space: pre-wrap; } +/* ProseMirror's block-separator hack (prosemirror-view inserts 1px imgs + between blocks for drop targets). No !important: nothing in the plugin + styles or Obsidian's app.css out-specifics the class selector. */ img.ProseMirror-separator { - display: inline !important; - border: none !important; - margin: 0 !important; - width: 1px !important; - height: 1px !important; + display: inline; + border: none; + margin: 0; + width: 1px; + height: 1px; } .ProseMirror-gapcursor { diff --git a/src/styles/typography-v2.css b/src/styles/typography-v2.css index 3b06b84..7504737 100644 --- a/src/styles/typography-v2.css +++ b/src/styles/typography-v2.css @@ -114,7 +114,9 @@ .note a { color: var(--note-accent-text); text-decoration: underline; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-thickness: 1px; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-color: color-mix( in srgb, var(--note-accent-text) 40%, @@ -129,6 +131,7 @@ .note a:hover { color: var(--note-accent-hover); + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-color: color-mix( in srgb, var(--note-accent-hover) 55%, @@ -279,11 +282,13 @@ .note .chk li.on { color: var(--text-muted); text-decoration: line-through; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-color: color-mix( in srgb, var(--text-muted) 45%, transparent ); + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-thickness: 1px; } diff --git a/src/texto/core/@types/AnyConfig.ts b/src/texto/core/@types/AnyConfig.ts index 74ba9ef..497f0ee 100644 --- a/src/texto/core/@types/AnyConfig.ts +++ b/src/texto/core/@types/AnyConfig.ts @@ -17,10 +17,13 @@ export type AnyObject = AnyRecord; export interface AnyConfig< Options = AnyObject, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Storage is intentionally `any`: extensions store arbitrary state Storage = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Config defaults to `any`: extension configs are structurally open Config extends Record = any, ConfigType = unknown, > { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- pass-through map of arbitrary tiptap-style config fields [key: string]: any; /** diff --git a/src/texto/core/@types/index.ts b/src/texto/core/@types/index.ts index 4eed2fa..19b71b1 100644 --- a/src/texto/core/@types/index.ts +++ b/src/texto/core/@types/index.ts @@ -27,10 +27,12 @@ export type Extensions = AnyExtension[]; // Centralize `any` for repeated patterns, avoiding duplicate warnings. // `any[]` is required here because this type stores callbacks with arbitrary parameter lists. +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- callbacks with arbitrary parameter lists export type AnyFn = (...args: any[]) => unknown; export type AnyFnVoid = (...args: never[]) => void; +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- structural compatibility with any options interface (see AnyObject) export type AnyRecord = Record; /** Expansion of `keyof any` */ @@ -114,9 +116,11 @@ export type JSONContent = { marks?: { type: string; attrs?: AnyRecord; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mark attrs carry arbitrary payload fields [key: string]: any; }[]; text?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON document nodes carry arbitrary payload fields [key: string]: any; }; @@ -141,11 +145,13 @@ export type CommandSpec = CommandFn; export type KeyboardShortcutCommand = (props: { editor: Editor }) => boolean; export type Attribute = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- attribute defaults mirror ProseMirror's Attribute.default (any by contract) default: any; rendered?: boolean; renderHTML?: | ((attributes: Record) => Record | null) | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- parseHTML return mirrors ProseMirror's Attribute type (any by contract) parseHTML?: ((element: HTMLElement) => any) | null; keepOnSplit: boolean; isRequired?: boolean; diff --git a/src/texto/core/Extension.ts b/src/texto/core/Extension.ts index d3ee790..226252c 100644 --- a/src/texto/core/Extension.ts +++ b/src/texto/core/Extension.ts @@ -5,6 +5,7 @@ import { getExtensionField } from "./helpers/getExtensionField"; import { callOrReturn } from "./utilities/callOrReturn"; import { mergeDeep } from "./utilities/mergeDeep"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- default `any` keeps Extension assignable to Extension (covariance) export class Extension { type = "extension"; @@ -51,6 +52,7 @@ export class Extension { ) || {}; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ExtensionConfig generic defaults mirror the class defaults (covariance) static create( config: Partial> = {}, ) { diff --git a/src/texto/core/Mark.ts b/src/texto/core/Mark.ts index 10153bc..6066421 100644 --- a/src/texto/core/Mark.ts +++ b/src/texto/core/Mark.ts @@ -8,15 +8,19 @@ import { mergeDeep } from "./utilities/mergeDeep"; // Options/Storage default to `any` for covariance (Mark must stay assignable to Mark) export class Mark< + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- default `any` keeps Mark assignable to Mark (covariance) Options extends AnyRecord = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- default `any` keeps Mark assignable to Mark (covariance) Storage extends AnyRecord = any, > { type = "mark"; name = "mark"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- self-referential defaults must stay `any` so Mark is assignable to Mark parent: Mark | null = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- self-referential defaults must stay `any` so Mark is assignable to Mark child: Mark | null = null; options: Options = {} as Options; @@ -56,6 +60,7 @@ export class Mark< ) || {}; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- MarkConfig generic defaults mirror the class defaults (covariance) static create( config: Partial> = {}, ) { diff --git a/src/texto/core/Node.ts b/src/texto/core/Node.ts index 4c8c383..65c1053 100644 --- a/src/texto/core/Node.ts +++ b/src/texto/core/Node.ts @@ -7,21 +7,26 @@ import { mergeDeep } from "./utilities/mergeDeep"; // Options/Storage default to `any` for covariance (Node must stay assignable to Node) export class Node< + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- default `any` keeps Node assignable to Node (covariance) Options extends AnyRecord = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- default `any` keeps Node assignable to Node (covariance) Storage extends AnyRecord = any, > { type = "node"; name = "node"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- self-referential defaults must stay `any` so Node is assignable to Node parent: Node | null = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- self-referential defaults must stay `any` so Node is assignable to Node child: Node | null = null; options: Options = {} as Options; storage: Storage; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- self-referential defaults must stay `any` so Node is assignable to Node config: NodeConfig = { name: this.name, }; @@ -53,6 +58,7 @@ export class Node< } // TODO: delete all static create methods... or... move to somewhere + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- NodeConfig generic defaults mirror the class defaults (covariance) static create( config: Partial> = {}, ) { diff --git a/src/texto/core/extensions/commands.ts b/src/texto/core/extensions/commands.ts index 7bf0930..865c63b 100644 --- a/src/texto/core/extensions/commands.ts +++ b/src/texto/core/extensions/commands.ts @@ -18,5 +18,6 @@ type CoreCommandsSet = { }; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CoreCommandsSet {} } diff --git a/src/texto/extensions/attachment/helpers/setAttachmentViewProps.ts b/src/texto/extensions/attachment/helpers/setAttachmentViewProps.ts index 27906e2..9b5530b 100644 --- a/src/texto/extensions/attachment/helpers/setAttachmentViewProps.ts +++ b/src/texto/extensions/attachment/helpers/setAttachmentViewProps.ts @@ -157,9 +157,12 @@ function onFileSelected( // attrs back as they were when the picker opened — placeholder for a // fresh block, the previous file for a replace — and drop the // prepared file so the picker does not re-trigger. + // PM attrs are untyped; this node is guaranteed to be an attachment + // node, so read the previous attrs through the schema type. + const prevAttrs = node.attrs as AttachmentOptions["attributes"]; dispatchAttrs({ - state: { ...node.attrs.state, preparedData: undefined }, - data: node.attrs.data, + state: { ...prevAttrs.state, preparedData: undefined }, + data: prevAttrs.data, }); }, ); diff --git a/src/texto/extensions/attachment/index.ts b/src/texto/extensions/attachment/index.ts index 2ef0c29..266a4f4 100644 --- a/src/texto/extensions/attachment/index.ts +++ b/src/texto/extensions/attachment/index.ts @@ -8,6 +8,7 @@ export * from "./attachment"; export default Attachment; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/blockquote/index.ts b/src/texto/extensions/blockquote/index.ts index 40aea1d..a1ff1ab 100644 --- a/src/texto/extensions/blockquote/index.ts +++ b/src/texto/extensions/blockquote/index.ts @@ -6,5 +6,6 @@ export * from "./blockquote"; export default Blockquote; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/bold/index.ts b/src/texto/extensions/bold/index.ts index 3f33b0f..de367b6 100644 --- a/src/texto/extensions/bold/index.ts +++ b/src/texto/extensions/bold/index.ts @@ -6,5 +6,6 @@ export * from "./bold"; export default Bold; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/bullet-list/index.ts b/src/texto/extensions/bullet-list/index.ts index 25cdd46..2238922 100644 --- a/src/texto/extensions/bullet-list/index.ts +++ b/src/texto/extensions/bullet-list/index.ts @@ -7,5 +7,6 @@ export * from "./bullet-list"; export default BulletList; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/code-block-hljs/index.ts b/src/texto/extensions/code-block-hljs/index.ts index f9327af..ee7ae27 100644 --- a/src/texto/extensions/code-block-hljs/index.ts +++ b/src/texto/extensions/code-block-hljs/index.ts @@ -7,5 +7,6 @@ export * from "./hljsCodeBlockRow"; export * from "./hljsMark"; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/code-block-hljs/style.css b/src/texto/extensions/code-block-hljs/style.css index d4daacf..2604a8d 100644 --- a/src/texto/extensions/code-block-hljs/style.css +++ b/src/texto/extensions/code-block-hljs/style.css @@ -50,7 +50,6 @@ margin: var(--texto-code-block__code-margin); display: block; overflow-x: auto; - scrollbar-color: var(--texto-code-block__scrollbar-color); padding: var(--texto-code-block__code-padding); cursor: default; diff --git a/src/texto/extensions/code/index.ts b/src/texto/extensions/code/index.ts index 99ba7c6..9394637 100644 --- a/src/texto/extensions/code/index.ts +++ b/src/texto/extensions/code/index.ts @@ -6,5 +6,6 @@ export * from "./code"; export default Code; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/color/index.ts b/src/texto/extensions/color/index.ts index b6d0e96..2ff3bae 100644 --- a/src/texto/extensions/color/index.ts +++ b/src/texto/extensions/color/index.ts @@ -6,5 +6,6 @@ export * from "./color"; export default Color; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/hard-break/index.ts b/src/texto/extensions/hard-break/index.ts index ccfc7f2..2cab36c 100644 --- a/src/texto/extensions/hard-break/index.ts +++ b/src/texto/extensions/hard-break/index.ts @@ -6,5 +6,6 @@ export * from "./hard-break"; export default HardBreak; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/highlight/index.ts b/src/texto/extensions/highlight/index.ts index 75f6992..cc05e1d 100644 --- a/src/texto/extensions/highlight/index.ts +++ b/src/texto/extensions/highlight/index.ts @@ -6,5 +6,6 @@ export * from "./highlight"; export default Highlight; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/history/index.ts b/src/texto/extensions/history/index.ts index b94d911..5a3cf95 100644 --- a/src/texto/extensions/history/index.ts +++ b/src/texto/extensions/history/index.ts @@ -6,5 +6,6 @@ export * from "./history"; export default History; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/horizontal-rule/index.ts b/src/texto/extensions/horizontal-rule/index.ts index d872d67..428ca4a 100644 --- a/src/texto/extensions/horizontal-rule/index.ts +++ b/src/texto/extensions/horizontal-rule/index.ts @@ -6,5 +6,6 @@ export * from "./horizontal-rule"; export default HorizontalRule; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/image/index.ts b/src/texto/extensions/image/index.ts index fda5b8f..3d2e147 100644 --- a/src/texto/extensions/image/index.ts +++ b/src/texto/extensions/image/index.ts @@ -8,5 +8,6 @@ export * from "./image"; export default Image; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/image/style.css b/src/texto/extensions/image/style.css index 533e858..09c0b9d 100644 --- a/src/texto/extensions/image/style.css +++ b/src/texto/extensions/image/style.css @@ -195,9 +195,10 @@ td .texto-extension-image-host.texto-image-layout-full { } } -/* Full-width images are not user-resizable. */ +/* Full-width images are not user-resizable. No other rule sets `display` + on resize handles, so the plain declaration wins the cascade. */ .texto-extension-image-host.texto-image-layout-full .image-resize-handle { - display: none !important; + display: none; } .texto-extension-image-host.texto-image-layout-center { @@ -272,5 +273,5 @@ body.texto-image-resizing { /* No resize handles for error blocks ("file not found") and empty placeholders — there is no image to resize. */ .texto-extension-image-host.texto-image-no-file .image-resize-handle { - display: none !important; + display: none; } diff --git a/src/texto/extensions/italic/index.ts b/src/texto/extensions/italic/index.ts index 777c601..9afde7e 100644 --- a/src/texto/extensions/italic/index.ts +++ b/src/texto/extensions/italic/index.ts @@ -6,5 +6,6 @@ export * from "./italic"; export default Italic; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/list-keymap/index.ts b/src/texto/extensions/list-keymap/index.ts index 50329f6..b85be6e 100644 --- a/src/texto/extensions/list-keymap/index.ts +++ b/src/texto/extensions/list-keymap/index.ts @@ -9,5 +9,6 @@ export * as listHelpers from "./helpers/index"; export default ListKeymap; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/mermaid/index.ts b/src/texto/extensions/mermaid/index.ts index 020da93..cafb0ea 100644 --- a/src/texto/extensions/mermaid/index.ts +++ b/src/texto/extensions/mermaid/index.ts @@ -8,5 +8,6 @@ export * from "./mermaid"; export default Mermaid; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/mermaid/mermaid.ts b/src/texto/extensions/mermaid/mermaid.ts index 5abc579..d6954da 100644 --- a/src/texto/extensions/mermaid/mermaid.ts +++ b/src/texto/extensions/mermaid/mermaid.ts @@ -86,6 +86,13 @@ export function mermaidLightboxTarget( /** Applies the layout class to the host (left = default, no class) — * the NodeView dom does not receive rendered attrs automatically. */ +/** PM node attrs are untyped (`Record`); read the diagram + * source through this guard so `any` cannot leak into render/edit state. */ +function attrCode(attrs: unknown): string { + const code = (attrs as { code?: unknown } | null)?.code; + return typeof code === "string" ? code : ""; +} + function syncLayoutClass(element: HTMLElement, align: unknown): void { const layout = isMermaidLayout(align) ? align : "left"; for (const value of MERMAID_LAYOUTS) { @@ -168,7 +175,7 @@ export const Mermaid = Node.create({ throw new Error("[TEXTO ERROR]: getPos must be a function."); } - let lastCode = node.attrs.code ?? ""; + let lastCode = attrCode(node.attrs); let editing = false; let committing = false; let dirty = true; @@ -414,7 +421,7 @@ export const Mermaid = Node.create({ element.classList.toggle("is-readonly", !editor.isEditable); syncLayoutClass(element, updatedNode.attrs.align); - const code = updatedNode.attrs.code ?? ""; + const code = attrCode(updatedNode.attrs); if (code !== lastCode) { lastCode = code; dirty = true; diff --git a/src/texto/extensions/mermaid/style.css b/src/texto/extensions/mermaid/style.css index cfa7b1b..8d83632 100644 --- a/src/texto/extensions/mermaid/style.css +++ b/src/texto/extensions/mermaid/style.css @@ -150,6 +150,7 @@ /* layout-transparent rows container: the rows behave as direct children of the code element (line numbers anchor to the pre's gutter) */ .texto-extension-mermaid-host .mermaid-editor__rows { + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- load-bearing layout: rows must behave as direct children of the pre; supported in Obsidian's Chromium, flagged only as caniuse "partial support" */ display: contents; } @@ -189,7 +190,6 @@ white-space: pre; overflow-wrap: normal; overflow: auto; - scrollbar-width: none; } .texto-extension-mermaid-host .mermaid-editor__input::-webkit-scrollbar { diff --git a/src/texto/extensions/mermaid/view/mermaid.element.tsx b/src/texto/extensions/mermaid/view/mermaid.element.tsx index 58551da..986c3ce 100644 --- a/src/texto/extensions/mermaid/view/mermaid.element.tsx +++ b/src/texto/extensions/mermaid/view/mermaid.element.tsx @@ -27,6 +27,7 @@ export const mermaidElement = litView.element({ onInput: p.opt<(code: string) => void>(), }, })(function* (params) { + // eslint-disable-next-line @typescript-eslint/no-this-alias -- lit's element generator binds `this` to the host element; the arrow callbacks below capture it const host: HTMLElement = this; const textarea = (): HTMLTextAreaElement => diff --git a/src/texto/extensions/note-property/noteProperty.plugin.ts b/src/texto/extensions/note-property/noteProperty.plugin.ts index 61eee75..18469e6 100644 --- a/src/texto/extensions/note-property/noteProperty.plugin.ts +++ b/src/texto/extensions/note-property/noteProperty.plugin.ts @@ -654,8 +654,8 @@ function suggestionDecorations(state: EditorState): Decoration[] { } function spriteIcon(iconId: string): SVGSVGElement { - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); + const svg = createSvg("svg"); + const use = createSvg("use"); use.setAttribute("href", `#inscriptum-tlb-${iconId}`); svg.appendChild(use); return svg; @@ -663,7 +663,7 @@ function spriteIcon(iconId: string): SVGSVGElement { /** The value-type marker shown before the name, as in Obsidian. */ function typeIcon(type: "text" | "list"): HTMLElement { - const span = document.createElement("span"); + const span = createSpan(); span.className = "note-property-type-icon"; span.setAttribute("aria-hidden", "true"); span.appendChild(spriteIcon(type === "list" ? "propTag" : "propText")); @@ -676,7 +676,7 @@ function chipRemoveButton( propFrom: number, valueIndex: number, ): HTMLElement { - const button = document.createElement("button"); + const button = createEl("button"); button.type = "button"; button.className = "note-property-chip-x"; button.setAttribute("aria-label", "Remove tag"); @@ -691,7 +691,7 @@ function chipRemoveButton( } function removeButton(view: EditorView): HTMLElement { - const button = document.createElement("button"); + const button = createEl("button"); button.type = "button"; button.className = "note-property-grip"; button.setAttribute("aria-label", "Property actions"); @@ -782,14 +782,14 @@ function removeButton(view: EditorView): HTMLElement { /** The drop indicator: a horizontal accent line between the rows. */ function dropLineWidget(): HTMLElement { - const el = document.createElement("div"); + const el = createDiv(); el.className = "note-property-drop-line"; return el; } /** The grip dots (⋮⋮): six theme-colored points, no sprite entry needed. */ function gripDots(): SVGSVGElement { - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const svg = createSvg("svg"); svg.setAttribute("viewBox", "0 0 12 16"); svg.setAttribute("aria-hidden", "true"); for (const [cx, cy] of [ @@ -800,7 +800,7 @@ function gripDots(): SVGSVGElement { [4, 13], [8, 13], ]) { - const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + const dot = createSvg("circle"); dot.setAttribute("cx", String(cx)); dot.setAttribute("cy", String(cy)); dot.setAttribute("r", "1.3"); @@ -821,7 +821,7 @@ function closeRowMenu(): void { function openRowMenu(view: EditorView, propFrom: number, x: number, y: number): void { closeRowMenu(); - const menu = document.createElement("div"); + const menu = createDiv(); menu.className = "note-property-menu"; const close = () => { menu.remove(); @@ -845,11 +845,11 @@ function openRowMenu(view: EditorView, propFrom: number, x: number, y: number): if (custom) { // The type toggle: a custom row holds one text line or a list of // chips. Preset rows are locked to their preset's type. - const typeItem = document.createElement("button"); + const typeItem = createEl("button"); typeItem.type = "button"; typeItem.className = "note-property-menu__item"; typeItem.appendChild(spriteIcon(isList ? "propText" : "propTag")); - const label = document.createElement("span"); + const label = createSpan(); label.textContent = isList ? "Text" : "List"; typeItem.appendChild(label); @@ -866,16 +866,16 @@ function openRowMenu(view: EditorView, propFrom: number, x: number, y: number): view.focus(); }); menu.appendChild(typeItem); - const sep = document.createElement("div"); + const sep = createDiv(); sep.className = "note-property-menu__sep"; menu.appendChild(sep); } - const deleteItem = document.createElement("button"); + const deleteItem = createEl("button"); deleteItem.type = "button"; deleteItem.className = "note-property-menu__item is-danger"; deleteItem.appendChild(spriteIcon("trash")); - const deleteLabel = document.createElement("span"); + const deleteLabel = createSpan(); deleteLabel.textContent = "Delete property"; deleteItem.appendChild(deleteLabel); deleteItem.addEventListener("mousedown", (event) => { @@ -924,14 +924,14 @@ function addButton(view: EditorView, visible: boolean): HTMLElement { // A quiet text button on its own line after the last property row. The // line belongs to the layout permanently; `visible` (the zone's focus) // only toggles the text — no reflow either way. - const line = document.createElement("div"); + const line = createDiv(); line.className = visible ? "note-property-add is-active" : "note-property-add"; - const button = document.createElement("button"); + const button = createEl("button"); button.type = "button"; button.className = "note-property-add__btn"; - button.textContent = "+ Add property"; + button.textContent = "+ add property"; button.addEventListener("mousedown", (event) => { event.preventDefault(); event.stopPropagation(); @@ -946,10 +946,10 @@ function suggestionList( items: string[], selected: number, ): HTMLElement { - const list = document.createElement("div"); + const list = createDiv(); list.className = "note-property-suggestions"; items.forEach((name, index) => { - const item = document.createElement("button"); + const item = createEl("button"); item.type = "button"; item.className = index === selected @@ -973,7 +973,29 @@ function valuePosFromPoint( x: number, y: number, ): { pos: number; $pos: import("prosemirror-model").ResolvedPos } | null { - const range = document.caretRangeFromPoint?.(x, y); + // `caretPositionFromPoint` is the standard API (Chromium 128+); Obsidian + // builds with minAppVersion 1.6.6 run older Chromium and only ship the + // deprecated `caretRangeFromPoint` — feature-detect, fall back. Both are + // read through a local type so the deprecated declaration is not + // referenced directly. + const doc = document as Omit & { + caretPositionFromPoint?: ( + x: number, + y: number, + ) => { offsetNode: Node; offset: number } | null; + caretRangeFromPoint?: (x: number, y: number) => Range | null; + }; + let range: Range | null = null; + if (typeof doc.caretPositionFromPoint === "function") { + const position = doc.caretPositionFromPoint(x, y); + if (position) { + range = document.createRange(); + range.setStart(position.offsetNode, position.offset); + range.collapse(true); + } + } else { + range = doc.caretRangeFromPoint?.(x, y) ?? null; + } if (range == null || !view.dom.contains(range.startContainer)) return null; let pos: number; try { @@ -1092,7 +1114,7 @@ function selectWordAtPoint(view: EditorView, x: number, y: number): void { * layout (a just-deleted row would leave the line too long, drawn over * the body text). */ function zoneRuleWidget(view: import("prosemirror-view").EditorView, zoneEnd: number): HTMLElement { - const anchor = document.createElement("span"); + const anchor = createSpan(); anchor.className = "note-property-zone-rule-anchor"; const measure = () => { if (!anchor.isConnected) return; // the widget was replaced meanwhile @@ -1107,7 +1129,7 @@ function zoneRuleWidget(view: import("prosemirror-view").EditorView, zoneEnd: nu if (titleEl != null && lastRow != null) { const top = titleEl.getBoundingClientRect(); const bottom = (addEl ?? lastRow).getBoundingClientRect(); - const line = document.createElement("div"); + const line = createDiv(); line.className = "note-property-zone-rule"; line.style.left = `${Math.round(top.left - pmRect.left - 21)}px`; line.style.top = `${Math.round(top.top - pmRect.top - 8)}px`; @@ -1118,7 +1140,7 @@ function zoneRuleWidget(view: import("prosemirror-view").EditorView, zoneEnd: nu // measurement is best-effort: an empty anchor renders nothing } }; - requestAnimationFrame(measure); + window.requestAnimationFrame(measure); void zoneEnd; return anchor; } diff --git a/src/texto/extensions/note-property/style.css b/src/texto/extensions/note-property/style.css index 4319c93..6a9df7d 100644 --- a/src/texto/extensions/note-property/style.css +++ b/src/texto/extensions/note-property/style.css @@ -11,7 +11,7 @@ display: grid; /* Fixed name column — rows align with each other and with the header. */ grid-template-columns: var(--property-name-col) 1fr; - column-gap: 16px; + gap: 16px; margin: 2px 0; /* Top-aligned, not baseline-aligned: the name field is a scroll container (a long name scrolls inside its column), and scroll @@ -38,7 +38,6 @@ overflows into the value column. */ overflow-x: auto; text-overflow: ellipsis; - scrollbar-width: none; } .note-property-name::-webkit-scrollbar { @@ -374,7 +373,7 @@ .is-mobile .note-property { --property-name-col: 120px; grid-template-columns: 120px 1fr; - column-gap: 10px; + gap: 10px; } .is-mobile .note-property-zone-rule { diff --git a/src/texto/extensions/ordered-list/index.ts b/src/texto/extensions/ordered-list/index.ts index 5883f6e..783c584 100644 --- a/src/texto/extensions/ordered-list/index.ts +++ b/src/texto/extensions/ordered-list/index.ts @@ -7,5 +7,6 @@ export * from "./ordered-list"; export default OrderedList; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/paragraph/index.ts b/src/texto/extensions/paragraph/index.ts index e51b42d..8963ae3 100644 --- a/src/texto/extensions/paragraph/index.ts +++ b/src/texto/extensions/paragraph/index.ts @@ -6,5 +6,6 @@ export * from "./paragraph"; export default Paragraph; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/strike/index.ts b/src/texto/extensions/strike/index.ts index 7e10a3d..c66825b 100644 --- a/src/texto/extensions/strike/index.ts +++ b/src/texto/extensions/strike/index.ts @@ -6,5 +6,6 @@ export * from "./strike"; export default Strike; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/table/bordersCommands.test.ts b/src/texto/extensions/table/bordersCommands.test.ts index 23c2041..3646768 100644 --- a/src/texto/extensions/table/bordersCommands.test.ts +++ b/src/texto/extensions/table/bordersCommands.test.ts @@ -86,10 +86,10 @@ describe("table borders commands", () => { expect(bordersAt(editor, c1)).toEqual({ top: { style: "solid", width: "1.5pt", color: "#b3a3f7" }, }); - // The cell's DOM carries the inline border (beats the default grid). + // The cell's DOM carries the inline border (beats the default grid — + // inline declarations outrank every stylesheet rule). // (jsdom re-serializes hex colors to rgb() — assert format, not color.) expect(cellDomStyle(editor, c1)).toContain("border-top: 1.5pt solid"); - expect(cellDomStyle(editor, c1)).toContain("!important"); // Neighbours untouched. expect(bordersAt(editor, cells(editor)[1])).toBe(null); @@ -334,14 +334,14 @@ describe("table borders commands", () => { }); describe("borders style helpers", () => { - it("bordersToStyle renders per-side declarations with !important", () => { + it("bordersToStyle renders per-side declarations with", () => { expect( bordersToStyle({ top: { style: "solid", width: "2.25pt", color: null }, bottom: { style: "none" }, }), ).toBe( - "border-top: 2.25pt solid var(--inscriptum-table-border) !important; border-bottom: none !important", + "border-top: 2.25pt solid var(--inscriptum-table-border); border-bottom: none", ); expect(bordersToStyle(null)).toBe(""); expect(bordersToStyle({})).toBe(""); diff --git a/src/texto/extensions/table/helpers/borders.ts b/src/texto/extensions/table/helpers/borders.ts index 099c137..6a7fadf 100644 --- a/src/texto/extensions/table/helpers/borders.ts +++ b/src/texto/extensions/table/helpers/borders.ts @@ -79,8 +79,9 @@ function safeBorderColor(color: string): string | null { } /** Inline style for the cell's `borders` attribute; "" when nothing to draw. - !important is required to beat the stylesheet's default - `td/th { border: 1px solid … !important }` (see style.css). + plain declarations suffice: inline styles outrank every + stylesheet rule, including the cell default in style.css and the + "No borders" preset suppressor. Sides with malformed values are skipped — an untrusted attribute must not reach the style. */ export function bordersToStyle( @@ -92,7 +93,7 @@ export function bordersToStyle( const spec = borders[side]; if (!spec) continue; if (spec.style === "none") { - parts.push(`border-${side}: none !important`); + parts.push(`border-${side}: none`); } else { const width = spec.width ?? "1pt"; const color = safeBorderColor(spec.color || BORDER_AUTO_COLOR); @@ -103,7 +104,7 @@ export function bordersToStyle( ) { continue; } - parts.push(`border-${side}: ${width} ${spec.style} ${color} !important`); + parts.push(`border-${side}: ${width} ${spec.style} ${color}`); } } return parts.join("; "); diff --git a/src/texto/extensions/table/helpers/safeStyle.test.ts b/src/texto/extensions/table/helpers/safeStyle.test.ts index 3d1579f..e091658 100644 --- a/src/texto/extensions/table/helpers/safeStyle.test.ts +++ b/src/texto/extensions/table/helpers/safeStyle.test.ts @@ -28,7 +28,7 @@ describe("bordersToStyle injection guard", () => { bordersToStyle({ top: { style: "solid", width: "1.5pt", color: "#b3a3f7" }, }), - ).toBe("border-top: 1.5pt solid #b3a3f7 !important"); + ).toBe("border-top: 1.5pt solid #b3a3f7"); }); it("skips sides with malformed values instead of interpolating them", () => { @@ -42,7 +42,7 @@ describe("bordersToStyle injection guard", () => { bottom: { style: "solid; } td { display: none", width: "1pt" } as never, right: { style: "solid", width: "1.5pt", color: "#b3a3f7" }, }), - ).toBe("border-right: 1.5pt solid #b3a3f7 !important"); + ).toBe("border-right: 1.5pt solid #b3a3f7"); }); it("keeps the theme-default color and rejects crafted colors", () => { @@ -50,7 +50,7 @@ describe("bordersToStyle injection guard", () => { bordersToStyle({ left: { style: "dashed", width: "1pt", color: null }, }), - ).toBe("border-left: 1pt dashed var(--inscriptum-table-border) !important"); + ).toBe("border-left: 1pt dashed var(--inscriptum-table-border)"); expect( bordersToStyle({ left: { diff --git a/src/texto/extensions/table/index.ts b/src/texto/extensions/table/index.ts index fa895a1..c9bdfe4 100644 --- a/src/texto/extensions/table/index.ts +++ b/src/texto/extensions/table/index.ts @@ -13,5 +13,6 @@ export * from "./elements/tableCell"; export default Table; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/table/style.css b/src/texto/extensions/table/style.css index e449b52..5a8de4c 100644 --- a/src/texto/extensions/table/style.css +++ b/src/texto/extensions/table/style.css @@ -5,7 +5,8 @@ ProseMirror Tables — v2 Project uses its OWN table extension: div.table-wrapper (kebab-case) (see src/texto/extensions/table/tableView.ts) - Max specificity + !important to override default Obsidian styles. + Max specificity to override default Obsidian styles; no !important — + per-cell inline border styles must be able to win the cascade. ═══════════════════════════════════════════════════════════════ */ /* ─── Wrapper ─── */ @@ -38,18 +39,21 @@ /* Table with borders disabled ("No borders" preset): suppresses the default grid for every cell — including cells inserted later. Per-cell inline - border styles (rendered from the `borders` attr with !important) still - win over this rule, so borders can be drawn back onto a borderless - table cell by cell. */ + border styles (rendered from the `borders` attr, no !important) still win + over this rule — inline declarations beat any stylesheet rule — so + borders can be drawn back onto a borderless table cell by cell. */ .ProseMirror .table-wrapper table[data-borders="none"] td, .ProseMirror .table-wrapper table[data-borders="none"] th { - border: none !important; + border: none; } /* ─── Cells ─── */ .ProseMirror .table-wrapper table td, .ProseMirror .table-wrapper table th { - border: 1px solid var(--inscriptum-table-border) !important; + /* No !important: per-cell inline border styles (borders attr) must stay + able to override this default without resorting to !important of their + own (see helpers/borders.ts). */ + border: 1px solid var(--inscriptum-table-border); min-width: 1em; /* The horizontal value is public: the image extension negates it for full-width images inside a cell (see texto/extensions/image/style.css). */ @@ -59,8 +63,8 @@ box-sizing: border-box; position: relative; font-size: 14px; - color: #b6b8c4 !important; - background: transparent !important; + color: #b6b8c4; + background: transparent; font-variant-numeric: tabular-nums; } @@ -70,15 +74,15 @@ in srgb, var(--interactive-accent, #7c6af7) 4%, transparent - ) !important; + ); } .ProseMirror .table-wrapper table th { text-align: left; font-size: 13.5px; font-weight: 600; - color: #d9dbe4 !important; - background: #15151a !important; + color: #d9dbe4; + background: #15151a; } /* ─── Overlay (selection frame + resize) ─── @@ -145,13 +149,13 @@ body.texto-table__cell-dragging .theme-light .ProseMirror .table-wrapper table td, .theme-light .ProseMirror .table-wrapper table th { - border-color: #d7d7e0 !important; - color: #45454d !important; + border-color: #d7d7e0; + color: #45454d; } .theme-light .ProseMirror .table-wrapper table th { - color: #33333b !important; - background: #f0f0f4 !important; + color: #33333b; + background: #f0f0f4; } .theme-light .ProseMirror .table-wrapper table td:hover, @@ -160,7 +164,7 @@ body.texto-table__cell-dragging in srgb, var(--interactive-accent, #7c6af7) 5%, transparent - ) !important; + ); } /* ─── Selected cells (CellSelection) ─── diff --git a/src/texto/extensions/task-item-folding/index.ts b/src/texto/extensions/task-item-folding/index.ts index 91ea5b6..d98c460 100644 --- a/src/texto/extensions/task-item-folding/index.ts +++ b/src/texto/extensions/task-item-folding/index.ts @@ -21,5 +21,6 @@ export { // interface (same pattern as extensions/heading/index.ts), so // editor.commands.toggleTaskFold(...) is fully typed. declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/task-item/style.css b/src/texto/extensions/task-item/style.css index 9ab7e67..596138c 100644 --- a/src/texto/extensions/task-item/style.css +++ b/src/texto/extensions/task-item/style.css @@ -43,7 +43,9 @@ ul.chk, &[data-checked="true"] .content > p { color: #8d8f9b; text-decoration: line-through; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-color: rgba(141, 143, 155, 0.45); + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-thickness: 1px; transition: color 200ms; } @@ -52,6 +54,7 @@ ul.chk, /* Light theme adjustments matching typography-v2.html .theme-light .note .chk */ .theme-light .texto-extension-task-item-host[data-checked="true"] .content > p { color: #8f8f99; + /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- intentional design: custom thickness/color/offset of underline and strikethrough; fully rendered in Obsidian's Chromium, flagged only as caniuse "partial support" */ text-decoration-color: rgba(143, 143, 153, 0.5); } @@ -93,18 +96,22 @@ ul.chk, transition: transform 100ms ease-in-out; } -.texto-extension-task-item-host:hover .texto-task-fold-chevron, -.texto-extension-task-item-host .texto-task-fold-chevron:hover, -.texto-extension-task-item-host.is-folded .texto-task-fold-chevron { +/* The :not(.is-grabbed)/:not(.is-dragging) exclusions let the + hold-grab/drag hide rules in styles/drag-handle.css (imported before + this file) win without !important — while grabbing, Chrome latches + :hover and the folded state stays on. */ +.texto-extension-task-item-host:hover .texto-task-fold-chevron:not(.is-grabbed):not(.is-dragging), +.texto-extension-task-item-host .texto-task-fold-chevron:hover:not(.is-grabbed):not(.is-dragging), +.texto-extension-task-item-host.is-folded .texto-task-fold-chevron:not(.is-grabbed):not(.is-dragging) { opacity: 1; } /* Drag-handle hover from the gutter: the strip left of the item belongs to it (see dragHandlePlugin.ts) — the plugin force-reveals the chevron (its grab point) via this class, the item's own :hover doesn't reach there. - The hold-grab/drag states hide the chevron (dots handle takes its place; - those rules live in styles/drag-handle.css, imported BEFORE this file — - so the exclusion here is what lets them win). */ + The hold-grab/drag states hide the chevron (dots handle takes its + place; those rules live in styles/drag-handle.css, imported BEFORE this + file — the exclusion here is what lets them win without !important). */ .texto-extension-task-item-host .texto-task-fold-chevron.is-hover:not(.is-grabbed):not(.is-dragging) { opacity: 1; diff --git a/src/texto/extensions/task-list/index.ts b/src/texto/extensions/task-list/index.ts index d5440a5..9da32c8 100644 --- a/src/texto/extensions/task-list/index.ts +++ b/src/texto/extensions/task-list/index.ts @@ -8,5 +8,6 @@ export * from "./task-list"; export default TaskList; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/text-style/index.ts b/src/texto/extensions/text-style/index.ts index 8d495b6..44ad1fc 100644 --- a/src/texto/extensions/text-style/index.ts +++ b/src/texto/extensions/text-style/index.ts @@ -6,5 +6,6 @@ export * from "./text-style"; export default TextStyle; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/underline/index.ts b/src/texto/extensions/underline/index.ts index b39742d..99d7e36 100644 --- a/src/texto/extensions/underline/index.ts +++ b/src/texto/extensions/underline/index.ts @@ -6,5 +6,6 @@ export * from "./underline"; export default Underline; declare global { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/stylelint.config.mjs b/stylelint.config.mjs new file mode 100644 index 0000000..4938595 --- /dev/null +++ b/stylelint.config.mjs @@ -0,0 +1,24 @@ +// Mirrors the Obsidian community-review CSS lint so `npm run lint:css` +// shows locally the same warnings the review bot reports. +export default { + extends: [], + plugins: ["stylelint-no-unsupported-browser-features"], + // The review reports CSS findings as warnings; keep the same severity so + // `npm run lint:css -- --max-warnings 0` gates the way the eslint gate does. + defaultSeverity: "warning", + rules: { + "declaration-no-important": true, + "declaration-block-no-duplicate-properties": true, + "plugin/no-unsupported-browser-features": [ + true, + { + browsers: ["Chrome >= 124"], + // The review reports these as per-entry warnings; keep the default + // "severity" behavior so each unsupported feature usage is reported. + ignore: [], + ignorePartialSupport: false, + }, + ], + }, + ignoreFiles: ["node_modules/**", "dist/**", "dist-harness/**", ".harness/**"], +}; From 4364f9c83a65c0cbd6c41f205683bbc41364669c Mon Sep 17 00:00:00 2001 From: sumbad Date: Tue, 20 Oct 2026 23:50:55 +0800 Subject: [PATCH 2/8] fix: address fresh-review findings (gap row-gap drift, separator important, dedup hardening) - note-property gap -> two-value (row-gap 0): the same node is a flex row for list values; single-value gap changed wrap spacing - img.ProseMirror-separator: !important back under a documented stylelint-disable (must beat unknown user-theme img rules, per PM's recommendation) - generate-note-css.mjs: importance-aware dedup key, url()-blocks and quote/brace crossings fail fast instead of mis-splitting - review nits: attrCode JSDoc placement, keyframes-name-pattern disable description, loadSettings comment wording, .inscriptum-log.jsonl name, dangling test title --- scripts/generate-note-css.mjs | 50 +++++++++++++------ src/main.ts | 6 +-- src/styles/texto-editor.css | 20 +++++--- src/texto/extensions/mermaid/mermaid.ts | 14 +++--- src/texto/extensions/note-property/style.css | 6 ++- .../extensions/table/bordersCommands.test.ts | 2 +- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/scripts/generate-note-css.mjs b/scripts/generate-note-css.mjs index b30462c..082765a 100644 --- a/scripts/generate-note-css.mjs +++ b/scripts/generate-note-css.mjs @@ -70,32 +70,54 @@ function extractParts(current) { } /** Removes duplicate property declarations inside each block, keeping the - * LAST occurrence (cascade-equivalent: the last declaration wins), and - * drops legacy IE star-hack declarations (*prop — invalid in every modern - * browser and flagged as duplicates by the stylelint gate). Custom - * properties participate too — a repeat in the same block is always - * overridden by the later one. Blocks whose text contains quotes with - * semicolons are skipped (conservative guard). */ + * LAST occurrence (cascade-equivalent: the last declaration wins — an + * `!important` repeat only loses to a later `!important` of the same + * property, so importance is part of the dedup key), and drops legacy IE + * star-hack declarations (*prop — invalid in every modern browser and + * flagged as duplicates by the stylelint gate). Conservative guards: any + * block containing `url(` is left untouched (a `;` inside an unquoted + * data URI would break naive splitting), and the whole run fails when + * quotes and braces interleave in a way the splitter cannot see through — + * the maintainer then dedups that upstream change by hand. */ function dedupProperties(css) { + for (let i = 0; i < css.length; i++) { + const ch = css[i]; + if (ch === '"' || ch === "'") { + const close = css.indexOf(ch, i + 1); + if (close < 0) throw new Error("unbalanced quote in deployed css"); + const inner = css.slice(i + 1, close); + if (/[{}]/.test(inner)) { + throw new Error( + "brace inside a quoted css value — dedup splitter cannot parse this safely; handle the upstream change manually", + ); + } + i = close; + } + } let removed = 0; const out = css.replace(/\{[^{}]*\}/g, (block) => { - if (/("[^"]*;[^"]*")|('[^']*;[^']*')/.test(block)) return block; + if (block.includes("url(")) return block; const inner = block.slice(1, -1); const decls = inner.split(";"); + const keyOf = (d) => { + const colon = d.indexOf(":"); + if (colon < 0) return null; + const prop = d.slice(0, colon).trim().toLowerCase(); + return prop ? `${prop}!${d.includes("!important") ? 1 : 0}` : null; + }; const lastIndex = new Map(); decls.forEach((d, i) => { - const prop = d.slice(0, d.indexOf(":")).trim().toLowerCase(); - if (prop) lastIndex.set(prop, i); + const key = keyOf(d); + if (key) lastIndex.set(key, i); }); const kept = decls.filter((d, i) => { - const colon = d.indexOf(":"); - if (colon < 0) return d.trim() !== ""; - const prop = d.slice(0, colon).trim().toLowerCase(); - if (prop.startsWith("*")) { + const key = keyOf(d); + if (!key) return d.trim() !== ""; + if (key.startsWith("*")) { removed += 1; return false; } - const isLast = lastIndex.get(prop) === i; + const isLast = lastIndex.get(key) === i; if (!isLast) removed += 1; return isLast; }); diff --git a/src/main.ts b/src/main.ts index 8ded66a..fb90d88 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,7 +28,7 @@ import { exportNoteAsWebsite } from "./export/exportWebsite"; import { exportNotepadAsWebsite } from "./export/exportNotepadWebsite"; interface InscriptumSettings { - /** Append one JSONL line per note save to .inscriptum-write-log.jsonl + /** Append one JSONL line per note save to .inscriptum-log.jsonl * (diagnostics for storage problems). Off by default. */ writeLog: boolean; /** Vault folder for "Export as website" output, one subfolder per note. */ @@ -190,8 +190,8 @@ export default class NotesPlugin extends Plugin { } async loadSettings(): Promise { - // loadData() is `any` by plugin API contract; narrow it before merging so - // a malformed data.json cannot smuggle untyped values into settings. + // loadData() is `any` by plugin API contract; the cast narrows the merge + // type so only the known settings keys are visible to the compiler. const stored = (await this.loadData()) as Partial | null; this.settings = Object.assign({}, DEFAULT_SETTINGS, stored); setWriteLogEnabled(this.settings.writeLog); diff --git a/src/styles/texto-editor.css b/src/styles/texto-editor.css index 4fdf8db..e94e8b9 100644 --- a/src/styles/texto-editor.css +++ b/src/styles/texto-editor.css @@ -27,15 +27,19 @@ } /* ProseMirror's block-separator hack (prosemirror-view inserts 1px imgs - between blocks for drop targets). No !important: nothing in the plugin - styles or Obsidian's app.css out-specifics the class selector. */ + between blocks for drop targets). !important is kept deliberately, as in + ProseMirror's own recommended style: arbitrary user themes/snippets style + `img` with higher specificity, and a visible separator would corrupt the + editor layout. */ +/* stylelint-disable declaration-no-important -- must beat unknown user-theme img rules, same as prosemirror-view's own recommendation for this block */ img.ProseMirror-separator { - display: inline; - border: none; - margin: 0; - width: 1px; - height: 1px; + display: inline !important; + border: none !important; + margin: 0 !important; + width: 1px !important; + height: 1px !important; } +/* stylelint-enable declaration-no-important */ .ProseMirror-gapcursor { display: none; @@ -54,7 +58,7 @@ img.ProseMirror-separator { animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite; } -/* stylelint-disable-next-line keyframes-name-pattern */ +/* stylelint-disable-next-line keyframes-name-pattern -- ProseMirror's own keyframe name; the animation is referenced by prosemirror-view and renaming it would break the gapcursor blink */ @keyframes ProseMirror-cursor-blink { to { visibility: hidden; diff --git a/src/texto/extensions/mermaid/mermaid.ts b/src/texto/extensions/mermaid/mermaid.ts index d6954da..5934556 100644 --- a/src/texto/extensions/mermaid/mermaid.ts +++ b/src/texto/extensions/mermaid/mermaid.ts @@ -86,13 +86,6 @@ export function mermaidLightboxTarget( /** Applies the layout class to the host (left = default, no class) — * the NodeView dom does not receive rendered attrs automatically. */ -/** PM node attrs are untyped (`Record`); read the diagram - * source through this guard so `any` cannot leak into render/edit state. */ -function attrCode(attrs: unknown): string { - const code = (attrs as { code?: unknown } | null)?.code; - return typeof code === "string" ? code : ""; -} - function syncLayoutClass(element: HTMLElement, align: unknown): void { const layout = isMermaidLayout(align) ? align : "left"; for (const value of MERMAID_LAYOUTS) { @@ -102,6 +95,13 @@ function syncLayoutClass(element: HTMLElement, align: unknown): void { } } +/** PM node attrs are untyped (`Record`); read the diagram + * source through this guard so `any` cannot leak into render/edit state. */ +function attrCode(attrs: unknown): string { + const code = (attrs as { code?: unknown } | null)?.code; + return typeof code === "string" ? code : ""; +} + /** Debounce of the live parse hint while typing, ms. */ const LIVE_PARSE_DEBOUNCE_MS = 300; diff --git a/src/texto/extensions/note-property/style.css b/src/texto/extensions/note-property/style.css index 6a9df7d..f6f1f74 100644 --- a/src/texto/extensions/note-property/style.css +++ b/src/texto/extensions/note-property/style.css @@ -11,7 +11,9 @@ display: grid; /* Fixed name column — rows align with each other and with the header. */ grid-template-columns: var(--property-name-col) 1fr; - gap: 16px; + /* Two-value gap: the same node is also a flex row for list-type values + (wrapping tag chips) — keep row-gap at the chip margins' spacing. */ + gap: 0 16px; margin: 2px 0; /* Top-aligned, not baseline-aligned: the name field is a scroll container (a long name scrolls inside its column), and scroll @@ -373,7 +375,7 @@ .is-mobile .note-property { --property-name-col: 120px; grid-template-columns: 120px 1fr; - gap: 10px; + gap: 0 10px; } .is-mobile .note-property-zone-rule { diff --git a/src/texto/extensions/table/bordersCommands.test.ts b/src/texto/extensions/table/bordersCommands.test.ts index 3646768..470e99c 100644 --- a/src/texto/extensions/table/bordersCommands.test.ts +++ b/src/texto/extensions/table/bordersCommands.test.ts @@ -334,7 +334,7 @@ describe("table borders commands", () => { }); describe("borders style helpers", () => { - it("bordersToStyle renders per-side declarations with", () => { + it("bordersToStyle renders per-side declarations", () => { expect( bordersToStyle({ top: { style: "solid", width: "2.25pt", color: null }, From d22852bf915549cb54e11d4e8568521d08a62c7a Mon Sep 17 00:00:00 2001 From: sumbad Date: Wed, 21 Oct 2026 00:22:42 +0800 Subject: [PATCH 3/8] docs: Unreleased changelog entries for review cleanup --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff0c64..2d3ce74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- A quieter update than it looks: this round clears every community review warning — stricter typing, no deprecated calls — so future updates stay easy to ship. +- Settings are now findable in Obsidian's settings search (1.13+): look for "Export folder" or "Diagnostic log". +- Code blocks show the slim custom scrollbar they were designed with, instead of the browser's default one. + ## [0.11.0] - 2026-10-16 ### Added From 26c798abbadc55205c3395e514b5e31c4764abd2 Mon Sep 17 00:00:00 2001 From: sumbad Date: Wed, 21 Oct 2026 16:48:47 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20close=20review=20gaps=20=E2=80=94=20?= =?UTF-8?q?harness=20suppressions,=20empty-interface=20parity,=20CI=20gate?= =?UTF-8?q?s,=20settings=20value=20read?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - browser-harness: describe the shim's own createEl/createDiv/createSpan implementation (the rule would flag its own shim) and type the class-list helpers; eslint.config.mts allows exactly obsidianmd/prefer-create-el disables, every other obsidianmd rule stays non-disableable - empty-interface suppressions now name both rule ids (the bot's rule name is not visible to us) and the local config keeps both enabled - main.ts: getControlValue reads the plugin's own settings so the 1.13+ declarative rows render the saved values instead of empty fields - ci.yml: run lint:src (--max-warnings 0) and lint:css on every push - scripts/debug/verify-review-css.mjs: live Chromium check of the CSS refactor (fold hidden, table border cascade incl. the No-borders preset, chevron reveal/hide states, note-property gap, hidden scrollbar) Gates: lint:src 0, lint:css 0, typecheck, 629 tests, build — pass --- .github/workflows/ci.yml | 8 + CHANGELOG.md | 3 +- eslint.config.mts | 13 +- scripts/debug/verify-review-css.mjs | 166 ++++++++++++++++++ src/browser-harness/harness.ts | 16 +- src/main.ts | 11 ++ src/texto/core/extensions/commands.ts | 2 +- src/texto/extensions/attachment/index.ts | 2 +- src/texto/extensions/blockquote/index.ts | 2 +- src/texto/extensions/bold/index.ts | 2 +- src/texto/extensions/bullet-list/index.ts | 2 +- src/texto/extensions/code-block-hljs/index.ts | 2 +- src/texto/extensions/code/index.ts | 2 +- src/texto/extensions/color/index.ts | 2 +- src/texto/extensions/hard-break/index.ts | 2 +- src/texto/extensions/highlight/index.ts | 2 +- src/texto/extensions/history/index.ts | 2 +- src/texto/extensions/horizontal-rule/index.ts | 2 +- src/texto/extensions/image/index.ts | 2 +- src/texto/extensions/italic/index.ts | 2 +- src/texto/extensions/list-keymap/index.ts | 2 +- src/texto/extensions/mermaid/index.ts | 2 +- src/texto/extensions/ordered-list/index.ts | 2 +- src/texto/extensions/paragraph/index.ts | 2 +- src/texto/extensions/strike/index.ts | 2 +- src/texto/extensions/table/index.ts | 2 +- .../extensions/task-item-folding/index.ts | 2 +- src/texto/extensions/task-list/index.ts | 2 +- src/texto/extensions/text-style/index.ts | 2 +- src/texto/extensions/underline/index.ts | 2 +- 30 files changed, 233 insertions(+), 32 deletions(-) create mode 100644 scripts/debug/verify-review-css.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b1131a..8bf2e10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,14 @@ jobs: - name: Lint run: npm run lint + # Strict gates for the shipped code (no tests, no dev harness): + # zero warnings, mirroring the Obsidian community review. + - name: Lint shipped sources + run: npm run lint:src + + - name: Lint CSS + run: npm run lint:css + - name: Unit tests run: npm test diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3ce74..bba4897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - A quieter update than it looks: this round clears every community review warning — stricter typing, no deprecated calls — so future updates stay easy to ship. -- Settings are now findable in Obsidian's settings search (1.13+): look for "Export folder" or "Diagnostic log". -- Code blocks show the slim custom scrollbar they were designed with, instead of the browser's default one. +- Settings are now findable in Obsidian's settings search (1.13+): look for "Export folder" or "Diagnostic log" — both show your saved values right away. ## [0.11.0] - 2026-10-16 diff --git a/eslint.config.mts b/eslint.config.mts index f5105ed..a82adf1 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -70,6 +70,12 @@ export default defineConfig( "eslint-comments/no-restricted-disable": [ "error", "obsidianmd/*", + // …except prefer-create-el: src/browser-harness/harness.ts IS the + // implementation of Obsidian's createEl/createDiv/createSpan, so the + // rule would flag its own implementation. The shim suppresses the + // rule locally with a described disable; every other obsidianmd rule + // stays non-disableable. + "!obsidianmd/prefer-create-el", "no-console", "no-restricted-globals", "@typescript-eslint/no-restricted-imports", @@ -90,9 +96,10 @@ export default defineConfig( "@typescript-eslint/no-unnecessary-type-assertion": "warn", // No option overrides: the review bot sees empty interfaces (the // `declare global { interface Commands extends … {} }` merging points - // cannot be type aliases), so the local gate must see them too. - "@typescript-eslint/no-empty-object-type": "warn", - "@typescript-eslint/no-empty-interface": "off", + // cannot be type aliases), so the local gate must see them too. Both + // rule names stay on — v8 merged no-empty-interface into + // no-empty-object-type, and the bot's naming is not visible to us; + // inline suppressions name both so they match either report. "@typescript-eslint/no-redundant-type-constituents": "warn", "@typescript-eslint/no-this-alias": "warn", "@typescript-eslint/no-unsafe-function-type": "warn", diff --git a/scripts/debug/verify-review-css.mjs b/scripts/debug/verify-review-css.mjs new file mode 100644 index 0000000..909c6df --- /dev/null +++ b/scripts/debug/verify-review-css.mjs @@ -0,0 +1,166 @@ +/** + * Live verification of the community-review CSS refactor (task + * obsidian-review-cleanup) in a REAL Chromium via the repo's browser + * harness (action-browser-harness): the harness page loads the actual + * editor bundle and the actual editor.css chain. + * + * Checks: + * A. folded body is hidden (.note .ProseMirror .texto-folded-content) + * B. table cell border cascade: plugin default -> inline cell border -> + * "No borders" preset (data-borders="none") + inline cell border + * C. fold chevron: hover reveals it (opacity 1) -> hold-grab (.is-grabbed) + * hides it again (opacity 0) — the rule the !important removal touched + * D. note-property gap: column-gap 16px, row-gap 0 + * E. scrollbar hiding: no classic scrollbar space on the name column + */ +import { chromium } from "playwright-core"; + +const URL = process.env.HARNESS_URL ?? "http://localhost:8791/.harness/index.html"; +const SHOTS = process.env.SHOT_DIR ?? "/tmp/rev-shots"; + +const browser = await chromium.launch(); +const page = await browser.newPage({ viewport: { width: 900, height: 900 } }); +const errors = []; +page.on("pageerror", (e) => errors.push(String(e))); +page.on("console", (m) => { + if (m.type() === "error") errors.push(`console: ${m.text()}`); +}); + +await page.goto(URL, { waitUntil: "load" }); +await page.waitForFunction(() => window.__harness != null, null, { timeout: 20000 }); +await page.waitForTimeout(400); + +const out = {}; + +// ── A. folded body hidden ─────────────────────────────────────────────── +out.A_fold = await page.evaluate(() => { + const h = window.__harness; + const headingPos = h.positions.find((p) => { + const node = h.view.state.doc.nodeAt(p); + return node != null && node.type.name === "heading"; + }); + h.foldHeading(headingPos); + const folded = document.querySelector(".note .ProseMirror .texto-folded-content"); + return { + foldedNodeFound: folded != null, + display: folded == null ? null : getComputedStyle(folded).display, + height: folded == null ? null : folded.getBoundingClientRect().height, + }; +}); + +// ── B. table cell border cascade ──────────────────────────────────────── +out.B_table = await page.evaluate(() => { + const host = document.createElement("div"); + host.className = "note"; + const pm = document.createElement("div"); + pm.className = "ProseMirror"; + const wrap = document.createElement("div"); + wrap.className = "table-wrapper"; + const table = document.createElement("table"); + const tbody = document.createElement("tbody"); + const tr = document.createElement("tr"); + const td = document.createElement("td"); + td.textContent = "cell"; + tr.appendChild(td); + tbody.appendChild(tr); + table.appendChild(tbody); + wrap.appendChild(table); + pm.appendChild(wrap); + host.appendChild(pm); + document.body.appendChild(host); + + const read = () => { + const cs = getComputedStyle(td); + return { + borderTop: cs.borderTopWidth + " " + cs.borderTopStyle, + borderBottom: cs.borderBottomWidth + " " + cs.borderBottomStyle, + }; + }; + + const pluginDefault = read(); + + // per-cell inline border (what the `borders` attr renders) + td.style.borderTop = "1.5pt solid #b3a3f7"; + const withInline = read(); + + // "No borders" preset: the stylesheet suppresses the grid for every cell + table.setAttribute("data-borders", "none"); + const presetNoBorder = read(); + + return { pluginDefault, withInline, presetNoBorder }; +}); + +// ── C. fold chevron: hover reveals, hold-grab hides ───────────────────── +const heading = page.locator(".note .ProseMirror h1").first(); +await heading.hover(); +await page.waitForTimeout(150); +out.C_chevron = await page.evaluate(() => { + const host = document.querySelector(".note .texto-heading-fold-chevron-host"); + if (host == null) return { found: false }; + const before = getComputedStyle(host).opacity; + // the plugin adds `is-grabbed` on the chevron host for the hold-grab state + host.classList.add("is-grabbed"); + const grabbed = getComputedStyle(host).opacity; + host.classList.add("is-dragging"); + const dragging = getComputedStyle(host).opacity; + host.classList.remove("is-grabbed", "is-dragging"); + const released = getComputedStyle(host).opacity; + return { found: true, before, grabbed, dragging, released }; +}); + +// task-item chevron (own rule set in task-item/style.css): the plugin +// toggles is-hover / is-grabbed on the chevron element itself — the +// hold-grab state must win over the reveal rule without !important. +out.C_taskChevron = await page.evaluate(() => { + const chevron = document.querySelector( + ".texto-extension-task-item-host .texto-task-fold-chevron", + ); + if (chevron == null) return { found: false }; + const idle = getComputedStyle(chevron).opacity; + chevron.classList.add("is-hover"); + const forceRevealed = getComputedStyle(chevron).opacity; + chevron.classList.add("is-grabbed"); + const grabbed = getComputedStyle(chevron).opacity; + chevron.classList.add("is-dragging"); + const dragging = getComputedStyle(chevron).opacity; + chevron.classList.remove("is-grabbed", "is-dragging", "is-hover"); + return { found: true, idle, forceRevealed, grabbed, dragging }; +}); + +// ProseMirror block separator: the rule keeps !important on purpose +out.E_separator = await page.evaluate(() => { + const img = document.querySelector("img.ProseMirror-separator"); + if (img == null) return { found: false }; + const cs = getComputedStyle(img); + return { found: true, display: cs.display, width: cs.width, height: cs.height }; +}); + +// ── D. note-property gap ──────────────────────────────────────────────── +out.D_notePropertyGap = await page.evaluate(() => { + const host = document.createElement("div"); + host.className = "note"; + const pm = document.createElement("div"); + pm.className = "ProseMirror"; + const prop = document.createElement("div"); + prop.className = "note-property"; + const name = document.createElement("div"); + name.className = "note-property-name"; + name.textContent = "a very long property name that must scroll inside its column"; + prop.appendChild(name); + pm.appendChild(prop); + host.appendChild(pm); + document.body.appendChild(host); + const cs = getComputedStyle(prop); + // classic scrollbars take space in Chromium: hidden scrollbar => no gap + const scrollbarSpace = name.offsetWidth - name.clientWidth; + return { + columnGap: cs.columnGap, + rowGap: cs.rowGap, + nameScrollbarSpace: scrollbarSpace, + }; +}); + +await page.screenshot({ path: `${SHOTS}/harness-editor.png`, fullPage: false }); + +console.log(JSON.stringify({ out, errors }, null, 2)); +await browser.close(); diff --git a/src/browser-harness/harness.ts b/src/browser-harness/harness.ts index a737246..fed0423 100644 --- a/src/browser-harness/harness.ts +++ b/src/browser-harness/harness.ts @@ -53,6 +53,7 @@ const g = window as unknown as Record; // Obsidian semantics: createDiv/createSpan take the CLASS as a bare string // (or a DomElementInfo object) — a string is never the text content. // createEl(tag, string) is the text form; createEl(tag, {...}) the info one. +/* eslint-disable obsidianmd/prefer-create-el -- this file IS the Obsidian DOM-helper shim: createEl/createDiv/createSpan/createFragment are IMPLEMENTED here, so the rule would flag its own implementation (there is no helper to call yet) */ g.createDiv = (attrs?: DomAttrs | string) => { const div = document.createElement("div"); if (typeof attrs === "string") div.className = attrs; @@ -68,6 +69,7 @@ g.createSpan = (attrs?: DomAttrs | string) => { g.createEl = (tag: string, attrs?: DomAttrs | string) => applyAttrs(document.createElement(tag), attrs); g.createFragment = () => document.createDocumentFragment(); +/* eslint-enable obsidianmd/prefer-create-el -- end of the global shim block (the rule stays disabled only while the shim's own helpers are defined) */ // NOTE: do NOT assign window.requestAnimationFrame — `g` IS window, and // overwriting the native function would recurse forever (bundle code calls // the bare `requestAnimationFrame` global). The native one already IS the @@ -97,21 +99,27 @@ const toStr = (value: unknown): string => : JSON.stringify(value); defineHelper("addClass", (el, [classes]) => { // Obsidian's addClass accepts a string (ONE class) or an array. - const list = Array.isArray(classes) ? classes : [classes as string]; + const list: string[] = Array.isArray(classes) + ? (classes as string[]) + : [classes as string]; for (const cls of list) el.classList.add(cls); }); defineHelper("addClasses", (el, [classes]) => { for (const list of classes as string[][]) el.classList.add(...list); }); defineHelper("removeClass", (el, [classes]) => { - const list = Array.isArray(classes) ? classes : [classes as string]; + const list: string[] = Array.isArray(classes) + ? (classes as string[]) + : [classes as string]; for (const cls of list) el.classList.remove(cls); }); defineHelper("removeClasses", (el, [classes]) => { for (const list of classes as string[][]) el.classList.remove(...list); }); defineHelper("toggleClass", (el, [classes, value]) => { - const list = Array.isArray(classes) ? classes : [classes as string]; + const list: string[] = Array.isArray(classes) + ? (classes as string[]) + : [classes as string]; for (const cls of list) el.classList.toggle(cls, Boolean(value)); }); defineHelper("hasClass", (el, [cls]) => el.classList.contains(cls as string)); @@ -138,6 +146,7 @@ defineHelper("setCssProps", (el, [props]) => { defineHelper("empty", (el) => { while (el.firstChild) el.removeChild(el.firstChild); }); +/* eslint-disable obsidianmd/prefer-create-el -- the prototype helpers below ARE Obsidian's createEl/createDiv/createSpan: they must call document.createElement to build the element they hand back */ defineHelper("createDiv", (el, [cls]) => { const div = document.createElement("div"); // Obsidian semantics: createDiv("name") sets the CLASS, createDiv({...}) @@ -160,6 +169,7 @@ defineHelper("createEl", (el, [tag, attrs]) => { el.appendChild(out); return out; }); +/* eslint-enable obsidianmd/prefer-create-el -- end of the prototype-helper block (the rule stays disabled only while the shim's own helpers are defined) */ defineHelper("setText", (el, [text]) => { el.textContent = toStr(text); return el; diff --git a/src/main.ts b/src/main.ts index fb90d88..9434af2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -548,6 +548,17 @@ class InscriptumSettingTab extends PluginSettingTab { ]; } + /** Read the current value for a control key (Obsidian 1.13.0+ calls this + * on every render of a `control`-type definition). The base class reads + * the app's own config storage; the plugin keeps its settings in + * `plugin.settings` (defaults merged with the stored data), so read them + * from there — otherwise the field would render empty and the next save + * would write the default over the user's value. */ + getControlValue(key: string): unknown { + const settings = this.plugin.settings as unknown as Record; + return settings[key]; + } + async setControlValue(key: string, value: unknown): Promise { // Same normalize-on-write semantics the imperative display() applies. if (key === "exportFolder") { diff --git a/src/texto/core/extensions/commands.ts b/src/texto/core/extensions/commands.ts index 865c63b..7901b46 100644 --- a/src/texto/core/extensions/commands.ts +++ b/src/texto/core/extensions/commands.ts @@ -18,6 +18,6 @@ type CoreCommandsSet = { }; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CoreCommandsSet {} } diff --git a/src/texto/extensions/attachment/index.ts b/src/texto/extensions/attachment/index.ts index 266a4f4..99e382a 100644 --- a/src/texto/extensions/attachment/index.ts +++ b/src/texto/extensions/attachment/index.ts @@ -8,7 +8,7 @@ export * from "./attachment"; export default Attachment; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/blockquote/index.ts b/src/texto/extensions/blockquote/index.ts index a1ff1ab..d07d6c7 100644 --- a/src/texto/extensions/blockquote/index.ts +++ b/src/texto/extensions/blockquote/index.ts @@ -6,6 +6,6 @@ export * from "./blockquote"; export default Blockquote; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/bold/index.ts b/src/texto/extensions/bold/index.ts index de367b6..e82f28e 100644 --- a/src/texto/extensions/bold/index.ts +++ b/src/texto/extensions/bold/index.ts @@ -6,6 +6,6 @@ export * from "./bold"; export default Bold; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/bullet-list/index.ts b/src/texto/extensions/bullet-list/index.ts index 2238922..a4f3559 100644 --- a/src/texto/extensions/bullet-list/index.ts +++ b/src/texto/extensions/bullet-list/index.ts @@ -7,6 +7,6 @@ export * from "./bullet-list"; export default BulletList; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/code-block-hljs/index.ts b/src/texto/extensions/code-block-hljs/index.ts index ee7ae27..f0ee809 100644 --- a/src/texto/extensions/code-block-hljs/index.ts +++ b/src/texto/extensions/code-block-hljs/index.ts @@ -7,6 +7,6 @@ export * from "./hljsCodeBlockRow"; export * from "./hljsMark"; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/code/index.ts b/src/texto/extensions/code/index.ts index 9394637..096642b 100644 --- a/src/texto/extensions/code/index.ts +++ b/src/texto/extensions/code/index.ts @@ -6,6 +6,6 @@ export * from "./code"; export default Code; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/color/index.ts b/src/texto/extensions/color/index.ts index 2ff3bae..b6aad94 100644 --- a/src/texto/extensions/color/index.ts +++ b/src/texto/extensions/color/index.ts @@ -6,6 +6,6 @@ export * from "./color"; export default Color; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/hard-break/index.ts b/src/texto/extensions/hard-break/index.ts index 2cab36c..07d1f9f 100644 --- a/src/texto/extensions/hard-break/index.ts +++ b/src/texto/extensions/hard-break/index.ts @@ -6,6 +6,6 @@ export * from "./hard-break"; export default HardBreak; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/highlight/index.ts b/src/texto/extensions/highlight/index.ts index cc05e1d..1c34ec9 100644 --- a/src/texto/extensions/highlight/index.ts +++ b/src/texto/extensions/highlight/index.ts @@ -6,6 +6,6 @@ export * from "./highlight"; export default Highlight; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/history/index.ts b/src/texto/extensions/history/index.ts index 5a3cf95..60f7a37 100644 --- a/src/texto/extensions/history/index.ts +++ b/src/texto/extensions/history/index.ts @@ -6,6 +6,6 @@ export * from "./history"; export default History; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/horizontal-rule/index.ts b/src/texto/extensions/horizontal-rule/index.ts index 428ca4a..0fdf6a8 100644 --- a/src/texto/extensions/horizontal-rule/index.ts +++ b/src/texto/extensions/horizontal-rule/index.ts @@ -6,6 +6,6 @@ export * from "./horizontal-rule"; export default HorizontalRule; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/image/index.ts b/src/texto/extensions/image/index.ts index 3d2e147..53d08da 100644 --- a/src/texto/extensions/image/index.ts +++ b/src/texto/extensions/image/index.ts @@ -8,6 +8,6 @@ export * from "./image"; export default Image; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/italic/index.ts b/src/texto/extensions/italic/index.ts index 9afde7e..453d6e8 100644 --- a/src/texto/extensions/italic/index.ts +++ b/src/texto/extensions/italic/index.ts @@ -6,6 +6,6 @@ export * from "./italic"; export default Italic; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/list-keymap/index.ts b/src/texto/extensions/list-keymap/index.ts index b85be6e..a32c425 100644 --- a/src/texto/extensions/list-keymap/index.ts +++ b/src/texto/extensions/list-keymap/index.ts @@ -9,6 +9,6 @@ export * as listHelpers from "./helpers/index"; export default ListKeymap; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/mermaid/index.ts b/src/texto/extensions/mermaid/index.ts index cafb0ea..ee225d6 100644 --- a/src/texto/extensions/mermaid/index.ts +++ b/src/texto/extensions/mermaid/index.ts @@ -8,6 +8,6 @@ export * from "./mermaid"; export default Mermaid; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/ordered-list/index.ts b/src/texto/extensions/ordered-list/index.ts index 783c584..ef8e207 100644 --- a/src/texto/extensions/ordered-list/index.ts +++ b/src/texto/extensions/ordered-list/index.ts @@ -7,6 +7,6 @@ export * from "./ordered-list"; export default OrderedList; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/paragraph/index.ts b/src/texto/extensions/paragraph/index.ts index 8963ae3..dc54e93 100644 --- a/src/texto/extensions/paragraph/index.ts +++ b/src/texto/extensions/paragraph/index.ts @@ -6,6 +6,6 @@ export * from "./paragraph"; export default Paragraph; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/strike/index.ts b/src/texto/extensions/strike/index.ts index c66825b..88f0643 100644 --- a/src/texto/extensions/strike/index.ts +++ b/src/texto/extensions/strike/index.ts @@ -6,6 +6,6 @@ export * from "./strike"; export default Strike; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/table/index.ts b/src/texto/extensions/table/index.ts index c9bdfe4..9e76378 100644 --- a/src/texto/extensions/table/index.ts +++ b/src/texto/extensions/table/index.ts @@ -13,6 +13,6 @@ export * from "./elements/tableCell"; export default Table; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/task-item-folding/index.ts b/src/texto/extensions/task-item-folding/index.ts index d98c460..2ab6256 100644 --- a/src/texto/extensions/task-item-folding/index.ts +++ b/src/texto/extensions/task-item-folding/index.ts @@ -21,6 +21,6 @@ export { // interface (same pattern as extensions/heading/index.ts), so // editor.commands.toggleTaskFold(...) is fully typed. declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/task-list/index.ts b/src/texto/extensions/task-list/index.ts index 9da32c8..648100c 100644 --- a/src/texto/extensions/task-list/index.ts +++ b/src/texto/extensions/task-list/index.ts @@ -8,6 +8,6 @@ export * from "./task-list"; export default TaskList; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/text-style/index.ts b/src/texto/extensions/text-style/index.ts index 44ad1fc..63a1af4 100644 --- a/src/texto/extensions/text-style/index.ts +++ b/src/texto/extensions/text-style/index.ts @@ -6,6 +6,6 @@ export * from "./text-style"; export default TextStyle; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } diff --git a/src/texto/extensions/underline/index.ts b/src/texto/extensions/underline/index.ts index 99d7e36..29157a4 100644 --- a/src/texto/extensions/underline/index.ts +++ b/src/texto/extensions/underline/index.ts @@ -6,6 +6,6 @@ export * from "./underline"; export default Underline; declare global { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface -- declaration-merging augmentation point: global Commands interface gains this extension's commands; a type alias cannot merge into a global interface interface Commands extends CommandsSet> {} } From 5c3a67b9a4e3cd1aadc05ec7bd42b306ae44852e Mon Sep 17 00:00:00 2001 From: sumbad Date: Wed, 21 Oct 2026 16:51:54 +0800 Subject: [PATCH 5/8] fix(harness): addClasses/removeClasses take an array of class names (Obsidian API) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shim iterated the argument as string[][] and spread each element — a multi-character class name was added character by character. --- src/browser-harness/harness.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser-harness/harness.ts b/src/browser-harness/harness.ts index fed0423..8f08c4d 100644 --- a/src/browser-harness/harness.ts +++ b/src/browser-harness/harness.ts @@ -105,7 +105,8 @@ defineHelper("addClass", (el, [classes]) => { for (const cls of list) el.classList.add(cls); }); defineHelper("addClasses", (el, [classes]) => { - for (const list of classes as string[][]) el.classList.add(...list); + // Obsidian's addClasses takes an ARRAY of class names (addClass takes one). + for (const cls of classes as string[]) el.classList.add(cls); }); defineHelper("removeClass", (el, [classes]) => { const list: string[] = Array.isArray(classes) @@ -114,7 +115,7 @@ defineHelper("removeClass", (el, [classes]) => { for (const cls of list) el.classList.remove(cls); }); defineHelper("removeClasses", (el, [classes]) => { - for (const list of classes as string[][]) el.classList.remove(...list); + for (const cls of classes as string[]) el.classList.remove(cls); }); defineHelper("toggleClass", (el, [classes, value]) => { const list: string[] = Array.isArray(classes) From bbdfdfba9ff14501a455dc7a58c1ec0a4fdffa50 Mon Sep 17 00:00:00 2001 From: sumbad Date: Wed, 21 Oct 2026 18:18:44 +0800 Subject: [PATCH 6/8] =?UTF-8?q?chore(deps):=20close=20Dependabot=20advisor?= =?UTF-8?q?ies=20=E2=80=94=20prosemirror-view=201.42.6,=20mermaid=2012.2.0?= =?UTF-8?q?,=20vite=208.3.5,=20obsidian=201.14.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prosemirror-view 1.42.2 -> 1.42.6: XSS in paste handling (GHSA-c8x8-7fp4-3x9w) — this library ships inside main.js, so the advisory was user-facing - prosemirror-model/-commands/-history/-dropcursor/-transform/-schema-basic: patch bumps - mermaid 11.17.2 -> 12.2.0: the published mermaid chunk carries katex 0.16.47 (GHSA-238p-pmpm-9mq7); 12.2.0 carries katex 0.18.11. An npm override would only silence the audit, not the bundled code - vite 8.2.0 -> 8.3.5 (dev): three launch-editor advisories on Windows - obsidian 1.13.1 -> 1.14.4 + override (eslint-plugin-obsidianmd pins 1.12.3 -> moment 2.29.4, GHSA-4p3w-j4w9-5jqw) - eslint-plugin-obsidianmd 0.4.1 -> 0.4.2 Residual: braces 3.0.3 (CVE-2026-93687) via stylelint -> micromatch -> braces. Dev-only, not shipped, no upstream patch — dismiss with rationale. Bundle: main.js 4,359 kB -> 6,091 kB (gzip 1,275 -> 1,800 kB); the delta is mermaid 12's new ELK layout chunk. Gates: lint:src 0, lint:css 0, typecheck, 629 tests, build — pass Live: mermaid renders on desktop (1.14.4) and Android, pasted markup is sanitized (no onerror/script), editor unaffected --- CHANGELOG.md | 5 + package-lock.json | 516 +++++++++++++++++++++------------------------- package.json | 25 +-- 3 files changed, 258 insertions(+), 288 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bba4897..506ef39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A quieter update than it looks: this round clears every community review warning — stricter typing, no deprecated calls — so future updates stay easy to ship. - Settings are now findable in Obsidian's settings search (1.13+): look for "Export folder" or "Diagnostic log" — both show your saved values right away. +### Security + +- Pasting is safer: a crafted snippet pasted into a note can no longer smuggle in active markup. +- Diagrams are rendered by a newer Mermaid engine, which also updates the maths renderer inside it. + ## [0.11.0] - 2026-10-16 ### Added diff --git a/package-lock.json b/package-lock.json index 5d04737..d589e6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,19 +16,19 @@ "highlight.js": "11.11.1", "linkifyjs": "4.3.3", "lit-html": "2.8.0", - "mermaid": "^11.17.2", - "prosemirror-commands": "1.7.1", - "prosemirror-dropcursor": "1.8.3", + "mermaid": "^12.2.0", + "prosemirror-commands": "1.7.2", + "prosemirror-dropcursor": "1.8.4", "prosemirror-gapcursor": "1.4.1", - "prosemirror-history": "1.5.0", + "prosemirror-history": "1.5.1", "prosemirror-keymap": "1.2.3", - "prosemirror-model": "1.25.11", - "prosemirror-schema-basic": "1.2.4", + "prosemirror-model": "1.25.12", + "prosemirror-schema-basic": "1.2.5", "prosemirror-schema-list": "1.5.1", "prosemirror-state": "1.4.4", "prosemirror-tables": "1.8.5", - "prosemirror-transform": "1.12.0", - "prosemirror-view": "1.42.2", + "prosemirror-transform": "1.12.2", + "prosemirror-view": "1.42.6", "tippy.js": "6.3.7", "type-fest": "5.8.0" }, @@ -42,18 +42,18 @@ "@types/node": "26.1.2", "babel-plugin-transform-jsx-to-tt": "0.5.0", "eslint": "9.39.5", - "eslint-plugin-obsidianmd": "0.4.1", + "eslint-plugin-obsidianmd": "0.4.2", "globals": "17.9.0", "jiti": "2.7.0", "jsdom": "30.0.1", "lightningcss": "1.33.0", - "obsidian": "1.13.1", + "obsidian": "1.14.4", "playwright-core": "1.63.0", "stylelint": "^16.26.1", "stylelint-no-unsupported-browser-features": "^8.1.2", "typescript": "5.9.3", "typescript-eslint": "8.66.0", - "vite": "8.2.0", + "vite": "8.3.5", "vitest": "^4.1.11" } }, @@ -850,16 +850,47 @@ "@keyv/serialize": "^1.1.1" } }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-13.2.0.tgz", + "integrity": "sha512-bA9dvWlhAUcHkNwU+8w9yiUxJPlj/Lm3VqS7MD+NbIR/7LmpNLuK/AWNFdCo/T3SK5ND0/UHf3IJvl/Jn/i6yg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "13.2.0", + "@chevrotain/types": "13.2.0" + } + }, + "node_modules/@chevrotain/gast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-13.2.0.tgz", + "integrity": "sha512-bCARdAsG+eEbOveSXcOAC7pVxA04I1ionzeMOwIYf5NX7SaS7zgn4Dgug2BSqoL+iyKeMNSorQjsFSHJHsCNwg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "13.2.0" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-13.2.0.tgz", + "integrity": "sha512-kxpUkLtiaK09ph+dib+Yvm+3sWSnuwlo25gqfKRgTPNmF6H7uW4gVkMjaRdtnN783S9sM6vUNWh9CsxYX4QwKw==", + "license": "Apache-2.0" + }, "node_modules/@chevrotain/types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", - "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-13.2.0.tgz", + "integrity": "sha512-7qrYHRlKfWf2AvBfJi5eVOrKAkbxvMFsy/iIHTGIiApNcEecCaVIdR+mIQhyCBsTDJxBz4JWXHMo6krbhR9hVg==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-13.2.0.tgz", + "integrity": "sha512-SzKYY68rseOrb9KILVzPsl6SjkQtdWw01SRGB5aCVI5kFcHltQsOYV3JB0Ffst72DgkvHCyDQ0NLwxtmCCf97Q==", "license": "Apache-2.0" }, "node_modules/@codemirror/state": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", - "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", "dev": true, "license": "MIT", "peer": true, @@ -868,14 +899,14 @@ } }, "node_modules/@codemirror/view": { - "version": "6.38.6", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", - "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", + "version": "6.43.13", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.13.tgz", + "integrity": "sha512-sihaFrUzAsYBQsL9J2t69y8nfMQGwcYmggAZsk+kjPbjYZMyuf2hU8tUNTZ+P+isb6XRr8JE22TZlJxBoVdH1A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@codemirror/state": "^6.5.0", + "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" @@ -1055,40 +1086,6 @@ "url": "https://github.com/sponsors/JounQin" } }, - "node_modules/@emnapi/core": { - "version": "2.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", - "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "2.0.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "2.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", - "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", - "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-plugin-eslint-comments": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.7.2.tgz", @@ -1461,20 +1458,23 @@ "license": "MIT" }, "node_modules/@marijn/find-cluster-break": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", - "integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", "dev": true, "license": "MIT", "peer": true }, "node_modules/@mermaid-js/parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz", - "integrity": "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-2.0.1.tgz", + "integrity": "sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA==", "license": "MIT", "dependencies": { - "@chevrotain/types": "~11.1.2" + "@chevrotain/types": "~13.2.0" + }, + "engines": { + "node": ">=22.12.0" } }, "node_modules/@microsoft/eslint-plugin-sdl": { @@ -1515,28 +1515,6 @@ "ret": "~0.1.10" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", - "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1576,13 +1554,13 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", - "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", + "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@pkgr/core": { @@ -1624,10 +1602,27 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", + "integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", - "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz", + "integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==", "cpu": [ "arm64" ], @@ -1642,9 +1637,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", - "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz", + "integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==", "cpu": [ "arm64" ], @@ -1659,9 +1654,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", - "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz", + "integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==", "cpu": [ "x64" ], @@ -1676,9 +1671,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", - "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz", + "integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==", "cpu": [ "x64" ], @@ -1693,9 +1688,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", - "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz", + "integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==", "cpu": [ "arm" ], @@ -1710,9 +1705,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", - "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz", + "integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==", "cpu": [ "arm64" ], @@ -1730,9 +1725,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", - "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz", + "integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==", "cpu": [ "arm64" ], @@ -1750,9 +1745,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", - "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz", + "integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==", "cpu": [ "ppc64" ], @@ -1770,9 +1765,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", - "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz", + "integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==", "cpu": [ "s390x" ], @@ -1790,9 +1785,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", - "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz", + "integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==", "cpu": [ "x64" ], @@ -1810,9 +1805,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", - "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz", + "integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==", "cpu": [ "x64" ], @@ -1830,9 +1825,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", - "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz", + "integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==", "cpu": [ "arm64" ], @@ -1846,26 +1841,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", - "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "2.0.0-alpha.3", - "@emnapi/runtime": "2.0.0-alpha.3", - "@napi-rs/wasm-runtime": "^1.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", - "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz", + "integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==", "cpu": [ "arm64" ], @@ -1880,9 +1859,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", - "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", + "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", "cpu": [ "x64" ], @@ -1917,17 +1896,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -3267,6 +3235,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chevrotain": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-13.2.0.tgz", + "integrity": "sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "13.2.0", + "@chevrotain/gast": "13.2.0", + "@chevrotain/regexp-to-ast": "13.2.0", + "@chevrotain/types": "13.2.0", + "@chevrotain/utils": "13.2.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4251,6 +4235,12 @@ "dev": true, "license": "ISC" }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "license": "EPL-2.0" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4964,9 +4954,9 @@ } }, "node_modules/eslint-plugin-obsidianmd": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.4.1.tgz", - "integrity": "sha512-Nv3593kVsFOS8kir/HWaJ5CR3xcyiPWEPyXst5Pib8mxRYYuLYPpJS2ALMoZXHulHyiGwoYW8AaxvLRc4rhrRg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.4.2.tgz", + "integrity": "sha512-NFp6wsRSvN0TU6EPi03uF2UQ3GuYyVtzjKDl25KyP8Bnz8gM0unX7R/e+VQ2JtMW2/3DC/VwIXjHnzAhB9JQwg==", "dev": true, "license": "MIT", "dependencies": { @@ -5028,21 +5018,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-obsidianmd/node_modules/obsidian": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", - "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/codemirror": "5.60.8", - "moment": "2.29.4" - }, - "peerDependencies": { - "@codemirror/state": "6.5.0", - "@codemirror/view": "6.38.6" - } - }, "node_modules/eslint-plugin-obsidianmd/node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -5302,15 +5277,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fastdom": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz", - "integrity": "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==", - "license": "MIT", - "dependencies": { - "strictdom": "^1.0.1" - } - }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -6711,28 +6677,28 @@ } }, "node_modules/katex": { - "version": "0.16.47", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", - "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.10.tgz", + "integrity": "sha512-/B6p9eY9DX7aHBfpkHdpirDTZ5QH9xTwL9y837PWuzuv41O5jFdqNQwVQEQsimY0/iVNkwBY4+4hMfhQ7d4Dbw==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" ], "license": "MIT", "dependencies": { - "commander": "^8.3.0" + "commander": "^15.0.0" }, "bin": { "katex": "cli.js" } }, "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=22.12.0" } }, "node_modules/keyv": { @@ -7215,16 +7181,17 @@ } }, "node_modules/mermaid": { - "version": "11.17.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz", - "integrity": "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-12.2.0.tgz", + "integrity": "sha512-w7nr/SFrLO7BaVTs3dQTvYSV6nYSBj0Yy9DjBhsIJtAcHqRzXSq4Pg32ujHZ/AdWsAMNEvvhbm5dt9E2tOiHzw==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.2.1", + "@mermaid-js/parser": "^2.0.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", + "chevrotain": "~13.2.0", "cytoscape": "^3.34.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", @@ -7232,16 +7199,19 @@ "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.21", - "dompurify": "^3.3.3", + "dompurify": "^3.4.12", + "elkjs": "^0.9.3", "es-toolkit": "^1.45.1", - "fastdom": "1.0.12", - "katex": "^0.16.47", + "katex": "^0.18.0", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + }, + "engines": { + "node": ">=22.12.0" } }, "node_modules/micromatch": { @@ -7302,9 +7272,9 @@ "license": "MIT" }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz", + "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==", "dev": true, "license": "MIT", "engines": { @@ -7339,9 +7309,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -7527,18 +7497,18 @@ } }, "node_modules/obsidian": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.13.1.tgz", - "integrity": "sha512-qtTEA2pmhJzhuhJqzbBFRYhpIOqvW+krDYjtFynv66KbxBbumHBlsJfWw3I4jtnK/6fZwbQhCrmmDdRwXmX56w==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.14.4.tgz", + "integrity": "sha512-Kyo+5t0wFW2On5irLOPrgtwWDnaJsvE9n/P1OVvdPytx+NqeHJXvxVqADNOqLp9bU1+I64B6i+dHb0fxR/ss3w==", "dev": true, "license": "MIT", "dependencies": { "@types/codemirror": "5.60.8", - "moment": "2.29.4" + "moment": "2.31.0" }, "peerDependencies": { - "@codemirror/state": "6.5.0", - "@codemirror/view": "6.38.6" + "@codemirror/state": "6.7.6", + "@codemirror/view": "6.43.13" } }, "node_modules/obug": { @@ -7749,9 +7719,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -7817,9 +7787,9 @@ } }, "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -7837,9 +7807,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -7923,9 +7893,9 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", - "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz", + "integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", @@ -7934,9 +7904,9 @@ } }, "node_modules/prosemirror-dropcursor": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", - "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.4.tgz", + "integrity": "sha512-VmKxhcA6a+h5oPSYmfF1ZNeQqPbyYaIxqZsKqeYCyTlPtrjUgNaHL3P8vVaO19oqAV0zuaM/axkowZHrB1P3Tg==", "license": "MIT", "dependencies": { "prosemirror-state": "^1.0.0", @@ -7957,9 +7927,9 @@ } }, "node_modules/prosemirror-history": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", - "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.1.tgz", + "integrity": "sha512-onlwnqKvgtFg6srZOmDOAhlAy1FIHQpOExORybX0S+DvJepE5tPALrB9iGet/2sqqqrelSnB10OoiOJqAvu2DA==", "license": "MIT", "dependencies": { "prosemirror-state": "^1.2.2", @@ -7979,18 +7949,18 @@ } }, "node_modules/prosemirror-model": { - "version": "1.25.11", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", - "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", + "version": "1.25.12", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.12.tgz", + "integrity": "sha512-Ue2gTmXMa7EhpLNhC7J+h4+ykD8ha12K6rrZFFKKJHBForfIStw5gJ6Zrf1mqqAa9NmpmEB4wp9bKmA4eUVjWg==", "license": "MIT", "dependencies": { "orderedmap": "^2.0.0" } }, "node_modules/prosemirror-schema-basic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", - "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.5.tgz", + "integrity": "sha512-McBWhfgOfThyu3Nltbs8SOUbM4t/h0pxteibaaHd1QR4S+F1t0u7M1ETaCE6/cveFoF+2fowTIBNkJOyeZQjNg==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.25.0" @@ -8032,21 +8002,21 @@ } }, "node_modules/prosemirror-transform": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", - "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.2.tgz", + "integrity": "sha512-PE/aY0HEY4zczvmqrilgkUK/WautF0chvMqkmM/iN5/aPHvwrCWaQZjS6CIZp+K84YrvPFqrL+YArrfJX8xX7g==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.42.2", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.2.tgz", - "integrity": "sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==", + "version": "1.42.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.6.tgz", + "integrity": "sha512-3g25f6hKV2bBxo3puH3EcYWmmVNSH0iWmNsMVT7sugDSpA025jgpOLarcDWr39SdzPjYMaiYtRdNtXialPSjEQ==", "license": "MIT", "dependencies": { - "prosemirror-model": "^1.25.8", + "prosemirror-model": "^1.25.12", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } @@ -8275,13 +8245,13 @@ "license": "Unlicense" }, "node_modules/rolldown": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", - "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", + "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.142.0", + "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -8291,21 +8261,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.1", - "@rolldown/binding-darwin-arm64": "1.2.1", - "@rolldown/binding-darwin-x64": "1.2.1", - "@rolldown/binding-freebsd-x64": "1.2.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", - "@rolldown/binding-linux-arm64-gnu": "1.2.1", - "@rolldown/binding-linux-arm64-musl": "1.2.1", - "@rolldown/binding-linux-ppc64-gnu": "1.2.1", - "@rolldown/binding-linux-s390x-gnu": "1.2.1", - "@rolldown/binding-linux-x64-gnu": "1.2.1", - "@rolldown/binding-linux-x64-musl": "1.2.1", - "@rolldown/binding-openharmony-arm64": "1.2.1", - "@rolldown/binding-wasm32-wasi": "1.2.1", - "@rolldown/binding-win32-arm64-msvc": "1.2.1", - "@rolldown/binding-win32-x64-msvc": "1.2.1" + "@rolldown/binding-android-arm-eabi": "1.2.14", + "@rolldown/binding-android-arm64": "1.2.14", + "@rolldown/binding-darwin-arm64": "1.2.14", + "@rolldown/binding-darwin-x64": "1.2.14", + "@rolldown/binding-freebsd-x64": "1.2.14", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", + "@rolldown/binding-linux-arm64-gnu": "1.2.14", + "@rolldown/binding-linux-arm64-musl": "1.2.14", + "@rolldown/binding-linux-ppc64-gnu": "1.2.14", + "@rolldown/binding-linux-s390x-gnu": "1.2.14", + "@rolldown/binding-linux-x64-gnu": "1.2.14", + "@rolldown/binding-linux-x64-musl": "1.2.14", + "@rolldown/binding-openharmony-arm64": "1.2.14", + "@rolldown/binding-win32-arm64-msvc": "1.2.14", + "@rolldown/binding-win32-x64-msvc": "1.2.14" } }, "node_modules/rope-sequence": { @@ -8722,12 +8692,6 @@ "dev": true, "license": "MIT" }, - "node_modules/strictdom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", - "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==", - "license": "MIT" - }, "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -8886,9 +8850,9 @@ } }, "node_modules/style-mod": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", - "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", "dev": true, "license": "MIT", "peer": true @@ -9687,16 +9651,16 @@ } }, "node_modules/vite": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", - "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.23", - "rolldown": "~1.2.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -9713,7 +9677,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/package.json b/package.json index 0ad46b3..33248b6 100644 --- a/package.json +++ b/package.json @@ -34,19 +34,19 @@ "highlight.js": "11.11.1", "linkifyjs": "4.3.3", "lit-html": "2.8.0", - "mermaid": "^11.17.2", - "prosemirror-commands": "1.7.1", - "prosemirror-dropcursor": "1.8.3", + "mermaid": "^12.2.0", + "prosemirror-commands": "1.7.2", + "prosemirror-dropcursor": "1.8.4", "prosemirror-gapcursor": "1.4.1", - "prosemirror-history": "1.5.0", + "prosemirror-history": "1.5.1", "prosemirror-keymap": "1.2.3", - "prosemirror-model": "1.25.11", - "prosemirror-schema-basic": "1.2.4", + "prosemirror-model": "1.25.12", + "prosemirror-schema-basic": "1.2.5", "prosemirror-schema-list": "1.5.1", "prosemirror-state": "1.4.4", "prosemirror-tables": "1.8.5", - "prosemirror-transform": "1.12.0", - "prosemirror-view": "1.42.2", + "prosemirror-transform": "1.12.2", + "prosemirror-view": "1.42.6", "tippy.js": "6.3.7", "type-fest": "5.8.0" }, @@ -54,7 +54,8 @@ "prosemirror-model": "$prosemirror-model", "prosemirror-state": "$prosemirror-state", "prosemirror-view": "$prosemirror-view", - "prosemirror-transform": "$prosemirror-transform" + "prosemirror-transform": "$prosemirror-transform", + "obsidian": "1.14.4" }, "devDependencies": { "@babel/core": "7.29.7", @@ -66,18 +67,18 @@ "@types/node": "26.1.2", "babel-plugin-transform-jsx-to-tt": "0.5.0", "eslint": "9.39.5", - "eslint-plugin-obsidianmd": "0.4.1", + "eslint-plugin-obsidianmd": "0.4.2", "globals": "17.9.0", "jiti": "2.7.0", "jsdom": "30.0.1", "lightningcss": "1.33.0", - "obsidian": "1.13.1", + "obsidian": "1.14.4", "playwright-core": "1.63.0", "stylelint": "^16.26.1", "stylelint-no-unsupported-browser-features": "^8.1.2", "typescript": "5.9.3", "typescript-eslint": "8.66.0", - "vite": "8.2.0", + "vite": "8.3.5", "vitest": "^4.1.11" } } From 84bff3d596a9ebad1ec1d128837c4bc1ef483368 Mon Sep 17 00:00:00 2001 From: sumbad Date: Wed, 21 Oct 2026 18:45:00 +0800 Subject: [PATCH 7/8] chore(vault): move the mermaid scratch note out of git into local/ mermaid-clean-check.note was a throwaway typing fixture tracked in the repo root; local/ is the gitignored scratch area of the dev vault. --- mermaid-clean-check.note | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 mermaid-clean-check.note diff --git a/mermaid-clean-check.note b/mermaid-clean-check.note deleted file mode 100644 index 650acda..0000000 --- a/mermaid-clean-check.note +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "noteDoc", - "content": [ - { - "type": "noteTitle", - "content": [ - { - "type": "text", - "text": "mermaid-clean-check" - } - ] - }, - { - "type": "mermaid", - "attrs": { - "code": "flowchart LR\n Clean --> Test", - "align": "left" - } - }, - { - "type": "paragraph" - } - ] -} \ No newline at end of file From 7ef96abce94417498f63943ba884f3e3b465a8c4 Mon Sep 17 00:00:00 2001 From: sumbad Date: Wed, 21 Oct 2026 19:44:34 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20PR=20review=20findings=20=E2=80=94?= =?UTF-8?q?=20harness=20mounts=20the=20notepad=20again,=20CSS=20dedup=20re?= =?UTF-8?q?spects=20quoted=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile P1 (src/NotepadView.ts:1392): the new 'instanceof TFile' guard (the community review forbids TFile casts) rejected the harness' plain object for v.file, so the notepad editors never mounted and 22 e2e specs timed out. The harness now builds a real TFile instance. Also fixed the sibling break the failing e2e run exposed: the note-property extension now uses Obsidian's createSvg helper (prefer-create-el), which the harness shim did not provide — the notepad editor failed to init with 'createSvg is not defined'. The shim now implements createSvg (SVG namespace, class via setAttribute — SVG className is read-only). Greptile P2 (scripts/generate-note-css.mjs:101): dedupProperties() split declaration blocks on every ';', including semicolons inside quoted values — a value like content:"x;color:red" was cut in half and the generated stylesheet lost a fragment. The splitter is now quote-aware, fails fast on unbalanced quotes, and the pure helpers moved to scripts/generate-note-css-lib.mjs with a regression test (tests/generate-note-css/generate-note-css.test.ts). Gates: lint:src 0, lint:css 0, typecheck, 643 tests, 48/48 e2e — pass --- scripts/generate-note-css-lib.mjs | 118 ++++++++++++++++++ scripts/generate-note-css.mjs | 63 +--------- src/browser-harness/harness.ts | 66 ++++++---- .../generate-note-css.test.ts | 90 +++++++++++++ 4 files changed, 256 insertions(+), 81 deletions(-) create mode 100644 scripts/generate-note-css-lib.mjs create mode 100644 tests/generate-note-css/generate-note-css.test.ts diff --git a/scripts/generate-note-css-lib.mjs b/scripts/generate-note-css-lib.mjs new file mode 100644 index 0000000..0367bb9 --- /dev/null +++ b/scripts/generate-note-css-lib.mjs @@ -0,0 +1,118 @@ +// Pure helpers for scripts/generate-note-css.mjs (the CLI). Kept separate +// from the CLI so they can be unit-tested (see +// tests/generate-note-css/generate-note-css.test.ts) — the CLI itself reads +// the vault-facing file, fetches the deployed stylesheet and writes the +// result, none of which a test wants to run. + +/** + * Fails fast on CSS the block splitter below cannot see through: + * - an unbalanced quote (the closing quote is missing), + * - a brace inside a quoted value (the block regex would cut the block + * at that brace and mangle the rule). + * The maintainer then fixes the upstream change by hand — the generator + * must never write silently broken CSS. + */ +export function assertSplittable(css) { + for (let i = 0; i < css.length; i += 1) { + const ch = css[i]; + if (ch !== '"' && ch !== "'") continue; + const close = css.indexOf(ch, i + 1); + if (close < 0) throw new Error("unbalanced quote in deployed css"); + const inner = css.slice(i + 1, close); + if (/[{}]/.test(inner)) { + throw new Error( + "brace inside a quoted css value — dedup splitter cannot parse this safely; handle the upstream change manually", + ); + } + i = close; + } +} + +/** + * Splits a declaration block on the `;` boundaries that are NOT inside a + * quoted string. CSS values may contain semicolons (`content:"a;b"`, and + * `content` is a plain value), so a naive split cuts the value in half and + * the rewritten file loses a fragment of it. + */ +export function splitDeclarations(inner) { + const out = []; + let current = ""; + let quote = null; + for (let i = 0; i < inner.length; i += 1) { + const ch = inner[i]; + if (quote != null) { + current += ch; + if (ch === "\\") { + // escaped character — copy it and the next one verbatim + if (i + 1 < inner.length) { + current += inner[i + 1]; + i += 1; + } + continue; + } + if (ch === quote) quote = null; + continue; + } + if (ch === '"' || ch === "'") { + quote = ch; + current += ch; + continue; + } + if (ch === ";") { + out.push(current); + current = ""; + continue; + } + current += ch; + } + if (quote != null) throw new Error("unbalanced quote in deployed css"); + out.push(current); + return out; +} + +/** + * Removes duplicate property declarations inside each block, keeping the + * LAST occurrence (cascade-equivalent: the last declaration wins — an + * `!important` repeat only loses to a later `!important` of the same + * property, so importance is part of the dedup key), and drops legacy IE + * star-hack declarations (*prop — invalid in every modern browser and + * flagged as duplicates by the stylelint gate). + * + * Conservative guards: a block containing `url(` is left untouched (a `;` + * inside an unquoted data URI would break naive splitting), and the whole + * run fails when quotes and braces interleave in a way the splitter cannot + * see through. + */ +export function dedupProperties(css) { + assertSplittable(css); + let removed = 0; + const out = css.replace(/\{[^{}]*\}/g, (block) => { + if (block.includes("url(")) return block; + const inner = block.slice(1, -1); + const decls = splitDeclarations(inner); + const keyOf = (d) => { + const colon = d.indexOf(":"); + if (colon < 0) return null; + const prop = d.slice(0, colon).trim().toLowerCase(); + return prop ? `${prop}!${d.includes("!important") ? 1 : 0}` : null; + }; + const lastIndex = new Map(); + decls.forEach((d, i) => { + const key = keyOf(d); + if (key) lastIndex.set(key, i); + }); + const kept = decls.filter((d, i) => { + const key = keyOf(d); + if (!key) return d.trim() !== ""; + if (key.startsWith("*")) { + removed += 1; + return false; + } + const isLast = lastIndex.get(key) === i; + if (!isLast) removed += 1; + return isLast; + }); + return `{${kept.join(";")}}`; + }); + return { out, removed }; +} diff --git a/scripts/generate-note-css.mjs b/scripts/generate-note-css.mjs index 082765a..6705f5b 100644 --- a/scripts/generate-note-css.mjs +++ b/scripts/generate-note-css.mjs @@ -27,6 +27,7 @@ */ import { readFile, writeFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; +import { dedupProperties } from "./generate-note-css-lib.mjs"; const DEPLOYED_URL = "https://inscriptum.js.org/css/note.css"; const TARGET = fileURLToPath( @@ -65,67 +66,13 @@ function extractParts(current) { } return { fonts: lines[fontsIdx], - additions: lines.slice(deployedIdx + 1).join("\n").replace(/^\n+/, ""), + additions: lines + .slice(deployedIdx + 1) + .join("\n") + .replace(/^\n+/, ""), }; } -/** Removes duplicate property declarations inside each block, keeping the - * LAST occurrence (cascade-equivalent: the last declaration wins — an - * `!important` repeat only loses to a later `!important` of the same - * property, so importance is part of the dedup key), and drops legacy IE - * star-hack declarations (*prop — invalid in every modern browser and - * flagged as duplicates by the stylelint gate). Conservative guards: any - * block containing `url(` is left untouched (a `;` inside an unquoted - * data URI would break naive splitting), and the whole run fails when - * quotes and braces interleave in a way the splitter cannot see through — - * the maintainer then dedups that upstream change by hand. */ -function dedupProperties(css) { - for (let i = 0; i < css.length; i++) { - const ch = css[i]; - if (ch === '"' || ch === "'") { - const close = css.indexOf(ch, i + 1); - if (close < 0) throw new Error("unbalanced quote in deployed css"); - const inner = css.slice(i + 1, close); - if (/[{}]/.test(inner)) { - throw new Error( - "brace inside a quoted css value — dedup splitter cannot parse this safely; handle the upstream change manually", - ); - } - i = close; - } - } - let removed = 0; - const out = css.replace(/\{[^{}]*\}/g, (block) => { - if (block.includes("url(")) return block; - const inner = block.slice(1, -1); - const decls = inner.split(";"); - const keyOf = (d) => { - const colon = d.indexOf(":"); - if (colon < 0) return null; - const prop = d.slice(0, colon).trim().toLowerCase(); - return prop ? `${prop}!${d.includes("!important") ? 1 : 0}` : null; - }; - const lastIndex = new Map(); - decls.forEach((d, i) => { - const key = keyOf(d); - if (key) lastIndex.set(key, i); - }); - const kept = decls.filter((d, i) => { - const key = keyOf(d); - if (!key) return d.trim() !== ""; - if (key.startsWith("*")) { - removed += 1; - return false; - } - const isLast = lastIndex.get(key) === i; - if (!isLast) removed += 1; - return isLast; - }); - return `{${kept.join(";")}}`; - }); - return { out, removed }; -} - const [current, deployed] = await Promise.all([ readFile(TARGET, "utf8"), fetchDeployed(), diff --git a/src/browser-harness/harness.ts b/src/browser-harness/harness.ts index 8f08c4d..8baa5fc 100644 --- a/src/browser-harness/harness.ts +++ b/src/browser-harness/harness.ts @@ -4,10 +4,7 @@ import type { JSONContent } from "../texto/core/@types"; import { Editor } from "../texto/core/Editor"; import { getExtensions } from "../texto/getExtensions"; -import { - openImageGallery, - type GalleryItem, -} from "../ui/imageLightbox"; +import { openImageGallery, type GalleryItem } from "../ui/imageLightbox"; import { headingFoldingKey } from "../texto/extensions/heading/folding"; import { taskFoldingKey } from "../texto/extensions/task-item-folding/taskFoldingPlugin"; import { NotepadView } from "../NotepadView"; @@ -16,6 +13,7 @@ import { UmNotepad } from "../storage/um/umNotepad"; import { exportNotepadAsWebsite } from "../export/exportNotepadWebsite"; import type { UmContainerData, UmNoteDescriptor } from "../storage/um/umTypes"; import type { WorkspaceLeaf } from "obsidian"; +import { TFile } from "obsidian"; import { UM_FORMAT, UM_SCHEMA_PLAIN, @@ -68,6 +66,21 @@ g.createSpan = (attrs?: DomAttrs | string) => { }; g.createEl = (tag: string, attrs?: DomAttrs | string) => applyAttrs(document.createElement(tag), attrs); +// Obsidian's createSvg builds an element in the SVG namespace. SVG elements +// have no writable `className` (it is an SVGAnimatedString), so the class +// goes through setAttribute — the note-property sprite icons rely on it. +g.createSvg = (tag: string, attrs?: DomAttrs | string) => { + const svg = document.createElementNS("http://www.w3.org/2000/svg", tag); + if (typeof attrs === "string") svg.textContent = attrs; + else if (attrs != null) { + if (attrs.cls) svg.setAttribute("class", attrs.cls); + if (attrs.text != null) svg.textContent = attrs.text; + if (attrs.attr) { + for (const [k, v] of Object.entries(attrs.attr)) svg.setAttribute(k, v); + } + } + return svg; +}; g.createFragment = () => document.createDocumentFragment(); /* eslint-enable obsidianmd/prefer-create-el -- end of the global shim block (the rule stays disabled only while the shim's own helpers are defined) */ // NOTE: do NOT assign window.requestAnimationFrame — `g` IS window, and @@ -363,14 +376,13 @@ function mountNotepad( ]; notes.set("title-page", { type: "noteDoc", - content: - opts?.titleDoc ?? [ - { - type: "noteTitle", - content: [{ type: "text", text: "Notepad title" }], - }, - { type: "paragraph" }, - ], + content: opts?.titleDoc ?? [ + { + type: "noteTitle", + content: [{ type: "text", text: "Notepad title" }], + }, + { type: "paragraph" }, + ], }); pages.forEach((page, i) => { descriptors.push({ @@ -462,15 +474,20 @@ function mountNotepad( view.contentEl.addClass("view-content"); view.contentEl.addClass("notepad-view-container"); v.app = app; - v.file = { - path: "e2e/harness.um", - name: "harness.um", - basename: "harness", - parent: null, - // renderCollapsedTitle falls back to the container mtime for pages - // without a modifiedAt stamp (um-page-updated-at) — TFile always has it. - stat: { mtime: 1790000000000, ctime: 1790000000000, size: 1 }, - }; + // NotepadView guards the open file with `instanceof TFile` (the community + // review forbids TFile casts) — the harness must hand it a real TFile + // instance, not a plain object, or the guard rejects it and the editors + // never mount. The fields are set explicitly: the harness shim's TFile + // takes no constructor argument. + const harnessFile = new TFile(); + harnessFile.path = "e2e/harness.um"; + harnessFile.name = "harness.um"; + harnessFile.basename = "harness"; + harnessFile.parent = null; + // renderCollapsedTitle falls back to the container mtime for pages + // without a modifiedAt stamp (um-page-updated-at) — TFile always has it. + harnessFile.stat = { mtime: 1790000000000, ctime: 1790000000000, size: 1 }; + v.file = harnessFile; // Mirror onOpen's DOM skeleton; the nav drawer (buildNavSidebar) stays // unbuilt — rebuildNavList null-guards on the missing navListEl. v.toolbarHost = view.contentEl.createDiv("notepad-toolbar-host"); @@ -525,9 +542,12 @@ function b64ToBytes(b64: string): Uint8Array { function toB64(data: string | ArrayBuffer): string { const bytes = - typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data); + typeof data === "string" + ? new TextEncoder().encode(data) + : new Uint8Array(data); let bin = ""; - for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]); + for (let i = 0; i < bytes.length; i += 1) + bin += String.fromCharCode(bytes[i]); return btoa(bin); } diff --git a/tests/generate-note-css/generate-note-css.test.ts b/tests/generate-note-css/generate-note-css.test.ts new file mode 100644 index 0000000..27bdcd8 --- /dev/null +++ b/tests/generate-note-css/generate-note-css.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect } from "vitest"; +import { + assertSplittable, + dedupProperties, + splitDeclarations, +} from "../../scripts/generate-note-css-lib.mjs"; + +describe("splitDeclarations", () => { + it("splits on top-level semicolons", () => { + expect(splitDeclarations("color:red;background:blue")).toEqual([ + "color:red", + "background:blue", + ]); + }); + + it("keeps a semicolon inside a double-quoted value", () => { + expect(splitDeclarations('content:"x;color:red";color:blue')).toEqual([ + 'content:"x;color:red"', + "color:blue", + ]); + }); + + it("keeps a semicolon inside a single-quoted value", () => { + expect(splitDeclarations("content:'a;b'")).toEqual(["content:'a;b'"]); + }); + + it("keeps an escaped quote inside a value", () => { + expect(splitDeclarations('content:"a\\"b;c"')).toEqual([ + 'content:"a\\"b;c"', + ]); + }); + + it("throws on an unbalanced quote", () => { + expect(() => splitDeclarations('content:"a;b')).toThrow(/unbalanced quote/); + }); +}); + +describe("assertSplittable", () => { + it("passes on plain css", () => { + expect(() => assertSplittable('.a{content:"x;y"}')).not.toThrow(); + }); + + it("throws on an unbalanced quote", () => { + expect(() => assertSplittable('.a{content:"x}')).toThrow( + /unbalanced quote/, + ); + }); + + it("throws on a brace inside a quoted value", () => { + expect(() => assertSplittable('.a{content:"{x}"}')).toThrow(/brace inside/); + }); +}); + +describe("dedupProperties", () => { + it("keeps the last occurrence of a duplicated property", () => { + const { out, removed } = dedupProperties(".a{color:red;color:blue}"); + expect(out).toBe(".a{color:blue}"); + expect(removed).toBe(1); + }); + + it("treats an important declaration as a different property", () => { + const { out } = dedupProperties(".a{color:red !important;color:blue}"); + expect(out).toBe(".a{color:red !important;color:blue}"); + }); + + it("drops legacy star-hack declarations", () => { + const { out, removed } = dedupProperties(".a{*width:100%;width:50%}"); + expect(out).toBe(".a{width:50%}"); + expect(removed).toBe(1); + }); + + it("leaves blocks with url() untouched", () => { + const css = ".a{background:url(data:image/png;base64,AAA);background:red}"; + const { out } = dedupProperties(css); + expect(out).toBe(css); + }); + + // Regression (PR review): a semicolon inside a quoted value used to be + // treated as a declaration boundary — the value was cut in half and the + // generated stylesheet lost a fragment of it. + it("keeps a quoted value that contains a semicolon", () => { + const { out } = dedupProperties('.x{content:"x;color:red";color:blue}'); + expect(out).toBe('.x{content:"x;color:red";color:blue}'); + }); + + it("keeps a quoted value that contains a semicolon next to a duplicate", () => { + const { out } = dedupProperties('.x{content:"a;b";color:red;color:blue}'); + expect(out).toBe('.x{content:"a;b";color:blue}'); + }); +});