From ea681ff253af83433546c6d5553d8e252c3a345b Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 12:23:41 +0800 Subject: [PATCH 01/19] chore: ignore .staged-delivery evidence dir --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 933a8ab..95c4c11 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ coverage/ npm-debug.log* .DS_Store + +# staged-delivery local verification evidence (recordings, screenshots) — never committed +.staged-delivery/ From ebd5375c9c9e697523e1c3d792e6ab743877ccd8 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 12:31:04 +0800 Subject: [PATCH 02/19] feat(vue): add default components mirroring the HTML adapter set --- package-lock.json | 461 +++++++++++++++++- package.json | 17 +- .../vue/default-components/alert.js | 20 + .../vue/default-components/codepen.js | 17 + .../default-components/default-components.js | 37 ++ .../default-components.test.js | 117 +++++ .../external-link-tab-title.js | 11 + .../default-components/external-link-tab.js | 11 + .../default-components/external-link-title.js | 7 + .../vue/default-components/heading.js | 11 + .../vue/default-components/iframe.js | 7 + .../default-components/image-display-link.js | 11 + .../vue/default-components/image-display.js | 9 + .../vue/default-components/image-link.js | 12 + .../vue/default-components/image.js | 7 + .../default-components/internal-link-title.js | 16 + .../vue/default-components/internal-link.js | 11 + .../vue/default-components/math.js | 13 + .../vue/default-components/youtube.js | 18 + 19 files changed, 784 insertions(+), 29 deletions(-) create mode 100644 src/markup-converters/vue/default-components/alert.js create mode 100644 src/markup-converters/vue/default-components/codepen.js create mode 100644 src/markup-converters/vue/default-components/default-components.js create mode 100644 src/markup-converters/vue/default-components/default-components.test.js create mode 100644 src/markup-converters/vue/default-components/external-link-tab-title.js create mode 100644 src/markup-converters/vue/default-components/external-link-tab.js create mode 100644 src/markup-converters/vue/default-components/external-link-title.js create mode 100644 src/markup-converters/vue/default-components/heading.js create mode 100644 src/markup-converters/vue/default-components/iframe.js create mode 100644 src/markup-converters/vue/default-components/image-display-link.js create mode 100644 src/markup-converters/vue/default-components/image-display.js create mode 100644 src/markup-converters/vue/default-components/image-link.js create mode 100644 src/markup-converters/vue/default-components/image.js create mode 100644 src/markup-converters/vue/default-components/internal-link-title.js create mode 100644 src/markup-converters/vue/default-components/internal-link.js create mode 100644 src/markup-converters/vue/default-components/math.js create mode 100644 src/markup-converters/vue/default-components/youtube.js diff --git a/package-lock.json b/package-lock.json index 65404a7..bb82a59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@coseeing/see-mark", - "version": "1.10.0", + "version": "1.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@coseeing/see-mark", - "version": "1.10.0", + "version": "1.11.0", "license": "ISC", "dependencies": { "@coseeing/nemeth2latex": "^0.2.0", @@ -26,6 +26,8 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", + "@vue/server-renderer": "^3.5.39", + "@vue/test-utils": "^2.4.11", "babel-jest": "^30.0.5", "eslint": "^9.25.1", "eslint-config-prettier": "^10.1.2", @@ -38,12 +40,24 @@ "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.4", "prettier": "^3.5.3", - "rollup": "^4.40.1" + "rollup": "^4.40.1", + "vue": "^3.5.39" }, "peerDependencies": { "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0" + }, + "peerDependenciesMeta": { + "prop-types": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, "node_modules/@adobe/css-tools": { @@ -399,9 +413,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -409,9 +423,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -458,13 +472,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1933,14 +1947,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -3023,9 +3037,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -3091,6 +3105,13 @@ "node": ">= 8" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4301,6 +4322,149 @@ "win32" ] }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.11.tgz", + "integrity": "sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, "node_modules/@xmldom/xmldom": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", @@ -4310,6 +4474,16 @@ "node": ">=14.6" } }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -5300,6 +5474,17 @@ "dev": true, "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5357,6 +5542,13 @@ "node": ">=18" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5724,6 +5916,74 @@ "dev": true, "license": "MIT" }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.214", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz", @@ -7242,6 +7502,13 @@ "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/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", @@ -8694,6 +8961,35 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8954,13 +9250,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-dir": { @@ -9142,6 +9438,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-postinstall": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", @@ -9179,6 +9494,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9213,6 +9544,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9642,6 +9974,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", + "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9713,6 +10074,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "devOptional": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -9724,8 +10086,16 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "devOptional": true, "license": "MIT" }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -9791,6 +10161,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -10165,6 +10536,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -10362,6 +10734,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -11212,6 +11594,35 @@ "node": ">=10.12.0" } }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.7.tgz", + "integrity": "sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 9dac994..c01ab51 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", + "@vue/server-renderer": "^3.5.39", + "@vue/test-utils": "^2.4.11", "babel-jest": "^30.0.5", "eslint": "^9.25.1", "eslint-config-prettier": "^10.1.2", @@ -46,7 +48,8 @@ "jest": "^30.0.0", "jest-environment-jsdom": "^30.0.4", "prettier": "^3.5.3", - "rollup": "^4.40.1" + "rollup": "^4.40.1", + "vue": "^3.5.39" }, "dependencies": { "@coseeing/nemeth2latex": "^0.2.0", @@ -62,8 +65,14 @@ "react-dom": "^18.2.0" }, "peerDependenciesMeta": { - "prop-types": { "optional": true }, - "react": { "optional": true }, - "react-dom": { "optional": true } + "prop-types": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } } diff --git a/src/markup-converters/vue/default-components/alert.js b/src/markup-converters/vue/default-components/alert.js new file mode 100644 index 0000000..b205559 --- /dev/null +++ b/src/markup-converters/vue/default-components/alert.js @@ -0,0 +1,20 @@ +import { h } from 'vue'; + +const alert = ( + { internalLinkId = '', variant = '', title = '' }, + { slots } +) => { + const children = [h('p', null, variant.toUpperCase())]; + if (slots.default) children.push(...slots.default()); + if (internalLinkId) { + children.push(h('a', { href: `#${internalLinkId}-source` }, 'back')); + } + return h( + 'div', + { role: 'region', 'aria-label': title, id: internalLinkId || null }, + children + ); +}; +alert.inheritAttrs = false; + +export default alert; diff --git a/src/markup-converters/vue/default-components/codepen.js b/src/markup-converters/vue/default-components/codepen.js new file mode 100644 index 0000000..1ee2609 --- /dev/null +++ b/src/markup-converters/vue/default-components/codepen.js @@ -0,0 +1,17 @@ +import { h } from 'vue'; + +// Attributes adapted from CodePen's official embed snippet. +const codepen = ({ title = '', source = '' }) => + h('iframe', { + height: '300', + style: 'width: 100%;', + scrolling: 'no', + title, + src: source, + frameborder: 'no', + loading: 'lazy', + allowtransparency: 'true', + }); +codepen.inheritAttrs = false; + +export default codepen; diff --git a/src/markup-converters/vue/default-components/default-components.js b/src/markup-converters/vue/default-components/default-components.js new file mode 100644 index 0000000..849a1ad --- /dev/null +++ b/src/markup-converters/vue/default-components/default-components.js @@ -0,0 +1,37 @@ +import { SUPPORTED_COMPONENT_TYPES } from '../../../shared/supported-components'; + +import alert from './alert'; +import heading from './heading'; +import internalLink from './internal-link'; +import internalLinkTitle from './internal-link-title'; +import image from './image'; +import imageLink from './image-link'; +import imageDisplay from './image-display'; +import imageDisplayLink from './image-display-link'; +import externalLinkTab from './external-link-tab'; +import externalLinkTitle from './external-link-title'; +import externalLinkTabTitle from './external-link-tab-title'; +import youtube from './youtube'; +import codepen from './codepen'; +import iframe from './iframe'; +import math from './math'; + +const defaultComponents = { + [SUPPORTED_COMPONENT_TYPES.ALERT]: alert, + [SUPPORTED_COMPONENT_TYPES.HEADING]: heading, + [SUPPORTED_COMPONENT_TYPES.INTERNAL_LINK]: internalLink, + [SUPPORTED_COMPONENT_TYPES.INTERNAL_LINK_TITLE]: internalLinkTitle, + [SUPPORTED_COMPONENT_TYPES.IMAGE]: image, + [SUPPORTED_COMPONENT_TYPES.IMAGE_LINK]: imageLink, + [SUPPORTED_COMPONENT_TYPES.IMAGE_DISPLAY]: imageDisplay, + [SUPPORTED_COMPONENT_TYPES.IMAGE_DISPLAY_LINK]: imageDisplayLink, + [SUPPORTED_COMPONENT_TYPES.EXTERNAL_LINK_TAB]: externalLinkTab, + [SUPPORTED_COMPONENT_TYPES.EXTERNAL_LINK_TITLE]: externalLinkTitle, + [SUPPORTED_COMPONENT_TYPES.EXTERNAL_LINK_TAB_TITLE]: externalLinkTabTitle, + [SUPPORTED_COMPONENT_TYPES.YOUTUBE]: youtube, + [SUPPORTED_COMPONENT_TYPES.CODEPEN]: codepen, + [SUPPORTED_COMPONENT_TYPES.IFRAME]: iframe, + [SUPPORTED_COMPONENT_TYPES.MATH]: math, +}; + +export default defaultComponents; diff --git a/src/markup-converters/vue/default-components/default-components.test.js b/src/markup-converters/vue/default-components/default-components.test.js new file mode 100644 index 0000000..5159409 --- /dev/null +++ b/src/markup-converters/vue/default-components/default-components.test.js @@ -0,0 +1,117 @@ +/** + * Vue 3 ships its default "." export condition pointing at the + * bundler-targeted ESM build, which assumes further processing by a bundler + * and cannot be executed directly by Node/Jest. jsdom's default Jest + * environment also requests the "browser" export condition instead of + * "node", so without this override Jest resolves `import ... from 'vue'` to + * that non-executable bundler build. This per-file docblock (a native Jest + * feature) asks Jest to prefer the real Node entry points instead — it does + * not touch the shared jest.config.js. + * @jest-environment jsdom + * @jest-environment-options {"customExportConditions": ["node", "node-addons"]} + */ +import { h } from 'vue'; +import { mount } from '@vue/test-utils'; + +import { SUPPORTED_COMPONENT_TYPES } from '../../../shared/supported-components'; + +import defaultComponents from './default-components'; +import alert from './alert'; +import heading from './heading'; +import math from './math'; +import imageDisplayLink from './image-display-link'; + +const mountComponent = (Component, props, childText) => + mount({ + render: () => + h( + 'div', + h( + Component, + props, + childText ? { default: () => [childText] } : undefined + ) + ), + }); + +describe('vue default components', () => { + it('registry covers every SUPPORTED_COMPONENT_TYPES key', () => { + for (const type of Object.values(SUPPORTED_COMPONENT_TYPES)) { + expect(typeof defaultComponents[type]).toBe('function'); + } + expect(Object.keys(defaultComponents)).toHaveLength( + Object.values(SUPPORTED_COMPONENT_TYPES).length + ); + }); + + it('every component disables attribute fallthrough', () => { + // Payload keys (position, math, typed, ...) would otherwise be applied + // to the root element as DOM attributes and break cross-adapter parity. + for (const Component of Object.values(defaultComponents)) { + expect(Component.inheritAttrs).toBe(false); + } + }); + + it('alert renders region, uppercased variant, slot children and backlink', () => { + const wrapper = mountComponent( + alert, + { internalLinkId: 'a1', variant: 'warning', title: 'Careful' }, + 'body text' + ); + const region = wrapper.get('[role="region"]'); + expect(region.attributes('aria-label')).toBe('Careful'); + expect(region.attributes('id')).toBe('a1'); + expect(region.get('p').text()).toBe('WARNING'); + expect(region.text()).toContain('body text'); + expect(region.get('a').attributes('href')).toBe('#a1-source'); + }); + + it('alert omits id and backlink without internalLinkId', () => { + const wrapper = mountComponent( + alert, + { internalLinkId: '', variant: 'note', title: '' }, + 'x' + ); + expect(wrapper.get('[role="region"]').attributes('id')).toBeUndefined(); + expect(wrapper.find('a').exists()).toBe(false); + }); + + it('heading renders the level and clamps invalid levels to h1', () => { + expect( + mountComponent(heading, { id: 'sec', level: 3 }, 'Title').get('h3').text() + ).toBe('Title'); + expect( + mountComponent(heading, { id: null, level: 9 }, 'T').find('h1').exists() + ).toBe(true); + }); + + it('math renders mathMl/svg via innerHTML into sr-only + aria-hidden spans', () => { + const wrapper = mountComponent(math, { + mathMl: 'x', + svg: '', + }); + expect(wrapper.get('span.sr-only').element.innerHTML).toBe( + 'x' + ); + expect(wrapper.get('span[aria-hidden="true"]').element.innerHTML).toBe( + '' + ); + }); + + it('imageDisplayLink composes anchor > figure > img + figcaption', () => { + const wrapper = mountComponent(imageDisplayLink, { + display: 'A cat', + target: 'https://example.com', + alt: 'cat', + imageId: 'pic-id', + source: 'https://example.com/p.png', + }); + const anchor = wrapper.get('a'); + expect(anchor.attributes('href')).toBe('https://example.com'); + const img = anchor.get('figure img'); + expect(img.attributes('src')).toBe('https://example.com/p.png'); + expect(img.attributes('alt')).toBe('cat'); + expect(img.attributes('data-seemark-image-id')).toBe('pic-id'); + expect(anchor.get('figcaption').text()).toBe('A cat'); + }); +}); diff --git a/src/markup-converters/vue/default-components/external-link-tab-title.js b/src/markup-converters/vue/default-components/external-link-tab-title.js new file mode 100644 index 0000000..410b04d --- /dev/null +++ b/src/markup-converters/vue/default-components/external-link-tab-title.js @@ -0,0 +1,11 @@ +import { h } from 'vue'; + +const externalLinkTabTitle = ({ display = '', title = '', target = '' }) => + h( + 'a', + { href: target, title, target: '_blank', rel: 'noopener noreferrer' }, + display + ); +externalLinkTabTitle.inheritAttrs = false; + +export default externalLinkTabTitle; diff --git a/src/markup-converters/vue/default-components/external-link-tab.js b/src/markup-converters/vue/default-components/external-link-tab.js new file mode 100644 index 0000000..952e3aa --- /dev/null +++ b/src/markup-converters/vue/default-components/external-link-tab.js @@ -0,0 +1,11 @@ +import { h } from 'vue'; + +const externalLinkTab = ({ display = '', target = '' }) => + h( + 'a', + { href: target, target: '_blank', rel: 'noopener noreferrer' }, + display + ); +externalLinkTab.inheritAttrs = false; + +export default externalLinkTab; diff --git a/src/markup-converters/vue/default-components/external-link-title.js b/src/markup-converters/vue/default-components/external-link-title.js new file mode 100644 index 0000000..d3e0478 --- /dev/null +++ b/src/markup-converters/vue/default-components/external-link-title.js @@ -0,0 +1,7 @@ +import { h } from 'vue'; + +const externalLinkTitle = ({ display = '', title = '', target = '' }) => + h('a', { href: target, title }, display); +externalLinkTitle.inheritAttrs = false; + +export default externalLinkTitle; diff --git a/src/markup-converters/vue/default-components/heading.js b/src/markup-converters/vue/default-components/heading.js new file mode 100644 index 0000000..82c29a3 --- /dev/null +++ b/src/markup-converters/vue/default-components/heading.js @@ -0,0 +1,11 @@ +import { h } from 'vue'; + +const VALID_LEVELS = new Set([1, 2, 3, 4, 5, 6]); + +const heading = ({ id = null, level = 1 }, { slots }) => { + const lvl = VALID_LEVELS.has(level) ? level : 1; + return h(`h${lvl}`, { id: id || null }, slots.default ? slots.default() : []); +}; +heading.inheritAttrs = false; + +export default heading; diff --git a/src/markup-converters/vue/default-components/iframe.js b/src/markup-converters/vue/default-components/iframe.js new file mode 100644 index 0000000..1191623 --- /dev/null +++ b/src/markup-converters/vue/default-components/iframe.js @@ -0,0 +1,7 @@ +import { h } from 'vue'; + +const iframe = ({ title = '', source = '' }) => + h('iframe', { title, src: source }); +iframe.inheritAttrs = false; + +export default iframe; diff --git a/src/markup-converters/vue/default-components/image-display-link.js b/src/markup-converters/vue/default-components/image-display-link.js new file mode 100644 index 0000000..f0f1d9f --- /dev/null +++ b/src/markup-converters/vue/default-components/image-display-link.js @@ -0,0 +1,11 @@ +import { h } from 'vue'; + +import image from './image'; + +const imageDisplayLink = ({ display = '', ...props }) => + h('a', { href: props.target }, [ + h('figure', null, [image(props), h('figcaption', null, display)]), + ]); +imageDisplayLink.inheritAttrs = false; + +export default imageDisplayLink; diff --git a/src/markup-converters/vue/default-components/image-display.js b/src/markup-converters/vue/default-components/image-display.js new file mode 100644 index 0000000..99f9c60 --- /dev/null +++ b/src/markup-converters/vue/default-components/image-display.js @@ -0,0 +1,9 @@ +import { h } from 'vue'; + +import image from './image'; + +const imageDisplay = ({ display = '', ...props }) => + h('figure', null, [image(props), h('figcaption', null, display)]); +imageDisplay.inheritAttrs = false; + +export default imageDisplay; diff --git a/src/markup-converters/vue/default-components/image-link.js b/src/markup-converters/vue/default-components/image-link.js new file mode 100644 index 0000000..9b42634 --- /dev/null +++ b/src/markup-converters/vue/default-components/image-link.js @@ -0,0 +1,12 @@ +import { h } from 'vue'; + +import image from './image'; + +// image() is a plain function — calling it directly (instead of h(image, ...)) +// keeps the DOM free of an extra component instance, mirroring the HTML +// adapter's string composition. +const imageLink = (props = {}) => + h('a', { href: props.target }, [image(props)]); +imageLink.inheritAttrs = false; + +export default imageLink; diff --git a/src/markup-converters/vue/default-components/image.js b/src/markup-converters/vue/default-components/image.js new file mode 100644 index 0000000..c32baf2 --- /dev/null +++ b/src/markup-converters/vue/default-components/image.js @@ -0,0 +1,7 @@ +import { h } from 'vue'; + +const image = ({ alt = '', imageId = '', source = '' }) => + h('img', { src: source, alt, 'data-seemark-image-id': imageId }); +image.inheritAttrs = false; + +export default image; diff --git a/src/markup-converters/vue/default-components/internal-link-title.js b/src/markup-converters/vue/default-components/internal-link-title.js new file mode 100644 index 0000000..846a03c --- /dev/null +++ b/src/markup-converters/vue/default-components/internal-link-title.js @@ -0,0 +1,16 @@ +import { h } from 'vue'; + +const internalLinkTitle = ({ display = '', title = '', target = '' }) => + h( + 'a', + { + href: `#${target}`, + id: `${target}-source`, + title, + class: 'underline', + }, + display + ); +internalLinkTitle.inheritAttrs = false; + +export default internalLinkTitle; diff --git a/src/markup-converters/vue/default-components/internal-link.js b/src/markup-converters/vue/default-components/internal-link.js new file mode 100644 index 0000000..374cfc8 --- /dev/null +++ b/src/markup-converters/vue/default-components/internal-link.js @@ -0,0 +1,11 @@ +import { h } from 'vue'; + +const internalLink = ({ display = '', target = '' }) => + h( + 'a', + { href: `#${target}`, id: `${target}-source`, class: 'underline' }, + display + ); +internalLink.inheritAttrs = false; + +export default internalLink; diff --git a/src/markup-converters/vue/default-components/math.js b/src/markup-converters/vue/default-components/math.js new file mode 100644 index 0000000..87787b8 --- /dev/null +++ b/src/markup-converters/vue/default-components/math.js @@ -0,0 +1,13 @@ +import { h } from 'vue'; + +// mathMl and svg are MathJax output produced in Stage 1 — trusted markup +// (Stage 1 strips forged data-seemark-* from user raw HTML, so a math payload +// can only come from the math extension itself). They must render as live +// HTML, hence innerHTML. +const math = ({ mathMl = '', svg = '' }) => [ + h('span', { class: 'sr-only', innerHTML: mathMl }), + h('span', { 'aria-hidden': 'true', innerHTML: svg }), +]; +math.inheritAttrs = false; + +export default math; diff --git a/src/markup-converters/vue/default-components/youtube.js b/src/markup-converters/vue/default-components/youtube.js new file mode 100644 index 0000000..6fbc3fb --- /dev/null +++ b/src/markup-converters/vue/default-components/youtube.js @@ -0,0 +1,18 @@ +import { h } from 'vue'; + +// Attributes adapted from YouTube's official embed snippet. +const youtube = ({ title = '', source = '' }) => + h('iframe', { + width: '560', + height: '315', + src: source, + title, + frameborder: '0', + allow: + 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share', + referrerpolicy: 'strict-origin-when-cross-origin', + allowfullscreen: '', + }); +youtube.inheritAttrs = false; + +export default youtube; From dc5540d29b2696821fbe32910144fa2d1a75f926 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 12:45:42 +0800 Subject: [PATCH 03/19] feat(vue): add markup-to-VNode converter with on* neutralization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review fix: the default-slot closure now rebuilds children per invocation instead of returning one cached VNode array — a component may call slots.default() twice in one render (or re-render later), and reusing mounted VNodes violates Vue's uniqueness contract. --- src/markup-converters/vue/converter.js | 89 +++++++++++++ src/markup-converters/vue/converter.test.js | 133 ++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 src/markup-converters/vue/converter.js create mode 100644 src/markup-converters/vue/converter.test.js diff --git a/src/markup-converters/vue/converter.js b/src/markup-converters/vue/converter.js new file mode 100644 index 0000000..75e1834 --- /dev/null +++ b/src/markup-converters/vue/converter.js @@ -0,0 +1,89 @@ +import { h } from 'vue'; +import { parseDocument } from 'htmlparser2'; + +import { + SEEMARK_ELEMENT_TYPE_DATA_ATTRIBUTE, + SEE_MARK_PAYLOAD_DATA_ATTRIBUTES, +} from '../../shared/common-markup'; + +import defaultComponents from './default-components/default-components'; + +// Vue sets string-valued native on* props via setAttribute (see vuejs/core +// shouldSetAsProp's native-on special case), which creates LIVE inline +// handlers — unlike React, which warns and ignores string handlers. Dropping +// them keeps this adapter no more dangerous than the React one. This is the +// ONLY attribute filtering the adapter does; everything else (including +// javascript: URLs) passes through verbatim — see the README trust model. +const isInlineHandlerAttr = (name) => /^on/i.test(name); + +const toPassthroughProps = (attribs) => { + if (!attribs) return null; + const props = {}; + for (const [name, value] of Object.entries(attribs)) { + if (isInlineHandlerAttr(name)) continue; + props[name] = value; + } + return props; +}; + +// Stage 1 guarantees parseable payloads (it attribute-escapes the JSON and +// strips forged data-seemark-* from user raw HTML), so an unparseable payload +// can only mean a SeeMark bug — fail loudly instead of silently emitting a +// fallback element that would leak data-seemark-* attributes downstream. +const parsePayload = (payloadStr, type) => { + if (!payloadStr) return {}; + try { + return JSON.parse(payloadStr); + } catch (error) { + throw new Error( + `SeeMark: unparseable ${SEE_MARK_PAYLOAD_DATA_ATTRIBUTES} for component "${type}" — Stage 1/Stage 2 contract violation (${error.message})` + ); + } +}; + +const convertMarkup = (markup = '', components = {}) => { + const processedComponents = { ...defaultComponents, ...components }; + + const walkAll = (nodes) => + (nodes || []).map(walk).filter((child) => child !== null); + + const walk = (node) => { + if (node.type === 'text') return node.data; + // Comments cannot round-trip through VNodes the way they do through the + // HTML adapter's strings; the React adapter drops them too. + if (node.type === 'comment') return null; + if ( + node.type === 'tag' || + node.type === 'script' || + node.type === 'style' + ) { + const type = node.attribs?.[SEEMARK_ELEMENT_TYPE_DATA_ATTRIBUTE]; + const Component = type && processedComponents[type]; + if (Component) { + const props = parsePayload( + node.attribs[SEE_MARK_PAYLOAD_DATA_ATTRIBUTES], + type + ); + // Children travel as the default slot — the one idiom every Vue + // component form (functional, defineComponent, SFC) understands. + // Rebuilt on every slot invocation: a slot must return fresh VNodes + // each call (a component may call slots.default() twice in one + // render, or re-render later) — never a cached, possibly-mounted + // array. + return h(Component, props, { default: () => walkAll(node.children) }); + } + return h( + node.name, + toPassthroughProps(node.attribs), + walkAll(node.children) + ); + } + // cdata / directive nodes have no VNode representation. + return null; + }; + + const doc = parseDocument(markup, { decodeEntities: true }); + return walkAll(doc.children); +}; + +export default convertMarkup; diff --git a/src/markup-converters/vue/converter.test.js b/src/markup-converters/vue/converter.test.js new file mode 100644 index 0000000..902fd3a --- /dev/null +++ b/src/markup-converters/vue/converter.test.js @@ -0,0 +1,133 @@ +/** + * @jest-environment jsdom + * @jest-environment-options {"customExportConditions": ["node", "node-addons"]} + */ +import { h, defineComponent } from 'vue'; +import { mount } from '@vue/test-utils'; + +import convertMarkup from './converter'; + +const mountMarkup = (markup, components) => + mount({ render: () => h('div', convertMarkup(markup, components)) }); + +describe('vue converter', () => { + it('re-creates raw passthrough elements with text and attributes', () => { + const wrapper = mountMarkup( + '

