diff --git a/babel.config.mjs b/babel.config.mjs index 0763b74..47f6669 100644 --- a/babel.config.mjs +++ b/babel.config.mjs @@ -1,5 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { resolvePathsUsingDecorators, litDecoratorsBabelOptions } from './config/babel.mjs' + +const projectRoot = path.dirname(fileURLToPath(import.meta.url)) +const pathsUsingDecorators = resolvePathsUsingDecorators(projectRoot) + export default { presets: [ + '@babel/preset-typescript', [ '@babel/preset-env', { @@ -17,5 +25,11 @@ export default { ] } ] + ], + overrides: [ + { + include: pathsUsingDecorators, + ...litDecoratorsBabelOptions, + } ] } diff --git a/config/babel.mjs b/config/babel.mjs new file mode 100644 index 0000000..ad0f600 --- /dev/null +++ b/config/babel.mjs @@ -0,0 +1,31 @@ +import path from 'path' + +/** + * This file contains config options for babel using Lit decorators. + * + * @see https://lit.dev/docs/components/decorators/#using-decorators-with-babel + */ + +const pathsUsingDecorators = ['src/design-system', 'src/primitives', 'src/storybook', 'src/components'] + +export const litDecoratorsBabelOptions = { + assumptions: { + setPublicClassFields: true, + privateFieldsAsSymbols: true + }, + plugins: [ + '@babel/plugin-transform-class-static-block', + ['@babel/plugin-transform-typescript', { allowDeclareFields: true }], + ['@babel/plugin-proposal-decorators', { version: '2023-05' }], + '@babel/plugin-transform-class-properties' + ] +} + +export const litDecoratorsLoaderOptions = { + cacheDirectory: true, + ...litDecoratorsBabelOptions, +} + +export function resolvePathsUsingDecorators (projectRoot) { + return pathsUsingDecorators.map((_path) => path.resolve(projectRoot, _path)) +} diff --git a/config/postcss.mjs b/config/postcss.mjs new file mode 100644 index 0000000..0880f95 --- /dev/null +++ b/config/postcss.mjs @@ -0,0 +1,12 @@ +import PostCSS from 'postcss' +import TailwindCSS from '@tailwindcss/postcss' + +const cssProcessor = PostCSS([TailwindCSS()]) + +export default { + async transform (css, { filePath }) { + const result = await cssProcessor.process(css, { from: filePath }) + + return result.css + } +} diff --git a/declarations.d.ts b/declarations.d.ts new file mode 100644 index 0000000..40e729a --- /dev/null +++ b/declarations.d.ts @@ -0,0 +1,7 @@ +declare module '*.styles.css' { + import type { CSSResultGroup } from 'lit' + const styles: CSSResultGroup + export default styles +} + +declare module '*.css'; \ No newline at end of file diff --git a/dev/index.js b/dev/index.js index 7bdd311..6a522d7 100644 --- a/dev/index.js +++ b/dev/index.js @@ -33,8 +33,8 @@ finishLogin() // const targetURIToShow = "https://timbl.com/timbl/Automation/mother/tracker.n3#mother" // const targetURIToShow = 'https://sstratsianis.solidcommunity.net/TestingTracker/index.ttl#this' -const targetURIToShow = 'https://testingsolidos.solidcommunity.net/profile/card#me' - +// const targetURIToShow = 'https://testingsolidos.solidcommunity.net/profile/card#me' +const targetURIToShow = 'https://sharontest.solidcommunity.net/profile/card#me' const context = { dom: document, session: { diff --git a/jest.config.mjs b/jest.config.mjs index ba800be..172a891 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -6,7 +6,7 @@ export default { customExportConditions: ['node'] }, setupFilesAfterEnv: ["./test/helpers/jest.setup.js"], - transformIgnorePatterns: ["/node_modules/(?!lit-html).+\\.js"], + transformIgnorePatterns: ["/node_modules/(?!(@lit|@lit-labs|lit|lit-html|lit-element)/).+\\.js$"], roots: ['/src', '/test'], moduleNameMapper: { '^SolidLogic$': 'solid-logic', diff --git a/package-lock.json b/package-lock.json index c40247a..24ff4bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,30 @@ "name": "source-pane", "version": "3.1.0", "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.12.3", + "@codemirror/legacy-modes": "^6.5.3", + "@codemirror/state": "^6.6.0", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.43.0", + "codemirror": "^6.0.2" + }, "devDependencies": { "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-proposal-decorators": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7", "@babel/preset-env": "^7.28.6", + "@babel/preset-typescript": "^7.29.7", + "@tailwindcss/postcss": "^4.3.0", "@testing-library/dom": "^10.4.1", + "@types/mime-types": "^3.0.1", "babel-loader": "^10.0.0", "babel-plugin-inline-import": "^3.0.0", "copy-webpack-plugin": "^14.0.0", @@ -21,7 +41,9 @@ "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "jest-fetch-mock": "^3.0.3", + "lit-css-loader": "^4.0.1", "node-polyfill-webpack-plugin": "^4.1.0", + "postcss": "^8.5.15", "rdflib": "^2.3.6", "solid-logic": "^4.0.6", "solid-ui": "^3.1.0", @@ -37,6 +59,19 @@ "solid-ui": "^3.1.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -542,6 +577,24 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -610,6 +663,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", @@ -669,13 +738,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -795,13 +864,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1616,6 +1685,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", @@ -1784,6 +1873,26 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -1849,6 +1958,184 @@ "dev": true, "license": "MIT" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.3.tgz", + "integrity": "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.6.tgz", + "integrity": "sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.0.tgz", + "integrity": "sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.0.tgz", + "integrity": "sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@colordx/core": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@colordx/core/-/core-5.4.3.tgz", + "integrity": "sha512-kIxYSfA5T8HXjav55UaaH/o/cKivF6jCCGIb8eqtcsfI46wsvlSiT8jMDyrl779qLec3c2c2oHBZo4oAhvbjrQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -3301,6 +3588,85 @@ "dev": true, "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", + "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lit-labs/ssr-dom-shim": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", @@ -3318,6 +3684,12 @@ "@lit-labs/ssr-dom-shim": "^1.5.0" } }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", @@ -3559,6 +3931,16 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@pwrs/lit-css": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@pwrs/lit-css/-/lit-css-4.1.0.tgz", + "integrity": "sha512-h4FGHzbiposqASNcdzofgsROa0r27DbpknyImJmMIUvCU4B0M+STLbEW76dnNKjwFB9mQMe1O4YonRUyBIujPg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cssnano": "^7.0.7" + } + }, "node_modules/@rdfjs/types": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-2.0.1.tgz", @@ -3596,67 +3978,350 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "android" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", + "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "postcss": "^8.5.10", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } }, "node_modules/@types/babel__template": { "version": "7.4.4", @@ -3831,6 +4496,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", @@ -5471,6 +6143,19 @@ "node": ">=6" } }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001790", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", @@ -5735,6 +6420,21 @@ "node": ">= 0.12.0" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -6027,6 +6727,12 @@ "dev": true, "license": "MIT" }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-fetch": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", @@ -6079,6 +6785,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/css-declaration-sorter": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, "node_modules/css-loader": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", @@ -6145,6 +6864,20 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", @@ -6171,33 +6904,148 @@ "node": ">=4" } }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "node_modules/cssnano": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.9.tgz", + "integrity": "sha512-uPR75+5Dk/WJ/YSPR1/YDHdwMM9c5FsaARljfKWgeCKLKOtJ0we21xy/RcCjn53fZnD/f6yYEIZ8pu18+GnbNQ==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "cssnano-preset-default": "^7.0.17", + "lilconfig": "^3.1.3" }, "engines": { - "node": ">=18" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/cssnano-preset-default": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.17.tgz", + "integrity": "sha512-11qO63A+czwguQFJCaTdICvbaxn0pJzz/XghLlv+OT7WyToDxAMR0Xb3/26/l0y0hQJywwNbj/SLSQlGBHE1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.3", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.10", + "postcss-convert-values": "^7.0.12", + "postcss-discard-comments": "^7.0.8", + "postcss-discard-duplicates": "^7.0.4", + "postcss-discard-empty": "^7.0.3", + "postcss-discard-overridden": "^7.0.3", + "postcss-merge-longhand": "^7.0.7", + "postcss-merge-rules": "^7.0.11", + "postcss-minify-font-values": "^7.0.3", + "postcss-minify-gradients": "^7.0.5", + "postcss-minify-params": "^7.0.9", + "postcss-minify-selectors": "^7.1.2", + "postcss-normalize-charset": "^7.0.3", + "postcss-normalize-display-values": "^7.0.3", + "postcss-normalize-positions": "^7.0.4", + "postcss-normalize-repeat-style": "^7.0.4", + "postcss-normalize-string": "^7.0.3", + "postcss-normalize-timing-functions": "^7.0.3", + "postcss-normalize-unicode": "^7.0.9", + "postcss-normalize-url": "^7.0.3", + "postcss-normalize-whitespace": "^7.0.3", + "postcss-ordered-values": "^7.0.4", + "postcss-reduce-initial": "^7.0.9", + "postcss-reduce-transforms": "^7.0.3", + "postcss-svgo": "^7.1.3", + "postcss-unique-selectors": "^7.0.7" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "node_modules/cssnano-utils": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.3.tgz", + "integrity": "sha512-ynIREMICLxkxm7e9bCR9sh75s4Q5drICi0ua1yxo5jH2XPBqSKkl4dOh4EbFqtUmnTMhRffHgYL0EKKkMjtJTg==", "dev": true, "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, "engines": { - "node": ">=18" - } + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } }, "node_modules/debug": { "version": "4.4.3", @@ -6397,6 +7245,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9875,6 +10733,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", @@ -10113,6 +10981,292 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/limit-it": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/limit-it/-/limit-it-3.2.11.tgz", @@ -10131,9 +11285,9 @@ "license": "MIT" }, "node_modules/lit": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", - "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", + "integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -10142,6 +11296,17 @@ "lit-html": "^3.3.0" } }, + "node_modules/lit-css-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lit-css-loader/-/lit-css-loader-4.0.1.tgz", + "integrity": "sha512-7dU6j9OmkDzcmPTUQSHC6pLudjOVqgzsa3R0/5hMQSjPo1IAuhUEw70fDT1HeqNr2DlIgk0YCKHdDuxudeSoZA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@pwrs/lit-css": "^4.0.0", + "loader-utils": "^3.3.1" + } + }, "node_modules/lit-element": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", @@ -10178,6 +11343,16 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10208,6 +11383,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -10215,6 +11397,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -10255,6 +11444,16 @@ "lz-string": "bin/bin.js" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -10316,6 +11515,13 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -10551,9 +11757,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -11513,216 +12719,623 @@ "node": ">=0.10.0" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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/postcss-calc": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12 || ^20.9 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/postcss-colormin": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.10.tgz", + "integrity": "sha512-yFr6JezOolHLta/buLE71VKPh2mXursp4saVe98/ol8ZnEWhL+racShqPKlvd/DKWLre/39B6HhcMXf7RZ3hxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colordx/core": "^5.4.3", + "browserslist": "^4.28.2", + "caniuse-api": "^3.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-convert-values": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.12.tgz", + "integrity": "sha512-xurKu5qqk4viR3Cp3p4xBR4KfnZm4w4ys6+UBwBmeuBSNkH7+DtLnYOYnOffgtE4yx8sH9S1VZ6RAAvROXzP2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-discard-comments": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.8.tgz", + "integrity": "sha512-CvvS5S9WrXblFXCEJ9nVo+4z+eA7zSC7Z88V1HEJuwlQhlFnYTIjg1xJY+BCUiG2bvICap2tXii4mP22BD108Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.4.tgz", + "integrity": "sha512-VBNn1+EuMZkeGVVtz0gRfbNGtx9IFgAsAV+E2pHtXPrp4qfGBkhTIiAuE/wrb+Y6Pakg9NewAlfTpYIFAWODtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-discard-empty": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.3.tgz", + "integrity": "sha512-M2pyjQCU+/7cMHVtL6bKTHjv0lZnPLMpicgr67Dlth7AbuV9gjVTtUqaRwn6Pp6BwSDspUzhz8SaUrRykJU5Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.3.tgz", + "integrity": "sha512-aNovXo9UsZuRNLzHJtp13lHIvinDPfiXBPePpXkSjCbgp++iU2FqE+YxvjIsg6EdyPZsASFbfu+JcBFVsErXIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.7.tgz", + "integrity": "sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^7.0.11" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-merge-rules": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.11.tgz", + "integrity": "sha512-SJUPM18g2BmPhf8BVlbwqWz4aK3pLu6u6xjfwEzra7xL6IBR10sUaiB++EzqcVfadPHrKBSMlNdP+XieykhI+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^5.0.3", + "postcss-selector-parser": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.3.tgz", + "integrity": "sha512-yilG/VOaNI74IylQvAQQxm3/wZVBkXyYUqNUAdxqwtbWUXPsbK1q8Ms0mL83v+f8YicgcyfYCRZtWACUdYajpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.5.tgz", + "integrity": "sha512-YraROyQRg3BI1+Hg8E05B/JPdnTm8EDSVu4P2BxdM+CRiOyfmou809+chGIqo6fQqwjPGQ947nbGncSjmTU1WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colordx/core": "^5.4.3", + "cssnano-utils": "^5.0.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-minify-params": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.9.tgz", + "integrity": "sha512-R8itbB8BhlpoYyBm1ou0dD+vJnQ3F6adQipR4UnkCHUwlo+S9WXJaDRg1RHjC8YVAtIdrQzSWvJl40HnGDTKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "cssnano-utils": "^5.0.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.1.2.tgz", + "integrity": "sha512-aQtrEWKwqafNlExcKHQvPGsXR2+vlUqqJtf5XsCQcgsSb5PL4wlujWBYDJuWsP4UnQX1YHDHU8qRlD+1PzTQ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-api": "^3.0.0", + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.3.tgz", + "integrity": "sha512-NoBfZu8PR4c2NlmjvrqQTzCzLY79hwcSRgNQ3ZiNK0ABzf9kYKloE/jNj+/8GQY1wsm8pRRgANk6ydLH8cwo0Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/postcss-normalize-display-values": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.3.tgz", + "integrity": "sha512-ldsCX0QIt05pKIOobZtVQ48wXJecr+czw4+e1/YjVhLMqslShgpVxgPtI2CefURR8oyVoYaU/l829MMwExDMLw==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/postcss-normalize-positions": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.4.tgz", + "integrity": "sha512-VEvlpeGd3Ju1Hqa/oN4jaP3+ms4laYwkEL9N9u+B6k54PZjXbW1n6wI+aVprf1BQXlCYpS5+1pl/7/vHiKgARg==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/postcss-normalize-repeat-style": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.4.tgz", + "integrity": "sha512-6mPKlY/8cSaDHxX502wERADarJsccwlky6yIrOapHH2ZgfoKAV94SbiTKfKEs4EEpdazuc3J72WsqeYk7hp9+Q==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/postcss-normalize-string": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.3.tgz", + "integrity": "sha512-HnEQPUchi1eznmDKEYrKUTqrprEq97SrpUYClgUkv7V2zRODD9DFoUsYU+m9ZOetmD5ku7fEMZB/lwy8IT6xVQ==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/postcss-normalize-timing-functions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.3.tgz", + "integrity": "sha512-zmEzHdvpZBZu0OKlbJSfgASQvaayyAoVuWtvyr34IJ/LyS+DaOKvvR3EvFJ9RWWtNIx+CMvO125OVophaxNYew==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/pkijs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", - "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@noble/hashes": "1.4.0", - "asn1js": "^3.0.6", - "bytestreamjs": "^2.0.1", - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", - "tslib": "^2.8.1" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/pkijs/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "node_modules/postcss-normalize-unicode": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.9.tgz", + "integrity": "sha512-DRAdWfeh/TjmhLJsw91vdiWCnUod9iwvM7xyS02/nF/sLsCR3A8l3pztrSUrWG8DSBqfX7yEk9FM0USaVJ2mSg==", "dev": true, "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">= 16" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "node_modules/postcss-normalize-url": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.3.tgz", + "integrity": "sha512-CL93wmloq5qsffmFv+bw24MIRbmhHrp53qoh1LDAb/5TtjWEXI/np4xcP/Gw9oWCb2XyWnqHYLDUwiKRoJBA1Q==", "dev": true, "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">= 0.4" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" } }, - "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "node_modules/postcss-normalize-whitespace": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.3.tgz", + "integrity": "sha512-FdHjjn+Ht5Z2ZRjNOmeCbNq6lq09sUYKpmlF/Aq0XjVNSLTL6fmHlA/3swN2wP2caY9GV/tjSDcIIyS7aN7W0A==", "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.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.5.13" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "node_modules/postcss-ordered-values": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.4.tgz", + "integrity": "sha512-nubSi49hDHQk4E8KIj+IbLY8Bg+8OcSUEhgyolgM+atnOvXjV7EjaR6bac4YGZoFyPa9mWoAF3EaYbWdFkKqVg==", "dev": true, "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" + "cssnano-utils": "^5.0.3", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.5.13" } }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "node_modules/postcss-reduce-initial": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.9.tgz", + "integrity": "sha512-ztTNPdIxXTxtBcG03E9u8v44M4ElXbMIRT7pf2onlquGula0Y83nKKxqM22FA/hMgkfCjN7ohevkVlaNwI8iOQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" + "browserslist": "^4.28.2", + "caniuse-api": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.5.13" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/postcss-reduce-transforms": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.3.tgz", + "integrity": "sha512-FXsnN9ZwcZTT8Yf8cAHA8qIGUXcX6WfLd9JoYhrdDfmvsVhhfqkkv7m4AC3rwFOfz+GzkUa87OCKF9dUcicd+g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.5.13" } }, "node_modules/postcss-selector-parser": { @@ -11739,6 +13352,39 @@ "node": ">=4" } }, + "node_modules/postcss-svgo": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.3.tgz", + "integrity": "sha512-2QfoFOYMcj8lwcVEf9WeTlkVIAm7u2QvOEhMzkQU3KUhhGX/l8hVV9EtjMv4iq3E9iI3OeeMN0YoMLbGusuigw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^4.0.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.7.tgz", + "integrity": "sha512-d+sCkaRnSefghOUdH8CMJZV9yUQhj2ojpe8Nw/lA+LV1UOfeleGkLTl6XdCFFSai9UJ+DJPb69FFuqthXYsY8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -12594,6 +14240,16 @@ "dev": true, "license": "MIT" }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -13538,6 +15194,29 @@ "webpack": "^5.27.0" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/stylehacks": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.11.tgz", + "integrity": "sha512-iODNfhXVLqc5LADs+Y6Oh5wJuK5ZcHbVng8aiK3y9pjMQdc5hLrBW0eFU6FtnpNrE6PoEg/MmFTU4waotj5WNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "postcss-selector-parser": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.5.13" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13564,6 +15243,118 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/svgo/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -13587,6 +15378,13 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", @@ -14420,6 +16218,12 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "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 86441fd..0972171 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "LICENSE" ], "sideEffects": [ - "**/*.css" + "**/*.css", + "src/components/**/*.ts" ], "scripts": { "clean": "rm -rf lib", @@ -56,8 +57,15 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-proposal-decorators": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7", "@babel/preset-env": "^7.28.6", + "@babel/preset-typescript": "^7.29.7", + "@tailwindcss/postcss": "^4.3.0", "@testing-library/dom": "^10.4.1", + "@types/mime-types": "^3.0.1", "babel-loader": "^10.0.0", "babel-plugin-inline-import": "^3.0.0", "copy-webpack-plugin": "^14.0.0", @@ -67,7 +75,9 @@ "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "jest-fetch-mock": "^3.0.3", + "lit-css-loader": "^4.0.1", "node-polyfill-webpack-plugin": "^4.1.0", + "postcss": "^8.5.15", "rdflib": "^2.3.6", "solid-logic": "^4.0.6", "solid-ui": "^3.1.0", @@ -76,5 +86,18 @@ "webpack": "^5.97.1", "webpack-cli": "^7.0.2", "webpack-dev-server": "^5.2.3" + }, + "dependencies": { + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.12.3", + "@codemirror/legacy-modes": "^6.5.3", + "@codemirror/state": "^6.6.0", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.43.0", + "codemirror": "^6.0.2" } } diff --git a/src/Header.ts b/src/Header.ts new file mode 100644 index 0000000..00362df --- /dev/null +++ b/src/Header.ts @@ -0,0 +1,134 @@ +import { html } from 'lit' +import { ref } from 'lit/directives/ref.js' +import { parse, serialize, NamedNode, LiveStore } from 'rdflib' +import { widgets, icons } from 'solid-ui' +import { getStatusSection } from './StatusSection' +import { applyResponseHeaders, checkSyntax, getResponseHeaders, happy, setControlVisible, setUnedited } from './helpers' +import { SourcePaneState } from './types' +import { compactable } from './compactableFormats' +import SourceEditorCard from './components/sourceEditor/SourceEditorCard' + +function mountButton (host: HTMLElement, button: HTMLElement) { + host.replaceChildren(button) +} + +export function renderHeader (store: LiveStore, subject: NamedNode, sourcePaneState: SourcePaneState) { + async function saveBack (store: LiveStore, subject: NamedNode, sourcePaneState: SourcePaneState) { + const fetcher = store.fetcher + const editorCard = document.querySelector('source-editor-card') as SourceEditorCard | null + if (!editorCard) return + const data = editorCard.getValue() + const { contentType, eTag } = sourcePaneState + if (!checkSyntax(store, subject, data, contentType, subject)) { + const { showError } = getStatusSection() + showError('Syntax error: fix the document before saving.') + return + } + const options: { data: string; contentType: string | undefined; headers?: { 'if-match': string } } = { data, contentType } + if (eTag) options.headers = { 'if-match': eTag } // avoid overwriting changed files -> status 412 + try { + const response = await fetcher.webOperation('PUT', subject.uri, options) + if (!happy(response, 'PUT')) return + /// @@ show edited: make save button disabled until edited again. + try { + const response = await fetcher.webOperation('HEAD', subject.uri) // , defaultFetchHeaders()) + if (!happy(response, 'HEAD')) return + applyResponseHeaders(sourcePaneState, getResponseHeaders(store, subject, response)) + setUnedited(subject, sourcePaneState) + } catch (err) { + throw err + } + } catch (err: any) { + const { showError } = getStatusSection() + showError('Error saving back: ' + err) + } + } + + function setEditable (sourcePaneState: SourcePaneState) { + const editorCard = document.querySelector('source-editor-card') as SourceEditorCard | null + const saveButton = document.querySelector('.sourcePaneSaveButton') as HTMLElement + const myEditButton = document.querySelector('.sourcePaneEditButton') as HTMLElement + const myCompactButton = document.querySelector('.sourcePaneCompactButton') as HTMLElement + if (sourcePaneState.broken) return + setControlVisible(saveButton, true) + setControlVisible(myEditButton, false) + setControlVisible(myCompactButton, false) // do not allow compact while editing + editorCard?.setReadOnly(false) + editorCard?.focusEditor() + } + + function compactHandler (store: LiveStore, subject: NamedNode, sourcePaneState: SourcePaneState) { + const { contentType } = sourcePaneState + const compactContentType = contentType?.split(';')[0] + const editorCard = document.querySelector('source-editor-card') as SourceEditorCard | null + const { showError } = getStatusSection() + + if (compactContentType && compactable[compactContentType]) { + try { + const text = editorCard?.getValue() ?? '' + parse(text, store, subject.uri, compactContentType) + // for jsonld serialize which is a Promise. New rdflib + const serialized = Promise.resolve(serialize(store.sym(subject.uri), store, subject.uri, compactContentType)) + serialized.then(result => { + if (typeof result === 'string') editorCard?.setValue(result) + }) + } catch (e: any) { + showError(String(e)) + } + } + } + + return html` +
+ { + if (host instanceof HTMLElement) { + mountButton(host, createEditButton(host.ownerDocument, () => setEditable(sourcePaneState))) + } + })}> + { + if (host instanceof HTMLElement) { + mountButton(host, createCompactButton(host.ownerDocument, () => compactHandler(store, subject, sourcePaneState))) + } + })}> + { + if (host instanceof HTMLElement) { + mountButton(host, createSaveButton(host.ownerDocument, () => saveBack(store, subject, sourcePaneState))) + } + })}> +
+ ` +} + +export function createEditButton (dom: Document, onClick?: EventListener) { + const myEditButton = widgets.button( + dom, + icons.iconBase + 'noun_253504.svg', + 'Edit' + ) + myEditButton.classList.add('sourcePaneEditButton') + if (onClick) { + myEditButton.addEventListener('click', onClick) + } + return myEditButton +} + +export function createCompactButton (dom: Document, onClick?: EventListener) { + const myCompactButton = widgets.button( + dom, + undefined, + 'Compact', + onClick, + { needsBorder: true } + ) + myCompactButton.classList.add('sourcePaneCompactButton') + return myCompactButton +} + +export function createSaveButton (dom: Document, onClick?: EventListener) { + const mySaveButton = widgets.continueButton(dom) + mySaveButton.classList.add('sourcePaneSaveButton') + if (onClick) { + mySaveButton.addEventListener('click', onClick) + } + return mySaveButton +} diff --git a/src/StatusSection.css b/src/StatusSection.css new file mode 100644 index 0000000..f71b3a6 --- /dev/null +++ b/src/StatusSection.css @@ -0,0 +1,19 @@ +.sourcePaneStatus { + display: none; + box-sizing: border-box; + transition: all 0.2s ease-in-out; +} + +.sourcePaneStatus[data-visible='true'] { + display: block; +} + +.error-text { + margin: 0; + padding: 12px; + background-color: #fde8e8; + border: 1px solid #f8b4b4; + border-radius: 4px; + color: #9b1c1c; + font-weight: 500; +} diff --git a/src/StatusSection.ts b/src/StatusSection.ts new file mode 100644 index 0000000..1a96e81 --- /dev/null +++ b/src/StatusSection.ts @@ -0,0 +1,52 @@ +import { html } from 'lit' +import './StatusSection.css' + +export function getStatusSection(){ + function showError(message: string) { + const statusSection = document.querySelector('.sourcePaneStatus') as HTMLElement | null + const statusMessageArea = statusSection?.querySelector('#statusMessageArea') as HTMLElement | null + if (statusSection) { + statusSection.dataset.visible = 'true' + } + if (statusMessageArea) { + statusMessageArea.hidden = false + const errorText = statusMessageArea.querySelector('.error-text') + if (errorText) { + errorText.textContent = message + } + } + } + + function clearError() { + const statusSection = document.querySelector('.sourcePaneStatus') as HTMLElement | null + const statusMessageArea = statusSection?.querySelector('#statusMessageArea') as HTMLElement | null + if (statusSection) { + statusSection.dataset.visible = 'false' + } + if (statusMessageArea) { + statusMessageArea.hidden = true + const errorText = statusMessageArea.querySelector('.error-text') + if (errorText) { + errorText.textContent = '' + } + } + } + + function renderStatusSection() { + const statusMessage = '' + + return html` + + ` + } + return { + renderStatusSection, + showError, + clearError + } +} diff --git a/src/compactableFormats.ts b/src/compactableFormats.ts new file mode 100644 index 0000000..8d44e3d --- /dev/null +++ b/src/compactableFormats.ts @@ -0,0 +1,5 @@ +export const compactable: Record = { + 'text/n3': true, + 'text/turtle': true, + 'application/ld+json': true +} diff --git a/src/components/sourceEditor/SourceEditor.ts b/src/components/sourceEditor/SourceEditor.ts new file mode 100644 index 0000000..ea3d43b --- /dev/null +++ b/src/components/sourceEditor/SourceEditor.ts @@ -0,0 +1,158 @@ + +import { Compartment } from '@codemirror/state' +import { EditorView } from '@codemirror/view' +import type { ThemeMode } from 'pane-registry' +import { darkThemeExtension } from './themes/dark' + +export class SourceEditor { + private _view: EditorView | null = null + private _languageCompartment = new Compartment() + private _editableCompartment = new Compartment() + + async initialize(container: HTMLElement, initialDoc = '', contentType: string = 'text/turtle', theme: ThemeMode = 'dark') { + if (this._view) { + this._view.destroy() + } + const [ + { EditorState }, + { drawSelection, keymap, lineNumbers }, + { defaultHighlightStyle, syntaxHighlighting }, + { defaultKeymap, history, historyKeymap }, + { oneDark } + ] = await Promise.all([ + import(/* webpackChunkName: "codemirror-core" */ '@codemirror/state'), + import(/* webpackChunkName: "codemirror-core" */ '@codemirror/view'), + import(/* webpackChunkName: "codemirror-core" */ '@codemirror/language'), + import(/* webpackChunkName: "codemirror-core" */ '@codemirror/commands'), + import(/* webpackChunkName: "codemirror-core" */ '@codemirror/theme-one-dark') + ]) + const languageExtension = await this._getLanguageExtension(contentType) + /* we could add this if we want to update automatically + perhaps or tell the user they have unsaved changes , + EditorView.updateListener.of((update) => { + if (update.docChanged) { + const text = update.state.doc.toString() + this._onChange(text) + // We could also set a flag here and then check it in the save handler to avoid doing expensive operations on every change + } + } + }) + and then set an onChange callback from the parent component to handle changes */ + + const state = EditorState.create({ + doc: initialDoc, + extensions: [ + theme === 'dark' ? darkThemeExtension : [], + this._languageCompartment.of(languageExtension), + this._editableCompartment.of(EditorView.editable.of(true)), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + lineNumbers(), + history(), + drawSelection(), + EditorView.lineWrapping, + keymap.of([ + ...defaultKeymap, + ...historyKeymap + ]) + ] + }) + + this._view = new EditorView({ + state, + parent: container + }) + } + + destroy() { + this._view?.destroy() + this._view = null + } + + getValue(): string { + return this._view ? this._view.state.doc.toString() : ''; + } + + // this is used in the compact handler to update the editor content after compacting without changing the editing state + replaceContent(text: string) { + if (!this._view) return + const current = this._view.state.doc.toString() + if (current === text) return + this._view.dispatch({ + changes: { from: 0, to: this._view.state.doc.length, insert: text } + }) + } + + setReadOnly(readOnly: boolean) { + if (!this._view) return + this._view.dispatch({ + effects: this._editableCompartment.reconfigure(EditorView.editable.of(!readOnly)) + }) + } + + focusEditor() { + this._view?.focus() + } + + async setLanguage(contentType: string) { + if (!this._view) return + + const extension = await this._getLanguageExtension(contentType) + this._view.dispatch({ + effects: this._languageCompartment.reconfigure(extension) + }) + } + + private async _getLanguageExtension(contentType: string) { + const getStreamLanguage = async () => + (await import(/* webpackChunkName: "codemirror-core" */ '@codemirror/language')).StreamLanguage + + switch (contentType) { + case 'text/turtle': + case 'text/n3': + const turtleStreamLanguage = await getStreamLanguage() + const { turtle } = await import(/* webpackChunkName: "lang-rdf" */ '@codemirror/legacy-modes/mode/turtle') + return turtleStreamLanguage.define(turtle) + + case 'application/sparql-update': + case 'application/sparql-query': + const sparqlStreamLanguage = await getStreamLanguage() + const { sparql } = await import(/* webpackChunkName: "lang-rdf" */ '@codemirror/legacy-modes/mode/sparql') + return sparqlStreamLanguage.define(sparql) + + case 'application/nquads': + case 'application/n-quads': + case 'application/n-triples': + const nTriplesStreamLanguage = await getStreamLanguage() + const { ntriples } = await import(/* webpackChunkName: "lang-rdf" */ '@codemirror/legacy-modes/mode/ntriples') + return nTriplesStreamLanguage.define(ntriples) + + case 'application/json': + case 'application/ld+json': + const { json } = await import(/* webpackChunkName: "lang-json" */ '@codemirror/lang-json') + return json() + + case 'text/html': + case 'application/xhtml+xml': + const { html } = await import(/* webpackChunkName: "lang-html" */ '@codemirror/lang-html') + return html() + + case 'application/rdf+xml': + case 'application/xml': + const { xml } = await import(/* webpackChunkName: "lang-xml" */ '@codemirror/lang-xml') + return xml() + + case 'text/css': + const { css } = await import(/* webpackChunkName: "lang-css" */ '@codemirror/lang-css') + return css() + + case 'text/javascript': + case 'application/javascript': + case 'application/ecmascript': + const { javascript } = await import(/* webpackChunkName: "lang-javascript" */ '@codemirror/lang-javascript') + return javascript() + + default: + return [] + } + } +} diff --git a/src/components/sourceEditor/SourceEditorCard.styles.css b/src/components/sourceEditor/SourceEditorCard.styles.css new file mode 100644 index 0000000..2a56c19 --- /dev/null +++ b/src/components/sourceEditor/SourceEditorCard.styles.css @@ -0,0 +1,29 @@ +:host { + + section { + display: flex; + flex-direction: column; + width: 100%; + padding: var(--spacing-md, 1.25rem); + align-items: flex-start; + gap: var(--spacing-md, 1.25rem); + border-radius: var(--border-radius-full, 0.625rem); + background: var(--color-card-bg, #FFFFFF); + box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.25); + } + + div.sourcePaneEditor { + box-sizing: border-box; + width: 100%; + max-width: 100%; + min-width: 0; + resize: vertical; + } + + p { + color: var(--color-text-primary, var(--gray-700, #364153)); + font-size: 1.25rem; /* 20px */ + font-weight: var(--font-weight-md, 500); + align-self: stretch; + } + } \ No newline at end of file diff --git a/src/components/sourceEditor/SourceEditorCard.ts b/src/components/sourceEditor/SourceEditorCard.ts new file mode 100644 index 0000000..2a21c00 --- /dev/null +++ b/src/components/sourceEditor/SourceEditorCard.ts @@ -0,0 +1,84 @@ +import { html } from 'lit' +import { createRef, ref } from 'lit/directives/ref.js' +import { customElement, property } from 'lit/decorators.js' +import { LiveStore, NamedNode } from 'rdflib' +import { fetchContentAndMetadata, setUnedited } from '../../helpers' +import styles from './SourceEditorCard.styles.css' +import WebComponent from '../../primitives/WebComponent' +import { SourcePaneState } from '../../types' +import { SourceEditor } from './SourceEditor' +import { getStatusSection } from '../../StatusSection' + +@customElement('source-editor-card') +export default class SourceEditorCard extends WebComponent { + static styles = styles + private _editor?: SourceEditor + private _editorMount = createRef() + + @property({ attribute: false }) + accessor store!: LiveStore + + @property({ attribute: false }) + accessor subject!: NamedNode + + @property({ attribute: false }) + accessor sourcePaneState!: SourcePaneState + + private _getFileName (uri?: string) { + if (!uri) return '' + const url = new URL(uri).pathname // remove #me and #this + return url.substring(url.lastIndexOf('/') + 1) + } + + getValue () { + return this._editor?.getValue() + } + + focusEditor () { + this._editor?.focusEditor() + } + + setReadOnly (readOnly: boolean) { + this._editor?.setReadOnly(readOnly) + } + + setValue (text: string) { + this._editor?.replaceContent(text) + } + + private async _initializeEditor () { + const sourcePaneEditor = this._editorMount.value + if (!sourcePaneEditor) return + try { + const { content, metadata } = await fetchContentAndMetadata(this.store, this.subject, this.sourcePaneState) + this._editor = new SourceEditor() + await this._editor.initialize(sourcePaneEditor, content, metadata.contentType) + setUnedited(this.subject, this.sourcePaneState) + } catch (err) { + const { showError } = getStatusSection() + showError('Error fetching content: ' + err) + } + } + + async firstUpdated () { + await this._initializeEditor() + } + + disconnectedCallback() { + super.disconnectedCallback() + if (this._editor) { + this._editor.destroy() + this._editor = undefined + } + } + + render() { + return html` +
+
+
+