hello world

' + ); + const p = wrapper.get('p'); + expect(p.classes()).toContain('lead'); + expect(p.attributes('style')).toBe('color: red;'); + expect(p.get('strong').text()).toBe('world'); + expect(p.text()).toBe('hello world'); + }); + + it('drops string on* attributes from raw passthrough (case-insensitive)', () => { + // Vue sets string-valued native on* props via setAttribute, which creates + // LIVE inline handlers — unlike React, which ignores string handlers. + const wrapper = mountMarkup( + '' + ); + const button = wrapper.get('button'); + expect(button.attributes('onclick')).toBeUndefined(); + expect(button.attributes('onmouseover')).toBeUndefined(); + button.element.click(); + expect(window.__seemarkPwned).toBeUndefined(); + }); + + it('keeps non-handler attributes verbatim, including data-* on unknown types', () => { + const wrapper = mountMarkup( + 'x' + ); + // Unknown seemark type falls back to passthrough, same as React/HTML. + const span = wrapper.get('span'); + expect(span.attributes('data-seemark-element-type')).toBe( + 'not-a-real-type' + ); + expect(span.attributes('data-foo')).toBe('bar'); + expect(span.text()).toBe('x'); + }); + + it('dispatches component placeholders to default components', () => { + const wrapper = mountMarkup( + `
body
` + ); + const region = wrapper.get('[role="region"]'); + expect(region.get('p').text()).toBe('NOTE'); + expect(region.text()).toContain('body'); + // The placeholder's data-seemark-* attributes must NOT leak into the DOM. + expect(wrapper.find('[data-seemark-element-type]').exists()).toBe(false); + }); + + it('lets consumer components override defaults and receive props + default slot', () => { + const CustomAlert = defineComponent({ + name: 'CustomAlert', + props: { + variant: { type: String, default: '' }, + title: { type: String, default: '' }, + internalLinkId: { type: String, default: '' }, + position: { type: Object, default: undefined }, + }, + setup(props, { slots }) { + return () => + h('section', { class: `custom-${props.variant}` }, slots.default?.()); + }, + }); + const wrapper = mountMarkup( + `
inner
`, + { alert: CustomAlert } + ); + const section = wrapper.get('section.custom-warning'); + expect(section.text()).toBe('inner'); + }); + + it('supports plain functional components as overrides', () => { + const FnAlert = ({ variant = '' }, { slots }) => + h('aside', { 'data-variant': variant }, slots.default?.()); + FnAlert.inheritAttrs = false; + const wrapper = mountMarkup( + `
fn body
`, + { alert: FnAlert } + ); + expect(wrapper.get('aside').attributes('data-variant')).toBe('tip'); + expect(wrapper.get('aside').text()).toBe('fn body'); + }); + + it('returns fresh VNodes on every slot invocation', () => { + // A component may render its default slot more than once (e.g. a visual + // copy plus an sr-only copy). Each invocation must get a fresh VNode + // tree — sharing one cached array would violate VNode uniqueness. + const TwiceAlert = (props, { slots }) => + h('div', [ + h('section', { class: 'visual' }, slots.default?.()), + h('section', { class: 'copy' }, slots.default?.()), + ]); + TwiceAlert.inheritAttrs = false; + const wrapper = mountMarkup( + `
bold text
`, + { alert: TwiceAlert } + ); + const sections = wrapper.findAll('section'); + expect(sections).toHaveLength(2); + expect(sections[0].element.innerHTML).toContain('bold'); + expect(sections[1].element.innerHTML).toContain('bold'); + // Both copies must be backed by distinct DOM nodes. + expect(sections[0].element.querySelector('strong')).not.toBe( + sections[1].element.querySelector('strong') + ); + }); + + it('throws loudly on an unparseable payload (Stage 1 contract violation)', () => { + expect(() => + convertMarkup( + '
' + ) + ).toThrow(/contract violation/); + }); + + it('drops HTML comments (VNodes have no string round-trip for them)', () => { + const wrapper = mountMarkup('

a

b

'); + expect(wrapper.element.innerHTML).not.toContain('secret'); + expect(wrapper.findAll('p')).toHaveLength(2); + }); +}); From 76113f65c54e38c734f02af4b413ee548b0198e4 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 12:47:17 +0800 Subject: [PATCH 04/19] feat(vue): add createMarkdownToVueParser pipeline factory --- src/parsers/create-markdown-to-vue-parser.js | 20 ++++++ .../create-markdown-to-vue-parser.test.js | 71 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/parsers/create-markdown-to-vue-parser.js create mode 100644 src/parsers/create-markdown-to-vue-parser.test.js diff --git a/src/parsers/create-markdown-to-vue-parser.js b/src/parsers/create-markdown-to-vue-parser.js new file mode 100644 index 0000000..2b132a2 --- /dev/null +++ b/src/parsers/create-markdown-to-vue-parser.js @@ -0,0 +1,20 @@ +import markdownProcessor from '../markdown-processor/markdown-processor'; +import convertMarkup from '../markup-converters/vue/converter'; + +import { createMarkdownParserOptions } from './options'; + +// Factory, mirroring createMarkdownToReactParser: fix the configuration once, +// render many times. Returns an array of VNodes — call it inside a render +// function (VNodes must be freshly created on every render). +const createMarkdownToVueParser = ({ options, components } = {}) => { + const parsedOptions = createMarkdownParserOptions(options); + + const parseMarkdown = (markdownContent) => { + const seemarkMarkup = markdownProcessor(markdownContent, parsedOptions); + return convertMarkup(seemarkMarkup, components); + }; + + return parseMarkdown; +}; + +export default createMarkdownToVueParser; diff --git a/src/parsers/create-markdown-to-vue-parser.test.js b/src/parsers/create-markdown-to-vue-parser.test.js new file mode 100644 index 0000000..899aa03 --- /dev/null +++ b/src/parsers/create-markdown-to-vue-parser.test.js @@ -0,0 +1,71 @@ +/** + * @jest-environment jsdom + * @jest-environment-options {"customExportConditions": ["node", "node-addons"]} + */ + +import { h } from 'vue'; +import { mount } from '@vue/test-utils'; + +import createMarkdownToVueParser from './create-markdown-to-vue-parser'; + +const OPTIONS = { + latexDelimiter: 'bracket', + asciimathDelimiter: 'graveaccent', + documentFormat: 'inline', + imageFiles: { 'pic-id': 'https://example.com/p.png' }, +}; + +const mountMarkdown = (markdown, config = {}) => { + const parse = createMarkdownToVueParser({ options: OPTIONS, ...config }); + return mount({ render: () => h('div', parse(markdown)) }); +}; + +describe('createMarkdownToVueParser', () => { + it('renders a heading through the full pipeline', () => { + const wrapper = mountMarkdown('# Hello'); + expect(wrapper.get('h1').text()).toBe('Hello'); + }); + + it('renders math with MathML in sr-only and svg aria-hidden', () => { + const wrapper = mountMarkdown('eq \\(a^2 + b^2 = c^2\\) done'); + expect(wrapper.get('span.sr-only').element.innerHTML).toContain(' { + const wrapper = mountMarkdown('![cat](pic-id)'); + expect(wrapper.get('img').attributes('src')).toBe( + 'https://example.com/p.png' + ); + }); + + it('applies consumer component overrides', () => { + const FnHeading = ({ id = null }, { slots }) => + h('h1', { id: id || null, class: 'custom-heading' }, slots.default?.()); + FnHeading.inheritAttrs = false; + const wrapper = mountMarkdown('# Custom', { + components: { heading: FnHeading }, + }); + expect(wrapper.get('h1.custom-heading').text()).toBe('Custom'); + }); + + // Trust-boundary documentation tests — these pin the DELIBERATE posture + // from the design spec (docs/superpowers/specs/2026-07-12-vue-adapter-design.md): + // the adapter is not a sanitizer; URL schemes pass through verbatim, same + // as the React adapter. Changing this behavior must be a conscious decision. + it('passes javascript: URLs through verbatim (documented trust boundary)', () => { + const wrapper = mountMarkdown('x'); + expect(wrapper.get('a').attributes('href')).toBe('javascript:alert(1)'); + }); + + it('passes data: URLs through verbatim (documented trust boundary)', () => { + const wrapper = mountMarkdown( + 'd' + ); + expect(wrapper.get('img').attributes('src')).toBe( + 'data:image/png;base64,AAAA' + ); + }); +}); From 17554ce6a417df3969f6861a58d3ab743f38b1d2 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 12:50:31 +0800 Subject: [PATCH 05/19] test: extend cross-adapter parity to a three-way React/HTML/Vue contract --- .../cross-adapter-parity.test.js | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/markup-converters/cross-adapter-parity.test.js b/src/markup-converters/cross-adapter-parity.test.js index 4b993d8..5a88e43 100644 --- a/src/markup-converters/cross-adapter-parity.test.js +++ b/src/markup-converters/cross-adapter-parity.test.js @@ -1,3 +1,7 @@ +/** + * @jest-environment jsdom + * @jest-environment-options {"customExportConditions": ["node", "node-addons"]} + */ import '@testing-library/jest-dom'; import { render, cleanup } from '@testing-library/react'; @@ -7,7 +11,12 @@ import { SUPPORTED_COMPONENT_TYPES } from '../shared/supported-components'; import { fullSyntaxMarkdown } from './html/full-syntax-fixture'; -// CONTRACT TEST — the formal drift guard between the React and HTML adapters. +import { h } from 'vue'; +import { mount } from '@vue/test-utils'; + +import createMarkdownToVueParser from '../parsers/create-markdown-to-vue-parser'; + +// CONTRACT TEST — the formal drift guard between the React, HTML and Vue adapters. // // The HTML default components are hand-written mirrors of the React ones (a // deliberate trade-off: it keeps /html framework-free instead of depending on @@ -17,6 +26,8 @@ import { fullSyntaxMarkdown } from './html/full-syntax-fixture'; // differences (self-closing tags, attribute order, className vs class, style // spacing, empty-string vs omitted attributes) are normalized away — both // outputs are parsed by the SAME DOM parser first. +// The Vue default components are a third hand-written mirror under the same +// contract: same markdown in, same semantic DOM out. // // Every type in SUPPORTED_COMPONENT_TYPES must be exercised by at least one // case (each case declares which types it `covers`; a coverage test below @@ -44,6 +55,14 @@ const toDom = (htmlString) => { return el; }; +const renderVueToDom = (markdown) => { + const parse = createMarkdownToVueParser({ + options: OPTIONS, + components: {}, + }); + return mount({ render: () => h('div', parse(markdown)) }); +}; + // Canonical, format-agnostic signature of a DOM subtree. const signature = (node) => { if (node.nodeType === Node.TEXT_NODE) { @@ -75,10 +94,13 @@ const parity = (markdown) => { const htmlSig = signature( toDom(renderToHtml(markdown, { options: OPTIONS })) ); - return { reactSig, htmlSig }; + const vueWrapper = renderVueToDom(markdown); + const vueSig = signature(vueWrapper.element); + vueWrapper.unmount(); + return { reactSig, htmlSig, vueSig }; }; -describe('cross-adapter parity (React vs HTML)', () => { +describe('cross-adapter parity (React vs HTML vs Vue)', () => { afterEach(() => cleanup()); // [name, markdown, covers] — `covers` declares which component types the @@ -141,8 +163,9 @@ describe('cross-adapter parity (React vs HTML)', () => { it.each(cases)( 'produces matching semantic DOM for: %s', (_name, markdown) => { - const { reactSig, htmlSig } = parity(markdown); + const { reactSig, htmlSig, vueSig } = parity(markdown); expect(htmlSig).toBe(reactSig); + expect(vueSig).toBe(reactSig); } ); From 881b5f7ca26748dfed1a60e425eecb6c92db172c Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 12:58:32 +0800 Subject: [PATCH 06/19] feat(vue): add SeeMark convenience component --- src/markup-converters/vue/seemark.js | 30 +++++++++ src/markup-converters/vue/seemark.test.js | 79 +++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/markup-converters/vue/seemark.js create mode 100644 src/markup-converters/vue/seemark.test.js diff --git a/src/markup-converters/vue/seemark.js b/src/markup-converters/vue/seemark.js new file mode 100644 index 0000000..5f84e90 --- /dev/null +++ b/src/markup-converters/vue/seemark.js @@ -0,0 +1,30 @@ +import { computed, defineComponent } from 'vue'; + +import createMarkdownToVueParser from '../../parsers/create-markdown-to-vue-parser'; + +// Caches only the parser (a pure function of options/components). Parsed +// VNodes are deliberately NOT cached: a VNode tree must be unique per render, +// and cloneVNode is shallow — it cannot safely clone a nested tree. Vue only +// re-runs this component's render when its own props change, so an unchanged +// `source` costs nothing. Consumers should pass stable references for +// `options`/`components` (an inline object literal in the parent re-creates +// the parser on every parent render). +const SeeMark = defineComponent({ + name: 'SeeMark', + props: { + source: { type: String, default: '' }, + options: { type: Object, default: undefined }, + components: { type: Object, default: undefined }, + }, + setup(props) { + const parse = computed(() => + createMarkdownToVueParser({ + options: props.options, + components: props.components, + }) + ); + return () => parse.value(props.source); + }, +}); + +export default SeeMark; diff --git a/src/markup-converters/vue/seemark.test.js b/src/markup-converters/vue/seemark.test.js new file mode 100644 index 0000000..a7b9588 --- /dev/null +++ b/src/markup-converters/vue/seemark.test.js @@ -0,0 +1,79 @@ +/** + * @jest-environment jsdom + * @jest-environment-options {"customExportConditions": ["node", "node-addons"]} + */ +import { h, ref, defineComponent } from 'vue'; +import { mount } from '@vue/test-utils'; + +import SeeMark from './seemark'; + +const OPTIONS = { + latexDelimiter: 'bracket', + asciimathDelimiter: 'graveaccent', + documentFormat: 'inline', + imageFiles: { 'pic-id': 'https://example.com/p.png' }, +}; + +describe('SeeMark component', () => { + it('renders markdown from the source prop', () => { + const wrapper = mount(SeeMark, { + props: { source: '# One', options: OPTIONS }, + }); + expect(wrapper.get('h1').text()).toBe('One'); + }); + + it('re-renders when source changes, including back to a previous value', async () => { + // A -> B -> A regression: a naive VNode cache keyed on source would hand + // back already-mounted VNodes on the third render and corrupt the patch. + // (Design spec: cache the parser only, never the VNodes.) + const wrapper = mount(SeeMark, { + props: { source: '# One', options: OPTIONS }, + }); + await wrapper.setProps({ source: '# Two' }); + expect(wrapper.get('h1').text()).toBe('Two'); + await wrapper.setProps({ source: '# One' }); + expect(wrapper.get('h1').text()).toBe('One'); + }); + + it('rebuilds the parser when options change', async () => { + const wrapper = mount(SeeMark, { + props: { source: 'eq $a^2$ done', options: OPTIONS }, + }); + // Under 'bracket', $...$ is not math. + expect(wrapper.find('span.sr-only').exists()).toBe(false); + await wrapper.setProps({ + options: { ...OPTIONS, latexDelimiter: 'dollar' }, + }); + expect(wrapper.find('span.sr-only').exists()).toBe(true); + }); + + it('applies and reacts to the components prop', async () => { + const FnHeading = ({ id = null }, { slots }) => + h('h1', { id: id || null, class: 'custom' }, slots.default?.()); + FnHeading.inheritAttrs = false; + const wrapper = mount(SeeMark, { + props: { source: '# T', options: OPTIONS }, + }); + expect(wrapper.find('h1.custom').exists()).toBe(false); + await wrapper.setProps({ components: { heading: FnHeading } }); + expect(wrapper.find('h1.custom').exists()).toBe(true); + }); + + it('survives unrelated parent re-renders (VNode uniqueness regression)', async () => { + const Parent = defineComponent({ + setup() { + const n = ref(0); + return () => + h('div', [ + h('button', { onClick: () => n.value++ }, String(n.value)), + h(SeeMark, { source: '# Stable', options: OPTIONS }), + ]); + }, + }); + const wrapper = mount(Parent); + await wrapper.get('button').trigger('click'); + await wrapper.get('button').trigger('click'); + expect(wrapper.get('button').text()).toBe('2'); + expect(wrapper.get('h1').text()).toBe('Stable'); + }); +}); From a5c419aec6fad542df77c1bbf602414b815a82dd Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 13:01:11 +0800 Subject: [PATCH 07/19] test(vue): add SSR + hydration round-trip coverage --- .../vue/ssr-hydration.test.js | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/markup-converters/vue/ssr-hydration.test.js diff --git a/src/markup-converters/vue/ssr-hydration.test.js b/src/markup-converters/vue/ssr-hydration.test.js new file mode 100644 index 0000000..33331a8 --- /dev/null +++ b/src/markup-converters/vue/ssr-hydration.test.js @@ -0,0 +1,129 @@ +/** + * @jest-environment jsdom + * @jest-environment-options {"customExportConditions": ["node", "node-addons"]} + */ +import { jest } from '@jest/globals'; +import { createSSRApp, defineComponent, h, ref, nextTick } from 'vue'; +import { renderToString } from '@vue/server-renderer'; + +import createMarkdownToVueParser from '../../parsers/create-markdown-to-vue-parser'; + +import { fullSyntaxMarkdown } from '../html/full-syntax-fixture'; + +const OPTIONS = { + latexDelimiter: 'bracket', + asciimathDelimiter: 'graveaccent', + documentFormat: 'inline', + imageFiles: { 'pic-id': 'https://example.com/p.png' }, +}; + +const makeApp = (markdown, components) => + createSSRApp( + defineComponent({ + setup() { + const parse = createMarkdownToVueParser({ + options: OPTIONS, + components, + }); + return () => h('div', parse(markdown)); + }, + }) + ); + +const hydrateInJsdom = async (markdown, components) => { + const html = await renderToString(makeApp(markdown, components)); + const container = document.createElement('div'); + document.body.appendChild(container); + container.innerHTML = html; + makeApp(markdown, components).mount(container); + await nextTick(); + return { html, container }; +}; + +describe('SSR + hydration', () => { + afterEach(() => { + jest.restoreAllMocks(); + document.body.innerHTML = ''; + }); + + it('hydrates a math-free document with zero mismatch warnings and reuses server DOM', async () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const error = jest.spyOn(console, 'error').mockImplementation(() => {}); + const markdown = [ + '# Title', + '', + '> [!NOTE]', + '> body', + '', + 'See [ref] and ![cat](pic-id).', + ].join('\n'); + + const html = await renderToString(makeApp(markdown)); + expect(html.length).toBeGreaterThan(0); + + const container = document.createElement('div'); + document.body.appendChild(container); + container.innerHTML = html; + const serverH1 = container.querySelector('h1'); + + makeApp(markdown).mount(container); + await nextTick(); + + // Hydration must adopt the server-rendered node, not replace it. + expect(container.querySelector('h1')).toBe(serverH1); + const logged = [...warn.mock.calls, ...error.mock.calls] + .flat() + .map(String) + .join('\n'); + expect(logged).not.toMatch(/hydrat/i); + }); + + it('SSR-renders and hydrates the full syntax document without structural replacement', async () => { + // MathJax assigns globally-incrementing element IDs (MJX-1-, MJX-2-, ...), + // so the server parse and the client parse can disagree on MJX-* + // attributes. Attribute-level hydration warnings are tolerated HERE ONLY; + // structural replacement is not. This caveat is documented in the README + // SSR section. + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'error').mockImplementation(() => {}); + const { html, container } = await hydrateInJsdom(fullSyntaxMarkdown); + expect(html).toContain(' { + const ClickAlert = defineComponent({ + name: 'ClickAlert', + props: { + variant: { type: String, default: '' }, + title: { type: String, default: '' }, + internalLinkId: { type: String, default: '' }, + position: { type: Object, default: undefined }, + }, + setup(props, { slots }) { + const clicks = ref(0); + return () => + h('div', { role: 'note' }, [ + h( + 'button', + { onClick: () => clicks.value++ }, + `clicks:${clicks.value}` + ), + slots.default?.(), + ]); + }, + }); + + const { container } = await hydrateInJsdom('> [!NOTE]\n> body', { + alert: ClickAlert, + }); + const button = container.querySelector('button'); + expect(button.textContent).toBe('clicks:0'); + button.dispatchEvent(new window.MouseEvent('click', { bubbles: true })); + await nextTick(); + expect(button.textContent).toBe('clicks:1'); + }); +}); From 01d23f6f3b0535e60e2a31c2cf42e01223ca6aa9 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 13:03:03 +0800 Subject: [PATCH 08/19] feat(vue): expose @coseeing/see-mark/vue package entry --- package.json | 9 ++++++++- rollup.config.mjs | 12 ++++++++++++ src/entries/vue.js | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/entries/vue.js diff --git a/package.json b/package.json index c01ab51..022aa6f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ }, "./html": { "default": "./lib/see-mark-html.cjs" + }, + "./vue": { + "default": "./lib/see-mark-vue.cjs" } }, "files": [ @@ -62,7 +65,8 @@ "peerDependencies": { "prop-types": "^15.8.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "vue": "^3.2.0" }, "peerDependenciesMeta": { "prop-types": { @@ -73,6 +77,9 @@ }, "react-dom": { "optional": true + }, + "vue": { + "optional": true } } } diff --git a/rollup.config.mjs b/rollup.config.mjs index 22ce378..88e2837 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -42,4 +42,16 @@ export default [ plugins: sharedPlugins, external: [/node_modules/], }, + { + input: 'src/entries/vue.js', + output: [ + { + file: 'lib/see-mark-vue.cjs', + format: 'cjs', + sourcemap: true, + }, + ], + plugins: sharedPlugins, + external: [/node_modules/], + }, ]; diff --git a/src/entries/vue.js b/src/entries/vue.js new file mode 100644 index 0000000..a5d903b --- /dev/null +++ b/src/entries/vue.js @@ -0,0 +1,16 @@ +// Package entry for `@coseeing/see-mark/vue` (built to lib/see-mark-vue.cjs). +// +// One entry per adapter, mirroring ./html. No escape utilities here: custom +// Vue components build VNodes, so there is no string context to escape into. +import latexDelimiterConvertor from '../content-processor/latext-delimiter-convertor'; + +import createMarkdownToVueParser from '../parsers/create-markdown-to-vue-parser'; +import SeeMark from '../markup-converters/vue/seemark'; +import createTableOfContents from '../table-of-contents/create-table-of-contents'; + +export { + latexDelimiterConvertor, + createMarkdownToVueParser, + SeeMark, + createTableOfContents, +}; From a0fd03f146e77d4e07a0219333b5d4093937a683 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 13:11:49 +0800 Subject: [PATCH 09/19] chore: add vue-playground example as a live consumer for manual verification --- examples/vue-playground/.gitignore | 2 + examples/vue-playground/index.html | 22 + examples/vue-playground/package-lock.json | 1372 +++++++++++++++++ examples/vue-playground/package.json | 17 + examples/vue-playground/src/main.js | 74 + .../vue-playground/src/sample-markdown.js | 25 + 6 files changed, 1512 insertions(+) create mode 100644 examples/vue-playground/.gitignore create mode 100644 examples/vue-playground/index.html create mode 100644 examples/vue-playground/package-lock.json create mode 100644 examples/vue-playground/package.json create mode 100644 examples/vue-playground/src/main.js create mode 100644 examples/vue-playground/src/sample-markdown.js diff --git a/examples/vue-playground/.gitignore b/examples/vue-playground/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/examples/vue-playground/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/examples/vue-playground/index.html b/examples/vue-playground/index.html new file mode 100644 index 0000000..d348ac9 --- /dev/null +++ b/examples/vue-playground/index.html @@ -0,0 +1,22 @@ + + + + + + SeeMark Vue Playground + + + +
+ + + diff --git a/examples/vue-playground/package-lock.json b/examples/vue-playground/package-lock.json new file mode 100644 index 0000000..86be2ed --- /dev/null +++ b/examples/vue-playground/package-lock.json @@ -0,0 +1,1372 @@ +{ + "name": "see-mark-vue-playground", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "see-mark-vue-playground", + "dependencies": { + "@coseeing/see-mark": "file:../..", + "vue": "^3.2.0" + }, + "devDependencies": { + "vite": "^6.0.0" + } + }, + "../..": { + "name": "@coseeing/see-mark", + "version": "1.11.0", + "license": "ISC", + "dependencies": { + "@coseeing/nemeth2latex": "^0.2.0", + "html-react-parser": "^5.2.5", + "htmlparser2": "^12.0.0", + "marked": "^15.0.11", + "mathjax-full": "^3.2.2" + }, + "devDependencies": { + "@babel/core": "^7.28.0", + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", + "@eslint/js": "^9.25.1", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-node-resolve": "^16.0.1", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@vue/server-renderer": "^3.5.39", + "@vue/test-utils": "^2.4.11", + "babel-jest": "^30.0.5", + "eslint": "^9.25.1", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-jest": "^28.14.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "globals": "^16.0.0", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.4", + "prettier": "^3.5.3", + "rollup": "^4.40.1", + "vue": "^3.5.39" + }, + "peerDependencies": { + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "vue": "^3.2.0" + }, + "peerDependenciesMeta": { + "prop-types": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@coseeing/see-mark": { + "resolved": "../..", + "link": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", + "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/examples/vue-playground/package.json b/examples/vue-playground/package.json new file mode 100644 index 0000000..3c5a2d2 --- /dev/null +++ b/examples/vue-playground/package.json @@ -0,0 +1,17 @@ +{ + "name": "see-mark-vue-playground", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@coseeing/see-mark": "file:../..", + "vue": "^3.2.0" + }, + "devDependencies": { + "vite": "^6.0.0" + } +} diff --git a/examples/vue-playground/src/main.js b/examples/vue-playground/src/main.js new file mode 100644 index 0000000..0c577a9 --- /dev/null +++ b/examples/vue-playground/src/main.js @@ -0,0 +1,74 @@ +import { createApp, defineComponent, h, ref } from 'vue'; +import { SeeMark } from '@coseeing/see-mark/vue'; + +import { sampleMarkdown } from './sample-markdown'; + +const OPTIONS = { + latexDelimiter: 'bracket', + asciimathDelimiter: 'graveaccent', + documentFormat: 'inline', + imageFiles: { 'pic-id': 'https://picsum.photos/240/160' }, +}; + +// Custom component override demo: a Vue component receiving payload props and +// the default slot. Declare the payload props you use (plus `position`) so +// they do not fall through as DOM attributes. +const DemoAlert = defineComponent({ + name: 'DemoAlert', + props: { + variant: { type: String, default: '' }, + title: { type: String, default: '' }, + internalLinkId: { type: String, default: '' }, + position: { type: Object, default: undefined }, + }, + setup(props, { slots }) { + return () => + h( + 'div', + { + style: 'border: 2px solid tomato; border-radius: 8px; padding: 8px;', + id: props.internalLinkId || undefined, + }, + [ + h('strong', null, `[custom ${props.variant}] ${props.title || ''}`), + slots.default?.(), + ] + ); + }, +}); + +const App = defineComponent({ + setup() { + const source = ref(sampleMarkdown); + const useCustomAlert = ref(false); + return () => + h('main', { style: 'display: flex; gap: 16px; padding: 16px;' }, [ + h('section', { style: 'width: 40%;' }, [ + h('label', null, [ + h('input', { + type: 'checkbox', + checked: useCustomAlert.value, + onChange: (e) => (useCustomAlert.value = e.target.checked), + }), + ' use custom alert component', + ]), + h('textarea', { + value: source.value, + onInput: (e) => (source.value = e.target.value), + style: 'width: 100%; min-height: 80vh; margin-top: 8px;', + }), + ]), + h('article', { style: 'width: 60%;' }, [ + h(SeeMark, { + source: source.value, + options: OPTIONS, + components: useCustomAlert.value + ? { alert: DemoAlert } + : undefined, + }), + ]), + ]); + }, +}); + +createApp(App).mount('#app'); diff --git a/examples/vue-playground/src/sample-markdown.js b/examples/vue-playground/src/sample-markdown.js new file mode 100644 index 0000000..1eb3b56 --- /dev/null +++ b/examples/vue-playground/src/sample-markdown.js @@ -0,0 +1,25 @@ +export const sampleMarkdown = `# SeeMark Vue Playground + +> [!NOTE]#note-1 +> Vue adapter demo alert with a backlink anchor. + +Jump: [to the note], or the titled variant [tooltip link][[shows a tooltip]]. + +External links: @[open in tab](https://example.com), [titled][[Example homepage]](https://example.com), @[tab + title][[Example homepage]](https://example.com). + +![inline image](pic-id) + +![linked image](pic-id)((https://example.com)) + +![captioned image][[A sample caption]](pic-id) + +![captioned linked image][[A sample caption]](pic-id)((https://example.com)) + +Math inline: \\(a^2 + b^2 = c^2\\) and AsciiMath \`sqrt(x)/2\`. + +@![Plain iframe](https://example.com/embed) + +@![YouTube](https://www.youtube.com/embed/dQw4w9WgXcQ) + +@![CodePen](https://codepen.io/team/codepen/embed/PNaGbb) +`; From 3bf5792bab80b82b31a7db380cc0f4769637a540 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 16:42:44 +0800 Subject: [PATCH 10/19] fix: load MathJax AsciiMath lazily so ESM-strict bundlers can import SeeMark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mathjax-full's AsciiMath is built on a MathJax-v2 legacy shim that calls arguments.callee during bootstrap — illegal under strict mode. Vite and esbuild convert CJS to (always-strict) ESM, so merely importing any @coseeing/see-mark entry crashed at startup; webpack consumers were unaffected because their CJS wrapper stays sloppy-mode. The AsciiMath runtime now initializes on the first conversion instead of at module scope (load-asciimath.cjs, kept as literal call-time requires via the commonjs plugin's ignore option). Importing SeeMark and rendering LaTeX/Nemeth now works under Vite; converting AsciiMath there still hits the upstream shim limitation and is documented in the README. --- rollup.config.mjs | 6 +++ .../marked-wrapper/ascii-math-to-mml.js | 29 ++--------- .../marked-wrapper/load-asciimath.cjs | 48 +++++++++++++++++++ 3 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 src/markdown-processor/marked-wrapper/load-asciimath.cjs diff --git a/rollup.config.mjs b/rollup.config.mjs index 88e2837..9e904ff 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -7,6 +7,12 @@ const sharedPlugins = [ requireReturnsDefault: (id) => { return id === 'html-react-parser'; }, + extensions: ['.js', '.cjs'], + // Keep load-asciimath.cjs's requires as literal, call-time requires + // instead of hoisting them to top-level imports: MathJax's AsciiMath + // legacy shim throws under strict mode, so it must not be evaluated + // when the bundle itself is merely imported (see load-asciimath.cjs). + ignore: (id) => id.startsWith('mathjax-full/'), }), nodeResolve(), babel({ diff --git a/src/markdown-processor/marked-wrapper/ascii-math-to-mml.js b/src/markdown-processor/marked-wrapper/ascii-math-to-mml.js index ed88bbb..e672ffe 100644 --- a/src/markdown-processor/marked-wrapper/ascii-math-to-mml.js +++ b/src/markdown-processor/marked-wrapper/ascii-math-to-mml.js @@ -21,34 +21,15 @@ * limitations under the License. */ -// -// Load the packages needed for MathJax -// -import { AsciiMath } from 'mathjax-full/js/input/asciimath.js'; -import { HTMLDocument } from 'mathjax-full/js/handlers/html/HTMLDocument.js'; -import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js'; -import { STATE } from 'mathjax-full/js/core/MathItem.js'; - -// -// Create the input jax -// -const asciimath = new AsciiMath(); - -// -// Create an HTML document using a LiteDocument and the input jax -// -const html = new HTMLDocument('', liteAdaptor(), { InputJax: asciimath }); - -// -// Create a MathML serializer -// -import { SerializedMmlVisitor } from 'mathjax-full/js/core/MmlTree/SerializedMmlVisitor.js'; -const visitor = new SerializedMmlVisitor(); -const toMathML = (node) => visitor.visitTree(node, html); +// MathJax's AsciiMath modules are loaded lazily, on the first conversion — +// see load-asciimath.cjs for why (its legacy shim crashes ESM-strict +// bundlers at import time). +import loadAsciimathRuntime from './load-asciimath.cjs'; const asciiMathToMMLFactory = ({ htmlMathDisplay }) => (mstring) => { + const { html, STATE, toMathML } = loadAsciimathRuntime(); return toMathML( html.convert(mstring || '', { display: htmlMathDisplay === 'block', diff --git a/src/markdown-processor/marked-wrapper/load-asciimath.cjs b/src/markdown-processor/marked-wrapper/load-asciimath.cjs new file mode 100644 index 0000000..6b186c3 --- /dev/null +++ b/src/markdown-processor/marked-wrapper/load-asciimath.cjs @@ -0,0 +1,48 @@ +// Lazy loader for MathJax's AsciiMath input jax. +// +// mathjax-full implements AsciiMath through a MathJax-v2 legacy shim that +// calls `arguments.callee` while bootstrapping its object system — code that +// throws under strict mode. CommonJS consumers (Node, webpack) evaluate it in +// sloppy mode and work, but ESM-strict bundlers (Vite, esbuild) throw AT +// IMPORT TIME if these modules are required at the top level, which used to +// make `import '@coseeing/see-mark/*'` crash any Vite app on startup. +// +// Keeping the requires inside the function defers that evaluation to the +// first actual AsciiMath conversion: merely importing SeeMark (and rendering +// LaTeX/Nemeth) never touches the shim. Converting AsciiMath under an +// ESM-strict bundler still throws — an upstream mathjax-full limitation, +// documented in the README; `enableAsciimath: false` avoids it entirely. +// +// This file is CommonJS on purpose: ESM `import` cannot be deferred, and +// rollup's commonjs plugin is configured (`ignore` in rollup.config.mjs) to +// leave these requires in place instead of hoisting them into top-level +// imports. + +let cached = null; + +const loadAsciimathRuntime = () => { + if (!cached) { + const { AsciiMath } = require('mathjax-full/js/input/asciimath.js'); + const { + HTMLDocument, + } = require('mathjax-full/js/handlers/html/HTMLDocument.js'); + const { liteAdaptor } = require('mathjax-full/js/adaptors/liteAdaptor.js'); + const { STATE } = require('mathjax-full/js/core/MathItem.js'); + const { + SerializedMmlVisitor, + } = require('mathjax-full/js/core/MmlTree/SerializedMmlVisitor.js'); + + const asciimath = new AsciiMath(); + const html = new HTMLDocument('', liteAdaptor(), { InputJax: asciimath }); + const visitor = new SerializedMmlVisitor(); + + cached = { + html, + STATE, + toMathML: (node) => visitor.visitTree(node, html), + }; + } + return cached; +}; + +module.exports = loadAsciimathRuntime; From 8201a008ac04807933acf5ae2b2358613e187f15 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 16:44:05 +0800 Subject: [PATCH 11/19] chore(playground): vite config for symlinked CJS dep; disable AsciiMath under Vite --- examples/vue-playground/src/main.js | 5 ++++- .../vue-playground/src/sample-markdown.js | 2 +- examples/vue-playground/vite.config.js | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 examples/vue-playground/vite.config.js diff --git a/examples/vue-playground/src/main.js b/examples/vue-playground/src/main.js index 0c577a9..f090ac5 100644 --- a/examples/vue-playground/src/main.js +++ b/examples/vue-playground/src/main.js @@ -5,7 +5,10 @@ import { sampleMarkdown } from './sample-markdown'; const OPTIONS = { latexDelimiter: 'bracket', - asciimathDelimiter: 'graveaccent', + // AsciiMath is disabled: MathJax's AsciiMath legacy shim cannot run under + // ESM-strict bundlers like Vite (see the README's bundler-compatibility + // note). LaTeX and Nemeth work fine. + enableAsciimath: false, documentFormat: 'inline', imageFiles: { 'pic-id': 'https://picsum.photos/240/160' }, }; diff --git a/examples/vue-playground/src/sample-markdown.js b/examples/vue-playground/src/sample-markdown.js index 1eb3b56..f3bf1dd 100644 --- a/examples/vue-playground/src/sample-markdown.js +++ b/examples/vue-playground/src/sample-markdown.js @@ -15,7 +15,7 @@ External links: @[open in tab](https://example.com), [titled][[Example homepage] ![captioned linked image][[A sample caption]](pic-id)((https://example.com)) -Math inline: \\(a^2 + b^2 = c^2\\) and AsciiMath \`sqrt(x)/2\`. +Math inline: \\(a^2 + b^2 = c^2\\) rendered by MathJax (AsciiMath is disabled under Vite — see README). @![Plain iframe](https://example.com/embed) diff --git a/examples/vue-playground/vite.config.js b/examples/vue-playground/vite.config.js new file mode 100644 index 0000000..78585e9 --- /dev/null +++ b/examples/vue-playground/vite.config.js @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; + +// `@coseeing/see-mark` is consumed via a `file:` dependency, which npm +// installs as a symlink. Vite's dependency crawler treats symlinked +// ("linked") packages as local source and skips its default CJS->ESM +// interop, so the browser receives the raw CommonJS build +// (`require(...)` / `exports.SeeMark = ...`) as an ES module and fails +// with "does not provide an export named 'SeeMark'". +// +// Explicitly listing it in optimizeDeps.include forces esbuild to +// pre-bundle it (with CJS interop) like a normal dependency. +export default defineConfig({ + optimizeDeps: { + include: ['@coseeing/see-mark/vue'], + }, + // mathjax-full (a SeeMark dependency) references the Node.js `global` + // global at module scope, which doesn't exist in the browser. Standard + // Vite workaround: alias it to `globalThis`. + define: { + global: 'globalThis', + }, +}); From e586be40fe0a5fd31b144816aa2b21d75c5cef2b Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 17:01:51 +0800 Subject: [PATCH 12/19] fix(vue): ship an ESM build to avoid the dual-Vue-runtime hazard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browser verification caught custom `components` overrides silently not reacting under Vite: the CJS-only build makes bundlers resolve require('vue') to vue.cjs.js while the app imports the esm-bundler build, so two Vue reactivity instances coexist and props tracked by one never invalidate computeds created by the other. The vue entry now also builds to lib/see-mark-vue.mjs with `import 'vue'` kept intact; exports route bundlers to it (import) while Node — CJS and ESM alike — stays on the CJS build (node condition), where AsciiMath keeps working. AsciiMath init failures now rethrow one actionable message covering both the ESM require gap and the strict-mode shim crash. mathjax-full import specifiers gain explicit .js extensions so the ESM output resolves under Node's stricter ESM rules too. --- package.json | 2 + rollup.config.mjs | 12 +++++ .../marked-wrapper/load-asciimath.cjs | 48 ++++++++++++------- .../marked-wrapper/tex-to-mml.js | 8 ++-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 022aa6f..42019e5 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "default": "./lib/see-mark-html.cjs" }, "./vue": { + "node": "./lib/see-mark-vue.cjs", + "import": "./lib/see-mark-vue.mjs", "default": "./lib/see-mark-vue.cjs" } }, diff --git a/rollup.config.mjs b/rollup.config.mjs index 9e904ff..9575d13 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -56,6 +56,18 @@ export default [ format: 'cjs', sourcemap: true, }, + // The Vue entry also ships as ESM. A CJS-only build makes bundlers + // resolve `require('vue')` to vue.cjs.js while the consuming app + // imports the esm-bundler build — two separate Vue reactivity + // instances in one page, so props tracked by the app's copy never + // invalidate computeds created by SeeMark's copy (custom `components` + // overrides silently stop reacting). The ESM build keeps `import 'vue'` + // intact so the consumer's bundler resolves a single Vue. + { + file: 'lib/see-mark-vue.mjs', + format: 'es', + sourcemap: true, + }, ], plugins: sharedPlugins, external: [/node_modules/], diff --git a/src/markdown-processor/marked-wrapper/load-asciimath.cjs b/src/markdown-processor/marked-wrapper/load-asciimath.cjs index 6b186c3..28efe16 100644 --- a/src/markdown-processor/marked-wrapper/load-asciimath.cjs +++ b/src/markdown-processor/marked-wrapper/load-asciimath.cjs @@ -22,25 +22,39 @@ let cached = null; const loadAsciimathRuntime = () => { if (!cached) { - const { AsciiMath } = require('mathjax-full/js/input/asciimath.js'); - const { - HTMLDocument, - } = require('mathjax-full/js/handlers/html/HTMLDocument.js'); - const { liteAdaptor } = require('mathjax-full/js/adaptors/liteAdaptor.js'); - const { STATE } = require('mathjax-full/js/core/MathItem.js'); - const { - SerializedMmlVisitor, - } = require('mathjax-full/js/core/MmlTree/SerializedMmlVisitor.js'); + // Any failure here — `require` missing in an ESM build, or the legacy + // shim's strict-mode crash under a bundler — gets one actionable message + // instead of a cryptic internal error. (Don't gate on `typeof require`: + // the commonjs plugin rewrites that expression during bundling.) + try { + const { AsciiMath } = require('mathjax-full/js/input/asciimath.js'); + const { + HTMLDocument, + } = require('mathjax-full/js/handlers/html/HTMLDocument.js'); + const { + liteAdaptor, + } = require('mathjax-full/js/adaptors/liteAdaptor.js'); + const { STATE } = require('mathjax-full/js/core/MathItem.js'); + const { + SerializedMmlVisitor, + } = require('mathjax-full/js/core/MmlTree/SerializedMmlVisitor.js'); - const asciimath = new AsciiMath(); - const html = new HTMLDocument('', liteAdaptor(), { InputJax: asciimath }); - const visitor = new SerializedMmlVisitor(); + const asciimath = new AsciiMath(); + const html = new HTMLDocument('', liteAdaptor(), { InputJax: asciimath }); + const visitor = new SerializedMmlVisitor(); - cached = { - html, - STATE, - toMathML: (node) => visitor.visitTree(node, html), - }; + cached = { + html, + STATE, + toMathML: (node) => visitor.visitTree(node, html), + }; + } catch (error) { + throw new Error( + 'SeeMark: AsciiMath is unavailable in this environment (MathJax legacy shim limitation — ' + + `${error.message}). Set enableAsciimath: false, or consume the CommonJS build; ` + + 'see the README bundler notes.' + ); + } } return cached; }; diff --git a/src/markdown-processor/marked-wrapper/tex-to-mml.js b/src/markdown-processor/marked-wrapper/tex-to-mml.js index c249930..6b08e94 100644 --- a/src/markdown-processor/marked-wrapper/tex-to-mml.js +++ b/src/markdown-processor/marked-wrapper/tex-to-mml.js @@ -24,10 +24,10 @@ // // Load the packages needed for MathJax // -import { TeX } from 'mathjax-full/js/input/tex'; -import { HTMLDocument } from 'mathjax-full/js/handlers/html/HTMLDocument'; -import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor'; -import { STATE } from 'mathjax-full/js/core/MathItem'; +import { TeX } from 'mathjax-full/js/input/tex.js'; +import { HTMLDocument } from 'mathjax-full/js/handlers/html/HTMLDocument.js'; +import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js'; +import { STATE } from 'mathjax-full/js/core/MathItem.js'; import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages.js'; From 3d343ab5b71e9c0a7033555f5341d8b35fc8d06c Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 17:13:44 +0800 Subject: [PATCH 13/19] fix(playground): dedupe vue across the file:-linked layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The symlinked SeeMark repo carries its own node_modules/vue (a devDependency), so esbuild inlined a second full Vue runtime into the prebundled chunk — two reactivity instances, and the components-prop toggle silently stopped reacting. resolve.dedupe forces every vue import to this app's copy (registry consumers are unaffected: npm never installs a dependency's devDependencies). Also define the __VUE_* feature flags normally injected by @vitejs/plugin-vue, silencing the esm-bundler warning. --- examples/vue-playground/vite.config.js | 31 +++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/vue-playground/vite.config.js b/examples/vue-playground/vite.config.js index 78585e9..ebf3f0c 100644 --- a/examples/vue-playground/vite.config.js +++ b/examples/vue-playground/vite.config.js @@ -1,22 +1,27 @@ import { defineConfig } from 'vite'; // `@coseeing/see-mark` is consumed via a `file:` dependency, which npm -// installs as a symlink. Vite's dependency crawler treats symlinked -// ("linked") packages as local source and skips its default CJS->ESM -// interop, so the browser receives the raw CommonJS build -// (`require(...)` / `exports.SeeMark = ...`) as an ES module and fails -// with "does not provide an export named 'SeeMark'". -// -// Explicitly listing it in optimizeDeps.include forces esbuild to -// pre-bundle it (with CJS interop) like a normal dependency. +// installs as a symlink into the SeeMark repo — where a second copy of vue +// lives (SeeMark's devDependency). Without dedupe, SeeMark's `import 'vue'` +// resolves to that copy and the page ends up with two Vue reactivity +// instances, silently breaking cross-boundary reactivity (e.g. the custom +// `components` prop). Registry consumers never hit this — npm does not +// install a dependency's devDependencies — it is purely a linked-package +// development artifact. `dedupe` forces every `vue` import to resolve to +// this app's copy. export default defineConfig({ - optimizeDeps: { - include: ['@coseeing/see-mark/vue'], + resolve: { + dedupe: ['vue'], }, - // mathjax-full (a SeeMark dependency) references the Node.js `global` - // global at module scope, which doesn't exist in the browser. Standard - // Vite workaround: alias it to `globalThis`. + // `global`: mathjax-full (a SeeMark dependency) references the Node.js + // `global` at module scope, which doesn't exist in the browser — alias it + // to `globalThis`. The __VUE_* feature flags are normally injected by + // @vitejs/plugin-vue; this playground has no SFCs and skips the plugin, so + // define them here to silence Vue's esm-bundler warning. define: { global: 'globalThis', + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, }, }); From 1147083a767a0165bb9de00f13dba17578b92dbb Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 17:15:11 +0800 Subject: [PATCH 14/19] docs: document the Vue adapter, custom components, SSR, bundlers and trust model --- README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/README.md b/README.md index b64973e..a67e9df 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,118 @@ interface Position { } ``` +## Vue + +The `@coseeing/see-mark/vue` entry renders the same markdown pipeline to Vue 3 +VNodes. Vue 3.2+ is required (declared as an optional peer dependency — React +users are unaffected). + +### Usage + +```js +import { createApp, defineComponent, h, ref } from 'vue'; +import { SeeMark, createMarkdownToVueParser } from '@coseeing/see-mark/vue'; + +// Option 1: the component (idiomatic for most apps) +const App = defineComponent({ + setup() { + const source = ref('# Hello \\(a^2 + b^2 = c^2\\)'); + return () => h(SeeMark, { source: source.value, options: OPTIONS }); + }, +}); + +// Option 2: the parser factory (mirrors createMarkdownToReactParser) +const parse = createMarkdownToVueParser({ options: OPTIONS }); +const MyDoc = defineComponent({ + props: { markdown: String }, + setup(props) { + return () => h('article', parse(props.markdown)); + }, +}); +``` + +`options` accepts the same table as the React parser (see above). +`createMarkdownToVueParser` returns a function producing a fresh VNode array — +call it inside a render function. `` re-parses when `source` changes +and rebuilds its parser when `options`/`components` change; pass stable object +references for `options`/`components` (an inline literal re-creates the parser +on every parent render). + +### Custom components + +A custom component is an ordinary Vue 3 component (functional, `defineComponent` +or SFC). Payload arrives as props; children arrive through the **default slot**. +Declare the payload props you use (plus `position`) — undeclared payload keys +would otherwise fall through onto the root element as DOM attributes. + +```js +const Alert = defineComponent({ + props: { + variant: { type: String, default: '' }, + title: { type: String, default: '' }, + internalLinkId: { type: String, default: '' }, + position: { type: Object, default: undefined }, + }, + setup(props, { slots }) { + return () => + h('div', { class: `alert alert-${props.variant}` }, [ + props.title ? h('strong', null, props.title) : null, + slots.default?.(), + ]); + }, +}); + +h(SeeMark, { source, options: OPTIONS, components: { alert: Alert } }); +``` + +Errors thrown by a custom component surface at mount/render time and follow +Vue's normal error path (`app.config.errorHandler`), not at parse time. + +### SSR + +The Vue adapter is SSR-safe (`@vue/server-renderer` / Nuxt): MathJax runs in +the parsing stage, not in components. One caveat: MathJax assigns +globally-incrementing element IDs, so a server parse and a client parse of the +same document can disagree on `MJX-*` attribute values, which may surface as +attribute-level hydration warnings in dev builds. Structure and content +hydrate correctly. + +### Bundler compatibility + +The `/vue` entry ships two builds: bundlers get an ESM build +(`import` condition) so their single `vue` copy is shared with your app, while +Node — CommonJS and ESM alike — gets the CommonJS build (`node` condition). +Two practical notes: + +- **AsciiMath under ESM-strict bundlers (Vite, esbuild) is unavailable.** + MathJax implements AsciiMath through a MathJax-v2 legacy shim that cannot + run under strict mode. SeeMark loads it lazily, so importing SeeMark and + rendering LaTeX/Nemeth work everywhere; converting AsciiMath in an + ESM-strict bundle throws a descriptive error. Set `enableAsciimath: false` + in that environment (backticks then render as ordinary code spans). + Server-side (Node/webpack) AsciiMath is unaffected. +- **Linked-package development**: if you consume SeeMark via `npm link` / + `file:` during development, add `resolve.dedupe: ['vue']` to your Vite + config — the linked repo carries its own `node_modules/vue`, and two Vue + runtimes on one page silently break reactivity across the component + boundary. Registry installs are unaffected. See + `examples/vue-playground/vite.config.js` for a working setup (including the + `global` → `globalThis` define that mathjax-full needs in browsers). + +## Security / trust model + +SeeMark adapters are **not sanitizers**. Raw HTML in the markdown source +passes through to the output — including `javascript:` URLs — matching the +React adapter's behavior. If you render untrusted markdown, sanitize it at the +source, or sanitize the HTML adapter's string output with DOMPurify via its +`sanitize` hook. + +The Vue adapter makes exactly one exception: string-valued `on*` attributes +(e.g. `onclick="..."`) are dropped from raw passthrough elements. Vue would +otherwise attach them as live inline handlers (React ignores string handlers), +which would make the Vue adapter strictly more dangerous than the React one. +Event handling on your own custom components (`@click`, `v-on`) is unaffected. + ## Table of Contents ### Usage From 07565c008c6b99521c5d846d89c2d0243c34576a Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Sun, 12 Jul 2026 18:21:19 +0800 Subject: [PATCH 15/19] fix(vue): neutralize script execution, ref/key hijack, and SVG attr drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deep review (multi-agent, adversarially verified) found three ways the Vue adapter diverged from its 'no more dangerous than React' posture, all reproduced empirically: - raw after' + ); + expect(wrapper.find('script').exists()).toBe(false); + expect(window.__seemarkScriptRan).toBeUndefined(); + expect(wrapper.text()).toContain('before'); + expect(wrapper.text()).toContain('after'); + }); + + it('strips Vue reserved props (ref/key) from raw passthrough', () => { + // ref/key handed to h() become framework directives, not attributes: + // `ref` would hijack the consuming component instance's $refs. + const refs = {}; + mount({ + render() { + return h('div', convertMarkup('x')); + }, + mounted() { + refs.hijack = this.$refs.hijack; + }, + }); + expect(refs.hijack).toBeUndefined(); + }); + + it('restores camelCase SVG attribute names lowercased by the HTML parser', () => { + // htmlparser2 lowercases viewBox -> viewbox; Vue's SVG setAttribute is + // case-sensitive, so without restoration the attribute is inert. + const wrapper = mountMarkup( + '' + ); + const svg = wrapper.get('svg').element; + expect(svg.getAttribute('viewBox')).toBe('0 0 100 100'); + expect(svg.getAttribute('preserveAspectRatio')).toBe('xMidYMid'); + }); + it('keeps non-handler attributes verbatim, including data-* on unknown types', () => { const wrapper = mountMarkup( 'x' diff --git a/src/markup-converters/vue/ssr-hydration.test.js b/src/markup-converters/vue/ssr-hydration.test.js index 33331a8..4fdc934 100644 --- a/src/markup-converters/vue/ssr-hydration.test.js +++ b/src/markup-converters/vue/ssr-hydration.test.js @@ -86,9 +86,23 @@ describe('SSR + hydration', () => { // SSR section. jest.spyOn(console, 'warn').mockImplementation(() => {}); jest.spyOn(console, 'error').mockImplementation(() => {}); - const { html, container } = await hydrateInJsdom(fullSyntaxMarkdown); + + // Inlined (not via hydrateInJsdom) so we can capture a server node BEFORE + // hydration and prove it was ADOPTED, not replaced. The h1 carries no + // MJX-* id, so — unlike the math subtree — it is a clean structural-reuse + // signal unaffected by MathJax's per-parse id drift. + const html = await renderToString(makeApp(fullSyntaxMarkdown)); expect(html).toContain(' Date: Sun, 12 Jul 2026 19:11:35 +0800 Subject: [PATCH 16/19] revert(vue): drop the ESM build, keep /vue CommonJS-only like React/HTML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ESM build was added on a wrong diagnosis of the dual-Vue-runtime bug. An isolated experiment (clean registry-style layout + real Vite build) shows a registry consumer works fine with the CJS-only build: Vite's optimizeDeps unifies require('vue') and the app's import 'vue' into a single Vue instance (the bundle's reactive core appears exactly once). The real dual-Vue cause is the playground's file:/symlink layout, where the SeeMark repo's own vue devDependency becomes a second physical copy — fixed by resolve.dedupe, independent of build format. Registry installs never install a dependency's devDependencies, so they never hit it. So /vue reverts to CommonJS-only, matching the React and HTML entries. Lazy AsciiMath loading stays (bundlers convert even CJS to strict ESM, so it's needed regardless of build format). The playground keeps dedupe and regains optimizeDeps.include (Vite skips CJS pre-bundling for linked packages). Browser-verified: custom components toggle correctly (no dual-Vue) and the script/ref/svg security fixes remain intact. --- README.md | 28 ++++++++------- examples/vue-playground/vite.config.js | 34 +++++++++++-------- package.json | 2 -- rollup.config.mjs | 12 ------- .../marked-wrapper/load-asciimath.cjs | 22 ++++++------ 5 files changed, 46 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index ee8bbed..b9c3c46 100644 --- a/README.md +++ b/README.md @@ -181,23 +181,25 @@ hydrate correctly. ### Bundler compatibility -The `/vue` entry ships two builds: bundlers get an ESM build -(`import` condition) so their single `vue` copy is shared with your app, while -Node — CommonJS and ESM alike — gets the CommonJS build (`node` condition). -Two practical notes: +Like the React and HTML entries, `/vue` ships a CommonJS bundle. Bundlers +(Vite, webpack) pre-bundle it to ESM automatically and share your app's single +`vue` copy; no extra config is needed for a normal registry install. Two +notes: - **AsciiMath under ESM-strict bundlers (Vite, esbuild) is unavailable.** MathJax implements AsciiMath through a MathJax-v2 legacy shim that cannot - run under strict mode. SeeMark loads it lazily, so importing SeeMark and - rendering LaTeX/Nemeth work everywhere; converting AsciiMath in an - ESM-strict bundle throws a descriptive error. Set `enableAsciimath: false` - in that environment (backticks then render as ordinary code spans). - Server-side (Node/webpack) AsciiMath is unaffected. + run under strict mode, and bundlers convert even CommonJS deps to + always-strict ESM. SeeMark loads it lazily, so importing SeeMark and + rendering LaTeX/Nemeth work everywhere; converting AsciiMath in such a + bundle throws a descriptive error. Set `enableAsciimath: false` in that + environment (backticks then render as ordinary code spans). Server-side + (Node/webpack) AsciiMath is unaffected. - **Linked-package development**: if you consume SeeMark via `npm link` / - `file:` during development, add `resolve.dedupe: ['vue']` to your Vite - config — the linked repo carries its own `node_modules/vue`, and two Vue - runtimes on one page silently break reactivity across the component - boundary. Registry installs are unaffected. See + `file:` during development, add `optimizeDeps.include: ['@coseeing/see-mark/vue']` + and `resolve.dedupe: ['vue']` to your Vite config — Vite skips CJS→ESM + pre-bundling for symlinked packages, and the linked repo carries its own + `node_modules/vue` (two Vue runtimes on one page silently break reactivity + across the component boundary). Registry installs need neither. See `examples/vue-playground/vite.config.js` for a working setup (including the `global` → `globalThis` define that mathjax-full needs in browsers). diff --git a/examples/vue-playground/vite.config.js b/examples/vue-playground/vite.config.js index ebf3f0c..dc83da7 100644 --- a/examples/vue-playground/vite.config.js +++ b/examples/vue-playground/vite.config.js @@ -1,23 +1,29 @@ import { defineConfig } from 'vite'; -// `@coseeing/see-mark` is consumed via a `file:` dependency, which npm -// installs as a symlink into the SeeMark repo — where a second copy of vue -// lives (SeeMark's devDependency). Without dedupe, SeeMark's `import 'vue'` -// resolves to that copy and the page ends up with two Vue reactivity -// instances, silently breaking cross-boundary reactivity (e.g. the custom -// `components` prop). Registry consumers never hit this — npm does not -// install a dependency's devDependencies — it is purely a linked-package -// development artifact. `dedupe` forces every `vue` import to resolve to -// this app's copy. +// SeeMark ships a CommonJS bundle. A normal registry install lets Vite +// auto-optimize it into ESM with no config; this playground needs two tweaks +// only because it consumes SeeMark through a `file:` symlink: +// +// - `optimizeDeps.include`: Vite skips CJS->ESM pre-bundling for symlinked +// ("linked") packages, so without this the browser receives raw +// `require(...)` / `exports.SeeMark = ...` and fails. Listing the subpath +// forces esbuild to pre-bundle it like a real dependency. +// - `resolve.dedupe`: the symlinked SeeMark repo carries its own +// node_modules/vue (a devDependency), so two Vue reactivity instances would +// otherwise coexist and cross-boundary reactivity (the custom `components` +// prop) would silently break. Registry consumers never hit either issue — +// npm does not install a dependency's devDependencies. export default defineConfig({ + optimizeDeps: { + include: ['@coseeing/see-mark/vue'], + }, resolve: { dedupe: ['vue'], }, - // `global`: mathjax-full (a SeeMark dependency) references the Node.js - // `global` at module scope, which doesn't exist in the browser — alias it - // to `globalThis`. The __VUE_* feature flags are normally injected by - // @vitejs/plugin-vue; this playground has no SFCs and skips the plugin, so - // define them here to silence Vue's esm-bundler warning. + // `global`: mathjax-full references the Node.js `global` at module scope, + // which doesn't exist in the browser. The __VUE_* feature flags are + // normally injected by @vitejs/plugin-vue; this playground has no SFCs and + // skips the plugin, so define them here to silence Vue's esm-bundler warning. define: { global: 'globalThis', __VUE_OPTIONS_API__: true, diff --git a/package.json b/package.json index 42019e5..022aa6f 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,6 @@ "default": "./lib/see-mark-html.cjs" }, "./vue": { - "node": "./lib/see-mark-vue.cjs", - "import": "./lib/see-mark-vue.mjs", "default": "./lib/see-mark-vue.cjs" } }, diff --git a/rollup.config.mjs b/rollup.config.mjs index 9575d13..9e904ff 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -56,18 +56,6 @@ export default [ format: 'cjs', sourcemap: true, }, - // The Vue entry also ships as ESM. A CJS-only build makes bundlers - // resolve `require('vue')` to vue.cjs.js while the consuming app - // imports the esm-bundler build — two separate Vue reactivity - // instances in one page, so props tracked by the app's copy never - // invalidate computeds created by SeeMark's copy (custom `components` - // overrides silently stop reacting). The ESM build keeps `import 'vue'` - // intact so the consumer's bundler resolves a single Vue. - { - file: 'lib/see-mark-vue.mjs', - format: 'es', - sourcemap: true, - }, ], plugins: sharedPlugins, external: [/node_modules/], diff --git a/src/markdown-processor/marked-wrapper/load-asciimath.cjs b/src/markdown-processor/marked-wrapper/load-asciimath.cjs index 28efe16..57957da 100644 --- a/src/markdown-processor/marked-wrapper/load-asciimath.cjs +++ b/src/markdown-processor/marked-wrapper/load-asciimath.cjs @@ -7,11 +7,14 @@ // IMPORT TIME if these modules are required at the top level, which used to // make `import '@coseeing/see-mark/*'` crash any Vite app on startup. // -// Keeping the requires inside the function defers that evaluation to the -// first actual AsciiMath conversion: merely importing SeeMark (and rendering -// LaTeX/Nemeth) never touches the shim. Converting AsciiMath under an -// ESM-strict bundler still throws — an upstream mathjax-full limitation, -// documented in the README; `enableAsciimath: false` avoids it entirely. +// SeeMark ships a CommonJS bundle, but bundlers (Vite, esbuild) pre-bundle +// even CJS deps by converting them to always-strict ESM, so the crash still +// applies to bundler consumers. Keeping the requires inside the function +// defers that evaluation to the first actual AsciiMath conversion: merely +// importing SeeMark (and rendering LaTeX/Nemeth) never touches the shim. +// Converting AsciiMath under such a bundler still throws — an upstream +// mathjax-full limitation, documented in the README; `enableAsciimath: false` +// avoids it entirely. // // This file is CommonJS on purpose: ESM `import` cannot be deferred, and // rollup's commonjs plugin is configured (`ignore` in rollup.config.mjs) to @@ -22,10 +25,8 @@ let cached = null; const loadAsciimathRuntime = () => { if (!cached) { - // Any failure here — `require` missing in an ESM build, or the legacy - // shim's strict-mode crash under a bundler — gets one actionable message - // instead of a cryptic internal error. (Don't gate on `typeof require`: - // the commonjs plugin rewrites that expression during bundling.) + // The legacy shim's strict-mode crash under a bundler gets one actionable + // message instead of a cryptic internal error. try { const { AsciiMath } = require('mathjax-full/js/input/asciimath.js'); const { @@ -51,8 +52,7 @@ const loadAsciimathRuntime = () => { } catch (error) { throw new Error( 'SeeMark: AsciiMath is unavailable in this environment (MathJax legacy shim limitation — ' + - `${error.message}). Set enableAsciimath: false, or consume the CommonJS build; ` + - 'see the README bundler notes.' + `${error.message}). Set enableAsciimath: false; see the README bundler notes.` ); } } From 15f40b2936d66f21d119904272d7222c89cb5c4c Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Tue, 14 Jul 2026 11:48:11 +0800 Subject: [PATCH 17/19] chore: pull vue-only playground out of the library PR - remove examples/vue-playground: only the Vue adapter had an example, so keep this branch focused on the library and defer the playground to a separate PR - drop the leftover .js import extensions in tex-to-mml.js (dead remnant of the reverted ESM build; the CJS bundle resolves extension-less imports) - restore .gitignore to main so staged-delivery evidence stays untracked rather than gitignored - README: remove the dangling examples/vue-playground reference, keeping the global->globalThis note inline --- .gitignore | 3 - README.md | 6 +- examples/vue-playground/.gitignore | 2 - examples/vue-playground/index.html | 22 - examples/vue-playground/package-lock.json | 1372 ----------------- examples/vue-playground/package.json | 17 - examples/vue-playground/src/main.js | 77 - .../vue-playground/src/sample-markdown.js | 25 - examples/vue-playground/vite.config.js | 33 - .../marked-wrapper/tex-to-mml.js | 8 +- 10 files changed, 7 insertions(+), 1558 deletions(-) delete mode 100644 examples/vue-playground/.gitignore delete mode 100644 examples/vue-playground/index.html delete mode 100644 examples/vue-playground/package-lock.json delete mode 100644 examples/vue-playground/package.json delete mode 100644 examples/vue-playground/src/main.js delete mode 100644 examples/vue-playground/src/sample-markdown.js delete mode 100644 examples/vue-playground/vite.config.js diff --git a/.gitignore b/.gitignore index 95c4c11..933a8ab 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ coverage/ npm-debug.log* .DS_Store - -# staged-delivery local verification evidence (recordings, screenshots) — never committed -.staged-delivery/ diff --git a/README.md b/README.md index b9c3c46..e8b402c 100644 --- a/README.md +++ b/README.md @@ -199,9 +199,9 @@ notes: and `resolve.dedupe: ['vue']` to your Vite config — Vite skips CJS→ESM pre-bundling for symlinked packages, and the linked repo carries its own `node_modules/vue` (two Vue runtimes on one page silently break reactivity - across the component boundary). Registry installs need neither. See - `examples/vue-playground/vite.config.js` for a working setup (including the - `global` → `globalThis` define that mathjax-full needs in browsers). + across the component boundary). Registry installs need neither. In the + browser you may also need a `global` → `globalThis` define, since + mathjax-full references the Node.js `global` at module scope. ## Security / trust model diff --git a/examples/vue-playground/.gitignore b/examples/vue-playground/.gitignore deleted file mode 100644 index f06235c..0000000 --- a/examples/vue-playground/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/examples/vue-playground/index.html b/examples/vue-playground/index.html deleted file mode 100644 index d348ac9..0000000 --- a/examples/vue-playground/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - SeeMark Vue Playground - - - -
- - - diff --git a/examples/vue-playground/package-lock.json b/examples/vue-playground/package-lock.json deleted file mode 100644 index 86be2ed..0000000 --- a/examples/vue-playground/package-lock.json +++ /dev/null @@ -1,1372 +0,0 @@ -{ - "name": "see-mark-vue-playground", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "see-mark-vue-playground", - "dependencies": { - "@coseeing/see-mark": "file:../..", - "vue": "^3.2.0" - }, - "devDependencies": { - "vite": "^6.0.0" - } - }, - "../..": { - "name": "@coseeing/see-mark", - "version": "1.11.0", - "license": "ISC", - "dependencies": { - "@coseeing/nemeth2latex": "^0.2.0", - "html-react-parser": "^5.2.5", - "htmlparser2": "^12.0.0", - "marked": "^15.0.11", - "mathjax-full": "^3.2.2" - }, - "devDependencies": { - "@babel/core": "^7.28.0", - "@babel/preset-env": "^7.28.3", - "@babel/preset-react": "^7.27.1", - "@eslint/js": "^9.25.1", - "@rollup/plugin-babel": "^6.0.4", - "@rollup/plugin-commonjs": "^28.0.3", - "@rollup/plugin-node-resolve": "^16.0.1", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.3.0", - "@vue/server-renderer": "^3.5.39", - "@vue/test-utils": "^2.4.11", - "babel-jest": "^30.0.5", - "eslint": "^9.25.1", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-jest": "^28.14.0", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-prettier": "^5.2.6", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", - "globals": "^16.0.0", - "jest": "^30.0.0", - "jest-environment-jsdom": "^30.0.4", - "prettier": "^3.5.3", - "rollup": "^4.40.1", - "vue": "^3.5.39" - }, - "peerDependencies": { - "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "vue": "^3.2.0" - }, - "peerDependenciesMeta": { - "prop-types": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@coseeing/see-mark": { - "resolved": "../..", - "link": true - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", - "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", - "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", - "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", - "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", - "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", - "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", - "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", - "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", - "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", - "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", - "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", - "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", - "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", - "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", - "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", - "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", - "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", - "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", - "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", - "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", - "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", - "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", - "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", - "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", - "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", - "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.39", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", - "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", - "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.39", - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.15", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", - "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", - "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", - "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", - "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/runtime-core": "3.5.39", - "@vue/shared": "3.5.39", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", - "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39" - }, - "peerDependencies": { - "vue": "3.5.39" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", - "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", - "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", - "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.2", - "@rollup/rollup-android-arm64": "4.62.2", - "@rollup/rollup-darwin-arm64": "4.62.2", - "@rollup/rollup-darwin-x64": "4.62.2", - "@rollup/rollup-freebsd-arm64": "4.62.2", - "@rollup/rollup-freebsd-x64": "4.62.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", - "@rollup/rollup-linux-arm-musleabihf": "4.62.2", - "@rollup/rollup-linux-arm64-gnu": "4.62.2", - "@rollup/rollup-linux-arm64-musl": "4.62.2", - "@rollup/rollup-linux-loong64-gnu": "4.62.2", - "@rollup/rollup-linux-loong64-musl": "4.62.2", - "@rollup/rollup-linux-ppc64-gnu": "4.62.2", - "@rollup/rollup-linux-ppc64-musl": "4.62.2", - "@rollup/rollup-linux-riscv64-gnu": "4.62.2", - "@rollup/rollup-linux-riscv64-musl": "4.62.2", - "@rollup/rollup-linux-s390x-gnu": "4.62.2", - "@rollup/rollup-linux-x64-gnu": "4.62.2", - "@rollup/rollup-linux-x64-musl": "4.62.2", - "@rollup/rollup-openbsd-x64": "4.62.2", - "@rollup/rollup-openharmony-arm64": "4.62.2", - "@rollup/rollup-win32-arm64-msvc": "4.62.2", - "@rollup/rollup-win32-ia32-msvc": "4.62.2", - "@rollup/rollup-win32-x64-gnu": "4.62.2", - "@rollup/rollup-win32-x64-msvc": "4.62.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", - "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-sfc": "3.5.39", - "@vue/runtime-dom": "3.5.39", - "@vue/server-renderer": "3.5.39", - "@vue/shared": "3.5.39" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - } - } -} diff --git a/examples/vue-playground/package.json b/examples/vue-playground/package.json deleted file mode 100644 index 3c5a2d2..0000000 --- a/examples/vue-playground/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "see-mark-vue-playground", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@coseeing/see-mark": "file:../..", - "vue": "^3.2.0" - }, - "devDependencies": { - "vite": "^6.0.0" - } -} diff --git a/examples/vue-playground/src/main.js b/examples/vue-playground/src/main.js deleted file mode 100644 index f090ac5..0000000 --- a/examples/vue-playground/src/main.js +++ /dev/null @@ -1,77 +0,0 @@ -import { createApp, defineComponent, h, ref } from 'vue'; -import { SeeMark } from '@coseeing/see-mark/vue'; - -import { sampleMarkdown } from './sample-markdown'; - -const OPTIONS = { - latexDelimiter: 'bracket', - // AsciiMath is disabled: MathJax's AsciiMath legacy shim cannot run under - // ESM-strict bundlers like Vite (see the README's bundler-compatibility - // note). LaTeX and Nemeth work fine. - enableAsciimath: false, - documentFormat: 'inline', - imageFiles: { 'pic-id': 'https://picsum.photos/240/160' }, -}; - -// Custom component override demo: a Vue component receiving payload props and -// the default slot. Declare the payload props you use (plus `position`) so -// they do not fall through as DOM attributes. -const DemoAlert = defineComponent({ - name: 'DemoAlert', - props: { - variant: { type: String, default: '' }, - title: { type: String, default: '' }, - internalLinkId: { type: String, default: '' }, - position: { type: Object, default: undefined }, - }, - setup(props, { slots }) { - return () => - h( - 'div', - { - style: 'border: 2px solid tomato; border-radius: 8px; padding: 8px;', - id: props.internalLinkId || undefined, - }, - [ - h('strong', null, `[custom ${props.variant}] ${props.title || ''}`), - slots.default?.(), - ] - ); - }, -}); - -const App = defineComponent({ - setup() { - const source = ref(sampleMarkdown); - const useCustomAlert = ref(false); - return () => - h('main', { style: 'display: flex; gap: 16px; padding: 16px;' }, [ - h('section', { style: 'width: 40%;' }, [ - h('label', null, [ - h('input', { - type: 'checkbox', - checked: useCustomAlert.value, - onChange: (e) => (useCustomAlert.value = e.target.checked), - }), - ' use custom alert component', - ]), - h('textarea', { - value: source.value, - onInput: (e) => (source.value = e.target.value), - style: 'width: 100%; min-height: 80vh; margin-top: 8px;', - }), - ]), - h('article', { style: 'width: 60%;' }, [ - h(SeeMark, { - source: source.value, - options: OPTIONS, - components: useCustomAlert.value - ? { alert: DemoAlert } - : undefined, - }), - ]), - ]); - }, -}); - -createApp(App).mount('#app'); diff --git a/examples/vue-playground/src/sample-markdown.js b/examples/vue-playground/src/sample-markdown.js deleted file mode 100644 index f3bf1dd..0000000 --- a/examples/vue-playground/src/sample-markdown.js +++ /dev/null @@ -1,25 +0,0 @@ -export const sampleMarkdown = `# SeeMark Vue Playground - -> [!NOTE]#note-1 -> Vue adapter demo alert with a backlink anchor. - -Jump: [to the note], or the titled variant [tooltip link][[shows a tooltip]]. - -External links: @[open in tab](https://example.com), [titled][[Example homepage]](https://example.com), @[tab + title][[Example homepage]](https://example.com). - -![inline image](pic-id) - -![linked image](pic-id)((https://example.com)) - -![captioned image][[A sample caption]](pic-id) - -![captioned linked image][[A sample caption]](pic-id)((https://example.com)) - -Math inline: \\(a^2 + b^2 = c^2\\) rendered by MathJax (AsciiMath is disabled under Vite — see README). - -@![Plain iframe](https://example.com/embed) - -@![YouTube](https://www.youtube.com/embed/dQw4w9WgXcQ) - -@![CodePen](https://codepen.io/team/codepen/embed/PNaGbb) -`; diff --git a/examples/vue-playground/vite.config.js b/examples/vue-playground/vite.config.js deleted file mode 100644 index dc83da7..0000000 --- a/examples/vue-playground/vite.config.js +++ /dev/null @@ -1,33 +0,0 @@ -import { defineConfig } from 'vite'; - -// SeeMark ships a CommonJS bundle. A normal registry install lets Vite -// auto-optimize it into ESM with no config; this playground needs two tweaks -// only because it consumes SeeMark through a `file:` symlink: -// -// - `optimizeDeps.include`: Vite skips CJS->ESM pre-bundling for symlinked -// ("linked") packages, so without this the browser receives raw -// `require(...)` / `exports.SeeMark = ...` and fails. Listing the subpath -// forces esbuild to pre-bundle it like a real dependency. -// - `resolve.dedupe`: the symlinked SeeMark repo carries its own -// node_modules/vue (a devDependency), so two Vue reactivity instances would -// otherwise coexist and cross-boundary reactivity (the custom `components` -// prop) would silently break. Registry consumers never hit either issue — -// npm does not install a dependency's devDependencies. -export default defineConfig({ - optimizeDeps: { - include: ['@coseeing/see-mark/vue'], - }, - resolve: { - dedupe: ['vue'], - }, - // `global`: mathjax-full references the Node.js `global` at module scope, - // which doesn't exist in the browser. The __VUE_* feature flags are - // normally injected by @vitejs/plugin-vue; this playground has no SFCs and - // skips the plugin, so define them here to silence Vue's esm-bundler warning. - define: { - global: 'globalThis', - __VUE_OPTIONS_API__: true, - __VUE_PROD_DEVTOOLS__: false, - __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, - }, -}); diff --git a/src/markdown-processor/marked-wrapper/tex-to-mml.js b/src/markdown-processor/marked-wrapper/tex-to-mml.js index 6b08e94..c249930 100644 --- a/src/markdown-processor/marked-wrapper/tex-to-mml.js +++ b/src/markdown-processor/marked-wrapper/tex-to-mml.js @@ -24,10 +24,10 @@ // // Load the packages needed for MathJax // -import { TeX } from 'mathjax-full/js/input/tex.js'; -import { HTMLDocument } from 'mathjax-full/js/handlers/html/HTMLDocument.js'; -import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js'; -import { STATE } from 'mathjax-full/js/core/MathItem.js'; +import { TeX } from 'mathjax-full/js/input/tex'; +import { HTMLDocument } from 'mathjax-full/js/handlers/html/HTMLDocument'; +import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor'; +import { STATE } from 'mathjax-full/js/core/MathItem'; import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages.js'; From 7ea34aa39fd138aadee9ff051f4dc6d3c1232fd7 Mon Sep 17 00:00:00 2001 From: jamliaoo Date: Tue, 14 Jul 2026 12:13:26 +0800 Subject: [PATCH 18/19] docs: tighten Vue adapter comments to rationale over mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trim the comments added with the Vue adapter so each states why, not how: drop Vue-internal function references, the strict-mode/arguments.callee mechanics, cloneVNode internals, and step-by-step restatements of the code. The security rationale (why on*/script/ref are dropped) is kept — it is the part that stops the guard being refactored away. --- rollup.config.mjs | 7 +-- .../marked-wrapper/load-asciimath.cjs | 29 ++++------ src/markup-converters/vue/converter.js | 56 +++++++------------ .../vue/default-components/image-link.js | 5 +- .../vue/default-components/math.js | 6 +- src/markup-converters/vue/seemark.js | 10 +--- 6 files changed, 39 insertions(+), 74 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 9e904ff..b00e8cc 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -8,10 +8,9 @@ const sharedPlugins = [ return id === 'html-react-parser'; }, extensions: ['.js', '.cjs'], - // Keep load-asciimath.cjs's requires as literal, call-time requires - // instead of hoisting them to top-level imports: MathJax's AsciiMath - // legacy shim throws under strict mode, so it must not be evaluated - // when the bundle itself is merely imported (see load-asciimath.cjs). + // Leave load-asciimath.cjs's requires call-time instead of hoisting them + // to top-level imports — MathJax's AsciiMath shim must not be evaluated + // when the bundle is merely imported (see load-asciimath.cjs). ignore: (id) => id.startsWith('mathjax-full/'), }), nodeResolve(), diff --git a/src/markdown-processor/marked-wrapper/load-asciimath.cjs b/src/markdown-processor/marked-wrapper/load-asciimath.cjs index 57957da..ac9f9a8 100644 --- a/src/markdown-processor/marked-wrapper/load-asciimath.cjs +++ b/src/markdown-processor/marked-wrapper/load-asciimath.cjs @@ -1,25 +1,16 @@ // Lazy loader for MathJax's AsciiMath input jax. // -// mathjax-full implements AsciiMath through a MathJax-v2 legacy shim that -// calls `arguments.callee` while bootstrapping its object system — code that -// throws under strict mode. CommonJS consumers (Node, webpack) evaluate it in -// sloppy mode and work, but ESM-strict bundlers (Vite, esbuild) throw AT -// IMPORT TIME if these modules are required at the top level, which used to -// make `import '@coseeing/see-mark/*'` crash any Vite app on startup. +// MathJax's AsciiMath is a legacy shim that crashes under strict mode. +// ESM-strict bundlers (Vite, esbuild) would hit that crash AT IMPORT TIME if +// these modules were required at the top level — enough to break `import +// '@coseeing/see-mark'` on startup. Requiring them lazily, on the first +// conversion, means merely importing SeeMark (and rendering LaTeX/Nemeth) +// never touches the shim. Converting AsciiMath under such a bundler still +// throws; see the README (`enableAsciimath: false` avoids it). // -// SeeMark ships a CommonJS bundle, but bundlers (Vite, esbuild) pre-bundle -// even CJS deps by converting them to always-strict ESM, so the crash still -// applies to bundler consumers. Keeping the requires inside the function -// defers that evaluation to the first actual AsciiMath conversion: merely -// importing SeeMark (and rendering LaTeX/Nemeth) never touches the shim. -// Converting AsciiMath under such a bundler still throws — an upstream -// mathjax-full limitation, documented in the README; `enableAsciimath: false` -// avoids it entirely. -// -// This file is CommonJS on purpose: ESM `import` cannot be deferred, and -// rollup's commonjs plugin is configured (`ignore` in rollup.config.mjs) to -// leave these requires in place instead of hoisting them into top-level -// imports. +// CommonJS on purpose: an ESM `import` cannot be deferred like this. Rollup's +// commonjs plugin is told (`ignore` in rollup.config.mjs) to leave these +// requires in place. let cached = null; diff --git a/src/markup-converters/vue/converter.js b/src/markup-converters/vue/converter.js index 88c8ddc..b732df3 100644 --- a/src/markup-converters/vue/converter.js +++ b/src/markup-converters/vue/converter.js @@ -8,30 +8,20 @@ import { import defaultComponents from './default-components/default-components'; -// Vue sets string-valued native on* props via setAttribute (see vuejs/core -// shouldSetAsProp's native-on special case), which creates LIVE inline -// handlers — unlike React, which warns and ignores string handlers. Dropping -// them keeps this adapter no more dangerous than the React one. Everything -// else (including javascript: URLs) passes through verbatim — see the README -// trust model. +// Vue turns string on* attributes into live inline handlers; React ignores +// them. Drop them so this adapter is no more dangerous than React — everything +// else (javascript: URLs included) passes through, per the README trust model. const isInlineHandlerAttr = (name) => /^on/i.test(name); -// Vue reserves these vnode props: handed to h() they become framework -// directives, not DOM attributes. Raw user markup must never reach them — -// `ref` would register on the CONSUMING component's $refs (letting untrusted -// content clobber a ref the app relies on), and `key`/`ref_for`/`ref_key` -// corrupt keyed diffing across re-renders. htmlparser2 lowercases attribute -// names, so a lowercase check suffices. +// Vue treats these as framework directives, not DOM attributes. Untrusted +// markup must never reach them: `ref` would register on the consuming +// component's $refs, and `key`/`ref_for`/`ref_key` corrupt keyed diffing. const RESERVED_VNODE_PROPS = new Set(['key', 'ref', 'ref_for', 'ref_key']); -// htmlparser2 lowercases attribute names when parsing HTML, which turns SVG's -// case-sensitive camelCase attributes into inert ones: Vue applies attributes -// on an SVG element via setAttribute, which (unlike on HTML elements) -// preserves case, so `viewbox` never behaves as `viewBox`. The React adapter -// (html-react-parser) and the browser's own SVG foreign-content parsing both -// restore this casing; we mirror them. Keyed by the lowercased name -// htmlparser2 emits. (MathJax's own SVG never travels this path — the math -// component injects it via innerHTML — so this only affects raw inline SVG.) +// htmlparser2 lowercases attribute names, which breaks SVG's case-sensitive +// camelCase attributes (`viewbox` never acts as `viewBox`). Restore the +// casing, as the React adapter and the browser's SVG parser do. Only raw +// inline SVG hits this — MathJax's SVG is injected via innerHTML. const SVG_CAMELCASE_ATTRS = { allowreorder: 'allowReorder', attributename: 'attributeName', @@ -108,10 +98,8 @@ const toPassthroughProps = (attribs) => { return props; }; -// Stage 1 guarantees parseable payloads (it attribute-escapes the JSON and -// strips forged data-seemark-* from user raw HTML), so an unparseable payload -// can only mean a SeeMark bug — fail loudly instead of silently emitting a -// fallback element that would leak data-seemark-* attributes downstream. +// Stage 1 guarantees parseable payloads, so a parse failure can only be a +// SeeMark bug — fail loudly instead of silently leaking data-seemark-* markup. const parsePayload = (payloadStr, type) => { if (!payloadStr) return {}; try { @@ -131,8 +119,7 @@ const convertMarkup = (markup = '', components = {}) => { const walk = (node) => { if (node.type === 'text') return node.data; - // Comments cannot round-trip through VNodes the way they do through the - // HTML adapter's strings; the React adapter drops them too. + // No VNode representation for comments; the React adapter drops them too. if (node.type === 'comment') return null; if ( node.type === 'tag' || @@ -146,19 +133,14 @@ const convertMarkup = (markup = '', components = {}) => { node.attribs[SEE_MARK_PAYLOAD_DATA_ATTRIBUTES], type ); - // Children travel as the default slot — the one idiom every Vue - // component form (functional, defineComponent, SFC) understands. - // Rebuilt on every slot invocation: a slot must return fresh VNodes - // each call (a component may call slots.default() twice in one - // render, or re-render later) — never a cached, possibly-mounted - // array. + // Children travel as the default slot (the idiom every Vue component + // form understands). Rebuilt per invocation, never cached: a slot must + // return fresh VNodes each call. return h(Component, props, { default: () => walkAll(node.children) }); } - // A raw