${this._getFileName(this.subject.value)}

+
+ ` + } +} diff --git a/src/components/sourceEditor/themes/dark.ts b/src/components/sourceEditor/themes/dark.ts new file mode 100644 index 0000000..c3f31d2 --- /dev/null +++ b/src/components/sourceEditor/themes/dark.ts @@ -0,0 +1,72 @@ +import { EditorView } from '@codemirror/view'; +import { HighlightStyle, syntaxHighlighting } from '@codemirror/language' +import { tags as t } from '@lezer/highlight'; + +/* The base styles were copied from google + highlighting was modified to fit our design and needs */ +const darkTheme = EditorView.theme({ + // Targets the outermost editor container + '&': { + color: '#e0e0e0', + backgroundColor: '#1e1e1e', + height: '100%' + }, + + // Targets the active editing area where text lives + '.cm-content': { + caretColor: '#ff0055', // Custom cursor color + fontFamily: 'monospace' + }, + + // Targets the line that the cursor is currently on + '.cm-activeLine': { + backgroundColor: '#2a2a2a' + }, + + // Targets the line numbers sidebar panel + '.cm-gutters': { + backgroundColor: '#1e1e1e', + color: '#858585', + border: 'none' + }, + + // Targets the active line number specifically + '.cm-activeLineGutter': { + color: '#fff', + backgroundColor: '#2a2a2a' + }, + + // Targets text highlighted/selected by the user + '.cm-selectionBackground, ::selection': { + backgroundColor: '#3e4451 !important' + } +}, { dark: true }) + +const darkHighlightStyle = HighlightStyle.define([ + { tag: t.keyword, color: '#FFFF8A', fontWeight: 'bold' }, + { tag: t.string, color: '#FF5CFF' }, + { tag: t.comment, color: '#6272a4', fontStyle: 'italic' }, + { tag: t.function(t.variableName), color: '#50fa7b' }, + { tag: t.variableName, color: '#f8f8f2' }, + { tag: t.number, color: '#bd93f9' }, + { tag: t.integer, color: '#bd93f9' }, + { tag: t.float, color: '#bd93f9' }, + { tag: t.url, color: '#FFFF00'}, + { tag: t.className, color: '#ff5555' }, + { tag: t.namespace, color: '#9b59b6' }, + { tag: t.meta, color: '#c678dd' }, + { tag: t.propertyName, color: '#00D100' }, + { tag: t.unit, color: '#9b59b6'}, + { tag: t.punctuation, color: '#ffffff' }, + { tag: t.literal, color: '#f1fa8c' }, + { tag: t.docString, color: '#6272a4', fontStyle: 'italic' }, + { tag: t.processingInstruction, color: '#ffffff' }, + { tag: t.angleBracket, color: '#f65353' }, + { tag: t.tagName, color: '#f65353' }, + { tag: t.attributeName, color: '#66d9ef' }, +]) + +export const darkThemeExtension = [ + darkTheme, + syntaxHighlighting(darkHighlightStyle) +] diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000..2bd1427 --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,17 @@ +/* eslint-disable no-console */ + +export function log (...args: any[]) { + console.log(...args) +} + +export function warn (...args: any[]) { + console.warn(...args) +} + +export function error (...args: any[]) { + console.error(...args) +} + +export function trace (...args: any[]) { + console.trace(...args) +} diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..b412c35 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,219 @@ +import { LiveStore, NamedNode, parse, serialize } from 'rdflib' +import { getStatusSection } from './StatusSection' +import { error, log } from './debug' +import { ns } from 'solid-ui' +import { HttpResourceMetadata, SourcePaneState } from './types' +import { compactable } from './compactableFormats' +import SourceEditorCard from './components/sourceEditor/SourceEditorCard' + +const parseable: Record = { + 'text/n3': true, + 'text/turtle': true, + 'application/rdf+xml': true, + 'application/xhtml+xml': true, // For RDFa? + 'text/html': true, // For data island + // 'application/sparql-update': true, + 'application/json': true, + 'application/ld+json': true + // 'application/nquads' : true, + // 'application/n-quads' : true +} + +export function happy (response: Response, method: string) { + if (!response.ok) { + let msg = 'HTTP error on ' + method + '! Status: ' + response.status + error(msg) + if (response.status === 412) msg = 'Error: File changed by someone else' + const { showError } = getStatusSection() + showError(msg) + } + return response.ok +} + +/** Set Caret position in a text box +* @param {Element} elem - the element to be tweaked +* @param {Integer} caretPos - the poisition starting at zero +* @credit https://stackoverflow.com/questions/512528/set-keyboard-caret-position-in-html-textbox +*/ +function setCaretPosition (elem: HTMLTextAreaElement, cause: { characterInFile: number, lineNo?: number }) { + if (elem != null) { + if (cause.characterInFile === -1 && cause.lineNo) cause.lineNo += 1 + const pos = cause.lineNo ? elem.value.split('\n', cause.lineNo).join('\n').length : 0 + const caretPos = pos + cause.characterInFile + const textArea = elem as HTMLTextAreaElement & { createTextRange?: () => any } + if (textArea.createTextRange) { + const range = textArea.createTextRange() + range.move('character', caretPos) + range.select() + } else { + elem.focus() + if (elem.selectionStart) { + elem.setSelectionRange(caretPos, caretPos) + } + } + } +} + +function HTMLDataIsland (data: string): [string, string] { + let dataIslandContentType = '' + let dataIsland = '' + const scripts = data.split('' + const RDFType = ['text/turtle', 'text/n3', 'application/ld+json', 'application/rdf+xml'] + const contentType = RDFType.find(type => script.includes(`type="${type}"`)) + if (contentType) { + dataIsland = script.replace(/^/gm, '').replace(/<\/script>$/gm, '') + dataIslandContentType = contentType + break + } + } + } + return [dataIsland, dataIslandContentType] +} + +export function checkSyntax (store: LiveStore, subject: NamedNode, data: string, contentType: string | undefined, base: NamedNode) { + const fetcher = store.fetcher + const { showError, clearError } = getStatusSection() + const textArea = document.querySelector('.sourcePaneTextArea') as HTMLTextAreaElement + + if (!showError || !clearError) return true + if (!contentType || !parseable[contentType]) return true // don't check things we don't understand + if (contentType === 'text/html') { + [data, contentType] = HTMLDataIsland(data) + if (!contentType) return true + } + try { + clearError() + if (contentType === 'application/json') { + JSON.parse(data) + return true + } else { + try { + store.removeDocument(subject) + } catch (err: any) { + // this is a hack until issue is resolved in rdflib + if (!(err instanceof Error) || !err.message.includes('Statement to be removed is not on store')) throw err + error(err) + } + delete fetcher.requested[subject.value] + // rdflib parse jsonld do not return parsing errors + if (contentType === 'application/ld+json') { + JSON.parse(data) + parse(data, store, base.uri, contentType, (err: any, res: any) => { + if (err) throw err + const serialized = serialize(base as any, res as any, base.uri, contentType) + if (typeof serialized === 'string' && data.includes('@id') && !serialized.includes('@id')) { + const e = new Error('Invalid jsonld : predicate do not expand to an absolute IRI') + showError(e.message) + // throw e + return false + } + return true + }) + } else { + parse(data, store, base.uri, contentType) + } + } + return true + } catch (e: any) { + showError(e.message) + let cause: any = e + while (cause && cause.cause) { + cause = cause.cause + if (cause && cause.characterInFile) { + setCaretPosition(textArea, cause) + } + } + return false + } + return true +} + +export function setControlVisible (button: HTMLElement | null, visible: boolean) { + if (!button) return + button.classList.toggle('sourcePaneControlVisible', visible) + button.classList.toggle('sourcePaneControlHidden', !visible) +} + +export function setUnedited (subject: NamedNode, sourcePaneState: SourcePaneState) { + const editorCard = document.querySelector('source-editor-card') as SourceEditorCard | null + const saveButton = document.querySelector('.sourcePaneSaveButton') as HTMLElement + const myEditButton = document.querySelector('.sourcePaneEditButton') as HTMLElement + const myCompactButton = document.querySelector('.sourcePaneCompactButton') as HTMLElement + const { broken, contentType, allowed } = sourcePaneState + if (broken) return + const canEdit = !subject.uri.endsWith('/') && (!allowed || allowed.includes('PUT')) + setControlVisible(myEditButton, canEdit) + setControlVisible(saveButton, false) + setControlVisible(myCompactButton, !!(contentType && compactable[contentType.split(';')[0]])) + editorCard?.setReadOnly(true) +} + +export function applyResponseHeaders (sourcePaneState: SourcePaneState, metadata: HttpResourceMetadata) { + sourcePaneState.contentType = metadata.contentType + sourcePaneState.allowed = metadata.allowed + sourcePaneState.eTag = metadata.eTag +} + +// get response headers +export function getResponseHeaders (store: LiveStore, subject: NamedNode, response: Response): HttpResourceMetadata { + let contentType: string | undefined + let allowed: string | undefined + let eTag: string | undefined + if (response.headers && response.headers.get('content-type')) { + contentType = response.headers.get('content-type')?.split(';')[0] ?? undefined // Should work but headers may be empty + allowed = response.headers.get('allow') ?? undefined // const cts = store.fetcher.getHeader(subject.doc(), 'content-type') + eTag = response.headers.get('etag') ?? undefined + } else { + const reqs = store.each( + null, + store.sym('http://www.w3.org/2007/ont/link#requestedURI'), + subject + ) + reqs.forEach((req: any) => { + const rrr = store.any( + req as any, + store.sym('http://www.w3.org/2007/ont/link#response') + ) + if (rrr && rrr.termType === 'NamedNode') { + contentType = store.anyValue(rrr as any, ns.httph('content-type')) || undefined + allowed = store.anyValue(rrr as any, ns.httph('allow')) || undefined + eTag = store.anyValue(rrr as any, ns.httph('etag')) || undefined + if (!eTag) log('sourcePane: No eTag on GET') + } + }) + } + return { contentType, allowed, eTag } +} + +export async function fetchContentAndMetadata(store: LiveStore, subject: NamedNode, sourcePaneState: SourcePaneState): Promise<{ content: string, metadata: HttpResourceMetadata }> { + const fetcher = store.fetcher + const { showError } = getStatusSection() + + try { + const response = await fetcher.webOperation('GET', subject.uri) + if (!happy(response, 'GET')) { + throw new Error('GET request failed') + } + + const content = (response as Response & { responseText?: string }).responseText + if (content === undefined) { // Defensive https://github.com/linkeddata/rdflib.js/issues/506 + throw new Error('source pane: No text in response object!!') + } + + const metadata = getResponseHeaders(store, subject, response) + if (!metadata.contentType) { + throw new Error('Error: No content-type available!') + } + applyResponseHeaders(sourcePaneState, metadata) + if (!metadata.allowed) { + error('@@@@@@@@@@ No Allow: header from this server') + } + return { content, metadata } + } catch (err: any) { + showError('Error reading file: ' + err) + throw err + } +} diff --git a/src/primitives/WebComponent.ts b/src/primitives/WebComponent.ts new file mode 100644 index 0000000..63f1d60 --- /dev/null +++ b/src/primitives/WebComponent.ts @@ -0,0 +1,44 @@ +import { html, LitElement } from 'lit' +// copied from solid-ui for now so I can follow same structure as other webcomponets +// i think we need to export this, but want to talk to team +export default abstract class WebComponent extends LitElement { + static states?: Record + + protected internals?: ElementInternals + + protected willUpdate (changedProperties: Map) { + super.willUpdate(changedProperties) + + const states = this.static().states + + if (!states) { + return + } + + const internals = this.getInternals() + + for (const [state, condition] of Object.entries(states)) { + const matches = condition(this) + + if (matches && !internals.states.has(state)) { + internals.states.add(state) + } else if (!matches && internals.states.has(state)) { + internals.states.delete(state) + } + } + } + + protected render () { + return html`` + } + + protected getInternals (): ElementInternals { + this.internals ??= this.attachInternals() + + return this.internals + } + + private static (): typeof WebComponent { + return this.constructor as typeof WebComponent + } +} diff --git a/src/styles/sourcePane.css b/src/sourcePane.css similarity index 69% rename from src/styles/sourcePane.css rename to src/sourcePane.css index 5e346b0..372552d 100644 --- a/src/styles/sourcePane.css +++ b/src/sourcePane.css @@ -1,6 +1,11 @@ .sourcePane { - width: 100%; - max-width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.438rem; /* 23px */ + padding-bottom: 1.813rem; /* 29px */ + border-radius: var(--border-radius-base, 0.3125rem); /* 5px */ + border: 1px solid var(--color-border, var(--gray-200, #E5E7EB)); } .sourcePaneControls { @@ -10,42 +15,18 @@ gap: 0.4rem; flex-wrap: wrap; width: 100%; - padding-right: 1.6em; text-align: right; + padding: 0 0.2em var(--spacing-md, 1em) 0; } /* generative ai */ .sourcePaneControls > * { flex: 0 1 auto; } -.sourcePaneTable { - width: 100%; - table-layout: fixed; -} - -.sourcePaneControlsCell { - width: 100%; - text-align: right; - padding: 0 0.2em var(--spacing-md, 1em) 0; -} - -.sourcePaneMainCell, -.sourcePaneStatusRow { - width: 100%; -} - -.sourcePaneTextArea { - box-sizing: border-box; +.sourcePaneHeader, +.sourcePaneCard, +.sourcePaneStatus { width: 100%; - max-width: 100%; - min-width: 0; - font-family: monospace; - font-size: 100%; - margin: var(--spacing-md, 1em) 0.2em; - padding: var(--spacing-md, 1em); - border: 0.1em solid #888; - border-radius: var(--border-radius-base, 0.5em); - resize: vertical; } .sourcePaneCompactButton { diff --git a/src/sourcePane.js b/src/sourcePane.js deleted file mode 100644 index cb7d038..0000000 --- a/src/sourcePane.js +++ /dev/null @@ -1,440 +0,0 @@ -/* Source editor Pane - ** - ** This pane allows the original source of a resource to be edited by hand - ** -*/ - -import * as $rdf from 'rdflib' -import * as UI from 'solid-ui' -import * as mime from 'mime-types' -import './styles/sourcePane.css' - -const pane = { - icon: UI.icons.iconBase + 'noun_109873.svg', // noun_109873_51A7F9.svg - - name: 'source', - - label: function (subject, context) { - const kb = context.session.store - const typeURIs = kb.findTypeURIs(subject) - const prefix = $rdf.Util.mediaTypeClass('text/*').uri.split('*')[0] - for (const t in typeURIs) { - if (t.startsWith(prefix)) return 'Source' - if (t.includes('xml')) return 'XML Source' - if (t.includes('json')) return 'JSON Source' // Like eg application/ld+json - if (t.includes('javascript')) return 'Javascript Source' - } - return null - }, - - // Create a new text file in a Solid system, - mintNew: function (context, newPaneOptions) { - const kb = context.session.store - let newInstance = newPaneOptions.newInstance - if (!newInstance) { - let uri = newPaneOptions.newBase - if (uri.endsWith('/')) { - uri = uri.slice(0, -1) - newPaneOptions.newBase = uri - } - newInstance = kb.sym(uri) - newPaneOptions.newInstance = newInstance - } - - const contentType = mime.lookup(newInstance.uri) - if ( - !contentType || - !(contentType.startsWith('text') || contentType.includes('xml') || contentType.includes('json') || contentType.includes('javascript')) - ) { - const msg = - 'A new text file has to have an file extension like .txt .ttl .json etc.' - alert(msg) - throw new Error(msg) - } - - function contentForNew (contentType) { - let content = '\n' - if (contentType.includes('json')) content = '{}\n' - else if (contentType.includes('rdf+xml')) content = '\n\n' - return content - } - - return new Promise(function (resolve, reject) { - kb.fetcher - .webOperation('PUT', newInstance.uri, { - data: contentForNew(contentType), - contentType: contentType - }) - .then( - function (_response) { - console.log('New text file created: ' + newInstance.uri) - newPaneOptions.newInstance = newInstance - resolve(newPaneOptions) - }, - err => { - alert('Cant make new file: ' + err) - reject(err) - } - ) - }) - }, - - render: function (subject, context) { - const dom = context.dom - const kb = context.session.store - const fetcher = kb.fetcher - let readonly = true - let editing = false - let broken = false - // Set in refresh() - let contentType, allowed, eTag // Note it when we read and use it when we save - - const div = dom.createElement('div') - div.setAttribute('class', 'sourcePane') - const table = div.appendChild(dom.createElement('table')) - table.setAttribute('class', 'sourcePaneTable') - const main = table.appendChild(dom.createElement('tr')) - main.setAttribute('class', 'sourcePaneMainCell') - const statusRow = table.appendChild(dom.createElement('tr')) - statusRow.setAttribute('class', 'sourcePaneStatusRow') - const controls = table.appendChild(dom.createElement('tr')) - controls.setAttribute('class', 'sourcePaneControls sourcePaneControlsCell') - - const textArea = main.appendChild(dom.createElement('textarea')) - textArea.setAttribute('class', 'sourcePaneTextArea') - - function editButton (dom) { - return UI.widgets.button( - dom, - UI.icons.iconBase + 'noun_253504.svg', - 'Edit' - ) - } - - function compactButton (dom) { - return UI.widgets.button( - dom, - undefined, - 'Compact', - compactHandler, - { needsBorder: true } - ) - } - - const myCompactButton = controls.appendChild(compactButton(dom)) - myCompactButton.classList.add('sourcePaneCompactButton') - const cancelButton = controls.appendChild(UI.widgets.cancelButton(dom)) - const saveButton = controls.appendChild(UI.widgets.continueButton(dom)) - - // This code was generated by Generative AI (GPT-5.3-Codex in GitHub Copilot) based on the following prompt: - /* can you refactor this code setUnedited () to use css instead of inline js styles */ - function setControlVisible (button, visible) { - button.classList.toggle('sourcePaneControlVisible', visible) - button.classList.toggle('sourcePaneControlHidden', !visible) - } - - function setTextState (stateClass) { - textArea.classList.remove( - 'sourcePaneTextAreaUnedited', - 'sourcePaneTextAreaEditing', - 'sourcePaneTextAreaEdited', - 'sourcePaneTextAreaError' - ) - textArea.classList.add(stateClass) - } - const myEditButton = controls.appendChild(editButton(dom)) - - function setUnedited () { - if (broken) return - editing = false - setControlVisible(myEditButton, !readonly && !subject.uri.endsWith('/')) - setControlVisible(cancelButton, true) - setControlVisible(saveButton, false) - setControlVisible(myCompactButton, !!(contentType && compactable[contentType.split(';')[0]])) - setTextState('sourcePaneTextAreaUnedited') - textArea.setAttribute('readonly', 'true') - } - function setEditable () { - if (broken) return - editing = true - setControlVisible(cancelButton, true) // not logically needed but may be comforting - setControlVisible(saveButton, false) - setControlVisible(myEditButton, false) - setControlVisible(myCompactButton, false) // do not allow compact while editing - setTextState('sourcePaneTextAreaEditing') - textArea.removeAttribute('readonly') - } - function setEdited (_event) { - if (broken || !editing) return - setControlVisible(cancelButton, true) - setControlVisible(saveButton, true) - setControlVisible(myEditButton, false) - setControlVisible(myCompactButton, false) - setTextState('sourcePaneTextAreaEdited') - textArea.removeAttribute('readonly') - } - // end of generative ai refactor - - const parseable = { - 'text/n3': true, - 'text/turtle': true, - 'application/rdf+xml': true, - 'application/xhtml+xml': true, // For RDFa? - 'text/html': true, // For data island - // 'application/sparql-update': true, - 'application/json': true, - 'application/ld+json': true - // 'application/nquads' : true, - // 'application/n-quads' : true - } - - /** Set Caret position in a text box - * @param {Element} elem - the element to be tweaked - * @param {Integer} caretPos - the poisition starting at zero - * @credit https://stackoverflow.com/questions/512528/set-keyboard-caret-position-in-html-textbox - */ - function setCaretPosition (elem, cause) { - if (elem != null) { - if (cause.characterInFile === -1 && cause.lineNo) cause.lineNo += 1 - const pos = cause.lineNo ? elem.value.split('\n', cause.lineNo).join('\n').length : 0 - const caretPos = pos + cause.characterInFile - if (elem.createTextRange) { - const range = elem.createTextRange() - range.move('character', caretPos) - range.select() - } else { - elem.focus() - if (elem.selectionStart) { - elem.setSelectionRange(caretPos, caretPos) - } - } - } - } - - function HTMLDataIsland (data) { - let dataIslandContentType = '' - let dataIsland = '' - const pos = 0 - const scripts = data.split('' - const RDFType = ['text/turtle', 'text/n3', 'application/ld+json', 'application/rdf+xml'] - const contentType = RDFType.find(type => script.includes(`type="${type}"`)) - if (contentType) { - dataIsland = script.replace(/^/gm, '').replace(/<\/script>$/gm, '') - dataIslandContentType = contentType - break - } - } - } - return [dataIsland, dataIslandContentType, pos] - } - - function checkSyntax (data, contentType, base) { - if (!parseable[contentType]) return true // don't check things we don't understand - let pos - if (contentType === 'text/html') { - [data, contentType, pos] = HTMLDataIsland(data) - if (!contentType) return true - } - try { - statusRow.innerHTML = '' - if (contentType === 'application/json') return JSON.parse(data) - else { - try { - kb.removeDocument(subject) - } catch (err) { - // this is a hack until issue is resolved in rdflib - if (!err.message.includes('Statement to be removed is not on store')) throw err - console.log(err) - } - delete fetcher.requested[subject.value] - // rdflib parse jsonld do not return parsing errors - if (contentType === 'application/ld+json') { - JSON.parse(data) - $rdf.parse(data, kb, base.uri, contentType, (err, res) => { - if (err) throw err - const serialized = $rdf.serialize(base, res, base.uri, contentType) - if (data.includes('@id') && !serialized.includes('@id')) { - const e = new Error('Invalid jsonld : predicate do not expand to an absolute IRI') - statusRow.appendChild(UI.widgets.errorMessageBlock(dom, e)) - // throw e - return false - } - return true - }) - } else { - $rdf.parse(data, kb, base.uri, contentType) - } - } - return true - } catch (e) { - statusRow.appendChild(UI.widgets.errorMessageBlock(dom, e)) - for (let cause = e; (cause = cause.cause); cause) { - if (cause.characterInFile) { - setCaretPosition(textArea, cause) - } - } - return false - } - return true - } - - async function saveBack (_event) { - const data = textArea.value - if (!checkSyntax(data, contentType, subject)) { - setEdited() // failed to save -> different from web - setTextState('sourcePaneTextAreaError') - return - } - const options = { data, contentType } - if (eTag) options.headers = { 'if-match': eTag } // avoid overwriting changed files -> status 412 - try { - const response = await fetcher.webOperation('PUT', subject.uri, options) - if (!happy(response, 'PUT')) return - /// @@ show edited: make save button disabled until edited again. - try { - const response = await fetcher.webOperation('HEAD', subject.uri) // , defaultFetchHeaders()) - if (!happy(response, 'HEAD')) return - getResponseHeaders(response) // get new eTag - setUnedited() // used to be setEdited() - } catch (err) { - throw err - } - } catch (err) { - div.appendChild( - UI.widgets.errorMessageBlock(dom, 'Error saving back: ' + err)) - } - } - - function happy (response, method) { - if (!response.ok) { - let msg = 'HTTP error on ' + method + '! Status: ' + response.status - console.log(msg) - if (response.status === 412) msg = 'Error: File changed by someone else' - statusRow.appendChild(UI.widgets.errorMessageBlock(dom, msg)) - } - return response.ok - } - - const compactable = { - 'text/n3': true, - 'text/turtle': true, - 'application/ld+json': true - } - function compactHandler (_event) { - if (compactable[contentType]) { - try { - $rdf.parse(textArea.value, kb, subject.uri, contentType) - // for jsonld serialize which is a Promise. New rdflib - const serialized = Promise.resolve($rdf.serialize(kb.sym(subject.uri), kb, subject.uri, contentType)) - serialized.then(result => { textArea.value = result /*return div*/ }) - setControlVisible(cancelButton, true) - } catch (e) { - statusRow.appendChild(UI.widgets.errorMessageBlock(dom, e)) - } - } - } - - // function refresh (_event) { - // Use default fetch headers (such as Accept) - /* function defaultFetchHeaders () { - const options = fetcher.initFetchOptions(subject.uri, {}) - const { headers } = options - options.headers = new Headers() - for (const header in headers) { - if (typeof headers[header] === 'string') { - options.headers.set(header, headers[header]) - } - } - return options - } */ - - // get response headers - function getResponseHeaders (response) { - if (response.headers && response.headers.get('content-type')) { - contentType = response.headers.get('content-type').split(';')[0] // Should work but headers may be empty - allowed = response.headers.get('allow') // const cts = kb.fetcher.getHeader(subject.doc(), 'content-type') - eTag = response.headers.get('etag') - } else { - const reqs = kb.each( - null, - kb.sym('http://www.w3.org/2007/ont/link#requestedURI'), - subject.uri - ) - reqs.forEach(req => { - const rrr = kb.any( - req, - kb.sym('http://www.w3.org/2007/ont/link#response') - ) - if (rrr && rrr.termType === 'NamedNode') { - contentType = kb.anyValue(rrr, UI.ns.httph('content-type')) - allowed = kb.anyValue(rrr, UI.ns.httph('allow')) - eTag = kb.anyValue(rrr, UI.ns.httph('etag')) - if (!eTag) console.log('sourcePane: No eTag on GET') - } - }) - } - } - - function refresh (_event) { - // see https://github.com/linkeddata/rdflib.js/issues/629 - // const options = defaultFetchHeaders() - - fetcher - .webOperation('GET', subject.uri) // , options) - .then(function (response) { - if (!happy(response, 'GET')) return - const desc = response.responseText - if (desc === undefined) { // Defensive https://github.com/linkeddata/rdflib.js/issues/506 - const msg = 'source pane: No text in response object!!' - statusRow.appendChild(UI.widgets.errorMessageBlock(dom, msg)) - return // Never mis-represent the contents of the file. - } - textArea.rows = desc.split('\n').length + 2 - textArea.value = desc - - getResponseHeaders (response) - if (!contentType) { - readonly = true - broken = true - statusRow.appendChild( - UI.widgets.errorMessageBlock( - dom, - 'Error: No content-type available!' - ) - ) - return - } - // console.log(' source content-type ' + contentType) - // let allowed = response.headers['allow'] - if (!allowed) { - console.log('@@@@@@@@@@ No Allow: header from this server') - readonly = false // better allow just in case - } else { - readonly = allowed.indexOf('PUT') < 0 // In future more info re ACL allow? - } - setUnedited() - }) - .catch(err => { - div.appendChild( - UI.widgets.errorMessageBlock(dom, 'Error reading file: ' + err) - ) - }) - } - - textArea.addEventListener('keyup', setEdited) - myCompactButton.addEventListener('click', compactHandler) - myEditButton.addEventListener('click', setEditable) - cancelButton.addEventListener('click', refresh) - saveButton.addEventListener('click', saveBack) - - refresh() - return div - } -} - -export default pane - -// ENDS diff --git a/src/sourcePane.ts b/src/sourcePane.ts new file mode 100644 index 0000000..74b0d2e --- /dev/null +++ b/src/sourcePane.ts @@ -0,0 +1,115 @@ +/* Source editor Pane + ** + ** This pane allows the original source of a resource to be edited by hand + ** +*/ +import { NamedNode, Util } from 'rdflib' +import { icons } from 'solid-ui' +import { DataBrowserContext, NewPaneOptions } from 'pane-registry' +import * as mime from 'mime-types' +import { html, render as litRender } from 'lit' +import { log } from './debug' +import './components/sourceEditor/SourceEditorCard' +import { renderHeader } from './Header' +import { getStatusSection } from './StatusSection' +import './sourcePane.css' +import { SourcePaneState } from './types' + +const pane = { + icon: icons.iconBase + 'noun_109873.svg', // noun_109873_51A7F9.svg + + name: 'source', + + label: function (subject: NamedNode, context: DataBrowserContext) { + const store = context.session.store + const typeURIs = store.findTypeURIs(subject) + const prefix = Util.mediaTypeClass('text/*').uri.split('*')[0] + for (const t in typeURIs) { + if (t.startsWith(prefix)) return 'Source' + if (t.includes('xml')) return 'XML Source' + if (t.includes('json')) return 'JSON Source' // Like eg application/ld+json + if (t.includes('javascript')) return 'Javascript Source' + } + return null + }, + + // Create a new text file in a Solid system, + mintNew: function (context: DataBrowserContext, newPaneOptions: NewPaneOptions) { + const store = context.session.store + let newInstance = newPaneOptions.newInstance + if (!newInstance) { + let uri = newPaneOptions.newBase + if (uri.endsWith('/')) { + uri = uri.slice(0, -1) + newPaneOptions.newBase = uri + } + newInstance = store.sym(uri) + newPaneOptions.newInstance = newInstance + } + + const contentType = mime.lookup(newInstance.uri) + if ( + !contentType || + !(contentType.startsWith('text') || contentType.includes('xml') || contentType.includes('json') || contentType.includes('javascript')) + ) { + const msg = + 'A new text file has to have an file extension like .txt .ttl .json etc.' + alert(msg) + throw new Error(msg) + } + + function contentForNew (contentType: string) { + let content = '\n' + if (contentType.includes('json')) content = '{}\n' + else if (contentType.includes('rdf+xml')) content = '\n\n' + return content + } + + return new Promise(function (resolve, reject) { + store.fetcher + .webOperation('PUT', newInstance.uri, { + data: contentForNew(contentType), + contentType: contentType + }) + .then( + function () { + log('New text file created: ' + newInstance.uri) + newPaneOptions.newInstance = newInstance + resolve(newPaneOptions) + }, + err => { + alert('Cant make new file: ' + err) + reject(err) + } + ) + }) + }, + + render: function (subject: NamedNode, context: DataBrowserContext) { + const store = context.session.store + const { renderStatusSection } = getStatusSection() + const sourcePaneState: SourcePaneState = { + broken: false, + allowed: undefined, + contentType: undefined, + eTag: undefined + } + + const sourcePane = context.dom.createElement('div') + sourcePane.setAttribute('class', 'sourcePane') + litRender(html` + ${renderHeader(store, subject, sourcePaneState)} + + + ${renderStatusSection()} + `, sourcePane) + + return sourcePane + } +} + +export default pane diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..720d3e6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,9 @@ +export type HttpResourceMetadata = { + contentType: string | undefined + allowed: string | undefined + eTag: string | undefined +} + +export type SourcePaneState = { + broken: boolean +} & HttpResourceMetadata diff --git a/test/helpers.test.js b/test/helpers.test.js new file mode 100644 index 0000000..9f8e48c --- /dev/null +++ b/test/helpers.test.js @@ -0,0 +1,102 @@ +jest.mock('../src/components/sourceEditor/SourceEditorCard', () => { + return class SourceEditorCard {} +}) + +const { getResponseHeaders, fetchContentAndMetadata, setUnedited } = require('../src/helpers') + +describe('helpers', () => { + beforeEach(() => { + document.body.innerHTML = '' + jest.clearAllMocks() + }) + + it('reads response headers into metadata', () => { + const response = { + headers: { + get: jest.fn((name) => { + if (name === 'content-type') return 'text/turtle; charset=utf-8' + if (name === 'allow') return 'GET,PUT' + if (name === 'etag') return '"abc"' + return null + }), + }, + } + const store = { each: jest.fn(), any: jest.fn(), anyValue: jest.fn(), sym: jest.fn() } + const subject = { uri: 'https://example.org/profile/card' } + + expect(getResponseHeaders(store, subject, response)).toEqual({ + contentType: 'text/turtle', + allowed: 'GET,PUT', + eTag: '"abc"', + }) + }) + + it('fetches content and applies the returned metadata', async () => { + const response = { + ok: true, + headers: { + get: jest.fn((name) => { + if (name === 'content-type') return 'text/turtle' + if (name === 'allow') return 'GET,PUT' + if (name === 'etag') return '"abc"' + return null + }), + }, + responseText: '<> a <#Thing>.', + } + const store = { + fetcher: { + webOperation: jest.fn().mockResolvedValue(response), + }, + each: jest.fn(), + any: jest.fn(), + anyValue: jest.fn(), + sym: jest.fn(), + } + const subject = { uri: 'https://example.org/profile/card' } + const sourcePaneState = { broken: false, contentType: undefined, allowed: undefined, eTag: undefined } + + const result = await fetchContentAndMetadata(store, subject, sourcePaneState) + + expect(result).toEqual({ + content: '<> a <#Thing>.', + metadata: { + contentType: 'text/turtle', + allowed: 'GET,PUT', + eTag: '"abc"', + }, + }) + expect(sourcePaneState).toEqual({ + broken: false, + contentType: 'text/turtle', + allowed: 'GET,PUT', + eTag: '"abc"', + }) + }) + + it('sets the editor back to read only and shows the edit controls', () => { + const editorCard = document.createElement('source-editor-card') + editorCard.setReadOnly = jest.fn() + document.body.appendChild(editorCard) + + const saveButton = document.createElement('button') + saveButton.className = 'sourcePaneSaveButton' + const editButton = document.createElement('button') + editButton.className = 'sourcePaneEditButton' + const compactButton = document.createElement('button') + compactButton.className = 'sourcePaneCompactButton' + document.body.appendChild(saveButton) + document.body.appendChild(editButton) + document.body.appendChild(compactButton) + + setUnedited( + { uri: 'https://example.org/profile/card' }, + { broken: false, contentType: 'text/turtle', allowed: 'GET', eTag: '"abc"' } + ) + + expect(editorCard.setReadOnly).toHaveBeenCalledWith(true) + expect(saveButton.className).toContain('sourcePaneControlHidden') + expect(editButton.className).toContain('sourcePaneControlHidden') + expect(compactButton.className).toContain('sourcePaneControlVisible') + }) +}) diff --git a/test/sourceEditor.test.js b/test/sourceEditor.test.js new file mode 100644 index 0000000..7864576 --- /dev/null +++ b/test/sourceEditor.test.js @@ -0,0 +1,128 @@ +jest.mock('@codemirror/state', () => { + class Compartment { + of(value) { + return { type: 'compartment-of', value } + } + + reconfigure(value) { + return { type: 'reconfigure', value } + } + } + + const EditorState = { + create: jest.fn((config) => ({ ...config })) + } + + return { Compartment, EditorState } +}) + +const createdViews = [] + +jest.mock('@codemirror/view', () => { + class EditorView { + constructor({ state, parent }) { + this.state = state + this.parent = parent + this.dom = globalThis.document.createElement('div') + this.dispatch = jest.fn((transaction) => { + this.lastTransaction = transaction + if (transaction.changes) { + this.state.doc = transaction.changes.insert + } + }) + this.focus = jest.fn() + this.destroy = jest.fn() + createdViews.push(this) + if (parent) { + parent.appendChild(this.dom) + } + } + } + + EditorView.editable = { + of: jest.fn((value) => ({ type: 'editable', value })) + } + EditorView.theme = jest.fn((spec, options) => ({ type: 'theme', spec, options })) + EditorView.lineWrapping = { type: 'lineWrapping' } + EditorView.updateListener = { + of: jest.fn((listener) => ({ type: 'updateListener', listener })) + } + + return { + EditorView, + drawSelection: jest.fn(() => ({ type: 'drawSelection' })), + keymap: { + of: jest.fn((value) => ({ type: 'keymap', value })) + }, + lineNumbers: jest.fn(() => ({ type: 'lineNumbers' })) + } +}) + +jest.mock('@codemirror/language', () => ({ + defaultHighlightStyle: { name: 'defaultHighlightStyle' }, + syntaxHighlighting: jest.fn((style, options) => ({ type: 'syntaxHighlighting', style, options })), + HighlightStyle: { + define: jest.fn(() => ({ type: 'highlightStyle' })) + }, + StreamLanguage: class { + static define(mode) { + return { type: 'stream-language', mode } + } + } +})) + +jest.mock('@codemirror/commands', () => ({ + defaultKeymap: [{ key: 'default' }], + history: jest.fn(() => ({ type: 'history' })), + historyKeymap: [{ key: 'history' }] +})) + +jest.mock('@codemirror/theme-one-dark', () => ({ + oneDark: { name: 'oneDark' } +})) + +const { SourceEditor } = require('../src/components/sourceEditor/SourceEditor') + +describe('SourceEditor', () => { + beforeEach(() => { + createdViews.length = 0 + document.body.innerHTML = '' + jest.clearAllMocks() + }) + + it('initializes with the provided document text', async () => { + const editor = new SourceEditor() + const container = document.createElement('div') + document.body.appendChild(container) + + await editor.initialize(container, 'hello world', 'text/plain') + + expect(editor.getValue()).toBe('hello world') + expect(createdViews).toHaveLength(1) + expect(createdViews[0].state.doc).toBe('hello world') + expect(createdViews[0].parent).toBe(container) + }) + + it('replaces content and toggles read only state', async () => { + const editor = new SourceEditor() + const container = document.createElement('div') + document.body.appendChild(container) + + await editor.initialize(container, 'first', 'text/plain') + const view = createdViews[0] + + editor.replaceContent('second') + expect(editor.getValue()).toBe('second') + expect(view.dispatch).toHaveBeenCalledWith({ + changes: { from: 0, to: 5, insert: 'second' } + }) + + editor.setReadOnly(true) + expect(view.dispatch).toHaveBeenLastCalledWith({ + effects: { type: 'reconfigure', value: { type: 'editable', value: false } } + }) + + editor.focusEditor() + expect(view.focus).toHaveBeenCalled() + }) +}) diff --git a/test/sourceEditorCard.test.js b/test/sourceEditorCard.test.js new file mode 100644 index 0000000..7d03c55 --- /dev/null +++ b/test/sourceEditorCard.test.js @@ -0,0 +1,85 @@ +jest.mock('../src/helpers', () => ({ + fetchContentAndMetadata: jest.fn(), + setUnedited: jest.fn(), +})) + +jest.mock('../src/components/sourceEditor/SourceEditor', () => { + return { + SourceEditor: jest.fn().mockImplementation(() => ({ + initialize: jest.fn(), + getValue: jest.fn(() => 'editor value'), + focusEditor: jest.fn(), + setReadOnly: jest.fn(), + replaceContent: jest.fn(), + destroy: jest.fn(), + })), + } +}) + +const { fetchContentAndMetadata, setUnedited } = require('../src/helpers') +const { SourceEditor } = require('../src/components/sourceEditor/SourceEditor') +require('../src/components/sourceEditor/SourceEditorCard') + +describe('source-editor-card', () => { + beforeEach(() => { + document.body.innerHTML = '' + jest.clearAllMocks() + }) + + it('renders the filename and initializes the editor with fetched content', async () => { + fetchContentAndMetadata.mockResolvedValue({ + content: 'hello world', + metadata: { + contentType: 'text/turtle', + allowed: 'GET', + eTag: '"123"', + }, + }) + + const card = document.createElement('source-editor-card') + card.store = { fetcher: {} } + card.subject = { + uri: 'https://testingsolidos.solidcommunity.net/profile/card', + value: 'https://testingsolidos.solidcommunity.net/profile/card', + } + card.sourcePaneState = { + broken: false, + contentType: undefined, + allowed: undefined, + eTag: undefined, + } + + document.body.appendChild(card) + await card.updateComplete + + await new Promise((resolve) => setTimeout(resolve, 0)) + + const editorInstance = SourceEditor.mock.results[0].value + expect(fetchContentAndMetadata).toHaveBeenCalledWith(card.store, card.subject, card.sourcePaneState) + expect(editorInstance.initialize).toHaveBeenCalled() + expect(setUnedited).toHaveBeenCalledWith(card.subject, card.sourcePaneState) + expect(card.shadowRoot.textContent).toContain('card') + }) + + it('delegates editor API methods', () => { + const card = document.createElement('source-editor-card') + const editorInstance = SourceEditor.mock.results[0]?.value ?? { + getValue: jest.fn(() => 'editor value'), + focusEditor: jest.fn(), + setReadOnly: jest.fn(), + replaceContent: jest.fn(), + } + card._editor = editorInstance + + expect(card.getValue()).toBe('editor value') + + card.focusEditor() + expect(editorInstance.focusEditor).toHaveBeenCalled() + + card.setReadOnly(true) + expect(editorInstance.setReadOnly).toHaveBeenCalledWith(true) + + card.setValue('updated') + expect(editorInstance.replaceContent).toHaveBeenCalledWith('updated') + }) +}) diff --git a/test/sourcePane.test.js b/test/sourcePane.test.js index 8f0d2e1..8fb24e0 100644 --- a/test/sourcePane.test.js +++ b/test/sourcePane.test.js @@ -1,102 +1,81 @@ -const { context, doc, subject } = require('./helpers/setup') +jest.mock('../src/components/sourceEditor/SourceEditorCard', () => { + if (!globalThis.customElements.get('source-editor-card')) { + class MockSourceEditorCard extends globalThis.HTMLElement { + getValue() { + return '' + } + + setValue() {} + + setReadOnly() {} + + focusEditor() {} + } + + globalThis.customElements.define('source-editor-card', MockSourceEditorCard) + } + + return globalThis.customElements.get('source-editor-card') +}) + +const { context } = require('./helpers/setup') const paneModule = require('../src/sourcePane') const pane = paneModule.default || paneModule -const { findByText, fireEvent, getByTitle, waitFor } = require('@testing-library/dom') -const fetchMock = require('jest-fetch-mock') -const { parse, sym } = require('rdflib') -const { solidLogicSingleton } = require('solid-logic') - -describe("source-pane", () => { - describe("test button compact", () => { - let result - - describe("text/turtle file", () => { - beforeAll(() => { - const subject = sym("https://janedoe.example/test.ttl") - fetchMock.mockOnceIf( - subject.uri, - `<> a "test".`, - { - headers: { - "Content-Type": "text/turtle", - }, - } - ); - result = pane.render(subject, context); - }); - - it.skip('button exist and is visible', async () => { - const compact = await findByText(result, 'COMPACT') - console.log(compact.style) - expect(compact.style.visibility).toEqual('visible') - }) - - it.skip('click "compact", button cancel is visible', async () => { - const compact = await findByText(result, 'COMPACT') - fireEvent.click(compact) - const cancel = await getByTitle(result, 'Cancel') - expect(cancel.style.visibility).toEqual('visible') - }) - - it('click "edit", button compact is not visible', async () => { - const edit = await getByTitle(result, 'Edit') - fireEvent.click(edit) - const compact = await findByText(result, 'COMPACT') - expect(compact.style.visibility).not.toEqual('visible') - }) - - it.skip('check content succeeds but should fail', async () => { - waitFor(() => { expect(result).toContainHTML('<> a "1111".') }) - }) +const { fireEvent } = require('@testing-library/dom') +const { setUnedited } = require('../src/helpers') + +describe('source-pane', () => { + afterEach(() => { + document.body.innerHTML = '' + jest.restoreAllMocks() + }) + + it('shows compact for compactable content and hides it for plain text', () => { + const turtleSubject = { uri: 'https://janedoe.example/test.ttl' } + const turtlePane = pane.render(turtleSubject, context) + document.body.appendChild(turtlePane) + + setUnedited(turtleSubject, { + broken: false, + contentType: 'text/turtle', + allowed: 'GET,PUT', + eTag: '"123"', }) - describe("text/plain file", () => { - beforeAll(() => { - const subject = sym("https://janedoe.example/test.txt") - fetchMock.mockOnceIf( - subject.uri, - `this is a test`, - { - headers: { - "Content-Type": "text/plain", - }, - } - ) - result = pane.render(subject, context); - }); - - it('button exist and is not visible', async () => { - const compact = await findByText(result, 'COMPACT') - expect(compact).not.toBeNull() - expect(compact.style.visibility).not.toEqual('visible') - }) - - it.skip('check content succeed but should fail', async () => { - waitFor(() => { expect(result).toContainHTML('<> a "1111".') }) - }) + expect(turtlePane.querySelector('.sourcePaneCompactButton').className).toContain('sourcePaneControlVisible') + expect(turtlePane.querySelector('.sourcePaneEditButton').className).toContain('sourcePaneControlVisible') + + document.body.innerHTML = '' + + const textSubject = { uri: 'https://janedoe.example/test.txt' } + const textPane = pane.render(textSubject, context) + document.body.appendChild(textPane) + + setUnedited(textSubject, { + broken: false, + contentType: 'text/plain', + allowed: 'GET,PUT', + eTag: '"123"', }) - describe.skip("container", () => { - beforeAll(() => { - const subject = sym("https://janedoe.example/public/") - fetchMock.mockOnceIf( - subject.uri, - ` `, - { - headers: { - "Allow": "text/turtle", - }, - } - ) - result = pane.render(subject, context); - }); - - it('compact and cancel are visible', async () => { - const compact = await findByText(result, 'COMPACT') - const cancel = await getByTitle(result, 'Cancel') - expect(compact.style.visibility).toEqual('visible') - expect(cancel.style.visibility).toEqual('visible') - }) + expect(textPane.querySelector('.sourcePaneCompactButton').className).toContain('sourcePaneControlHidden') + }) + + it('shows Save after edit and hides Compact until save', () => { + const subject = { uri: 'https://janedoe.example/test.ttl' } + const result = pane.render(subject, context) + document.body.appendChild(result) + + setUnedited(subject, { + broken: false, + contentType: 'text/turtle', + allowed: 'GET,PUT', + eTag: '"123"', }) - }); -}); + + fireEvent.click(result.querySelector('.sourcePaneEditButton')) + + expect(result.querySelector('.sourcePaneSaveButton').className).toContain('sourcePaneControlVisible') + expect(result.querySelector('.sourcePaneCompactButton').className).toContain('sourcePaneControlHidden') + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..35bc1db --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,83 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "ES2019" /* Specify ECMAScript target version - matches your browser support */, + "module": "ESNext" /* Let bundlers handle module transformation for better tree-shaking */, + "moduleResolution": "bundler", /* Use bundler-aware module resolution for modern Webpack builds. */ + /* Crucial for library compatibility: */ + "skipLibCheck": true, // Skip type checking of declaration files for faster builds + "allowSyntheticDefaultImports": true, // Often helps with ESM interop + /* Crucial for library compatibility END */ + "lib": [ + "DOM", + "ES2019", + "ES2022.Error" + ] /* Specify library files to be included in the compilation. */, + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true /* Generates corresponding '.d.ts' file. */, + "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, + "sourceMap": true /* Generates corresponding '.map' file. */, + // "outFile": "./", /* Concatenate and emit output to single file. */ + + "outDir": "lib" /* Redirect output structure to the directory. */, + "rootDir": "src/", + "plugins": [{ "name": "typescript-plugin-css-modules" }], + + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "incremental": true, /* Enable incremental compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": false /* Enable all strict type-checking options. */, + "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, + "strictNullChecks": false /* Enable strict null checks. */, + "strictFunctionTypes": true /* Enable strict checking of function types. */, + "strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */, + "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, + "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, + "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, + + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + "typeRoots": [ + "node_modules/@types" + ] /* List of folders to include type definitions from. */, + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + }, + "include": [ + "src/**/*", + "declarations.d.ts", + "**/*.ttl" + ], + "exclude": ["node_modules", "dist"] +} diff --git a/webpack.config.mjs b/webpack.config.mjs index 2f7a441..b978ff8 100644 --- a/webpack.config.mjs +++ b/webpack.config.mjs @@ -2,13 +2,12 @@ import path from 'path' import { moduleRules } from './webpack.module.rules.mjs' import { createRequire } from 'module' import TerserPlugin from 'terser-webpack-plugin' -import CopyPlugin from 'copy-webpack-plugin' const require = createRequire(import.meta.url) const common = { mode: 'production', - entry: './src/sourcePane.js', + entry: './src/sourcePane.ts', module: { rules: moduleRules, }, @@ -57,14 +56,6 @@ const normalConfig = { }, plugins: [ ...(common.plugins || []), - new CopyPlugin({ - patterns: [ - { - from: path.resolve('src/styles'), - to: path.resolve('lib/styles'), - }, - ], - }), ], optimization: { minimize: false, diff --git a/webpack.module.rules.mjs b/webpack.module.rules.mjs index c1c9aac..bc13381 100644 --- a/webpack.module.rules.mjs +++ b/webpack.module.rules.mjs @@ -1,12 +1,19 @@ +import postCSSConfig from './config/postcss.mjs' + export const moduleRules = [ { test: /\.(js|ts)$/, exclude: /node_modules/, use: ['babel-loader'], }, + { + test: /\.styles\.css$/, + loader: 'lit-css-loader', + options: postCSSConfig, + }, { test: /\.css$/, - exclude: /\.module\.css$/, + exclude: [/\.module\.css$/, /\.styles\.css$/], use: ['style-loader', 'css-loader'], }, {