diff --git a/README.md b/README.md index d1db972..71f99ac 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,31 @@ Building into the core is easy, just run `npm run build` -This will override the `app/assets/javascripts/vendor/ckeditor/*` contents with the newest webpack build. You need to run this before opening a pull request. +This will override the `frontend/src/vendor/ckeditor/*` contents with the newest webpack build. You need to run this before opening a pull request. > [!important] > Please ensure that for any changes in this repository, you have a core repository with the output of `npm run build`, so that all core tests can run and confirm your changes. Both pull requests should _always_ be merged at the same time, never alone +The generated output is written to: + +`frontend/src/vendor/ckeditor/*` + +### TypeScript types for downstream + +This package now emits declaration files into `build/types`. +During `npm run build`, those declarations are also copied into: + +- `frontend/src/vendor/ckeditor/types.d.ts` +- `frontend/src/vendor/ckeditor/op-ckeditor.d.ts` + +Downstream consumers can import shared editor interfaces from: + +```ts +import type { ICKEditorInstance, ICKEditorStatic } from '@openproject/commonmark-ckeditor-build/types'; +``` + +The main package entry also exports these interfaces. + ### Updating CKEditor @@ -54,7 +74,7 @@ We use `patch-package` (https://www.npmjs.com/package/patch-package) to store a - Run `npm run watch` -Now the webpack development mode is building the files and outputting them to `app/assets/javascripts/vendor/ckeditor/*`, overriding anything in there. +Now the webpack development mode is building the files and outputting them to `frontend/src/vendor/ckeditor/*`, overriding anything in there. @@ -67,4 +87,3 @@ As of version 11.2.0, this library no longer uses jQuery internally. All jQuery **Important for downstream consumers (e.g., OpenProject):** While this library no longer uses jQuery internally, downstream applications should continue to expose the jQuery global if other parts of the application depend on it. Do not remove the jQuery global from the downstream application (OpenProject) yet until all components have been migrated. For more details on the downstream migration, see: https://github.com/opf/openproject/pull/19429 - diff --git a/babel.config.js b/babel.config.js index 93efe0a..ab11bea 100644 --- a/babel.config.js +++ b/babel.config.js @@ -8,6 +8,7 @@ module.exports = { }, }, ], + "@babel/preset-typescript", ], plugins: ["@babel/plugin-transform-modules-commonjs"] } diff --git a/bin/clean.sh b/bin/clean.sh index fcab97c..7ca20a9 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -6,3 +6,5 @@ BUILD_FOLDER=$(realpath "${OPENPROJECT_CORE}/frontend/src/vendor/ckeditor/") echo "Clearing current build folder ${BUILD_FOLDER}" rm -rf "${BUILD_FOLDER}/ckeditor.*" || true rm -rf "${BUILD_FOLDER}/translations/" || true +rm -rf "${BUILD_FOLDER}/types.d.ts" || true +rm -rf "${BUILD_FOLDER}/op-ckeditor.d.ts" || true diff --git a/bin/copy-types.sh b/bin/copy-types.sh new file mode 100755 index 0000000..0115d26 --- /dev/null +++ b/bin/copy-types.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -eu + +: ${OPENPROJECT_CORE?"Need to set OPENPROJECT_CORE"} + +BUILD_FOLDER=$(realpath "${OPENPROJECT_CORE}/frontend/src/vendor/ckeditor/") +TYPES_FOLDER="build/types" + +if [ ! -f "${TYPES_FOLDER}/ckeditor-types.d.ts" ]; then + echo "Missing generated type file: ${TYPES_FOLDER}/ckeditor-types.d.ts" + exit 1 +fi + +cp "${TYPES_FOLDER}/ckeditor-types.d.ts" "${BUILD_FOLDER}/types.d.ts" +cp "${TYPES_FOLDER}/op-ckeditor.d.ts" "${BUILD_FOLDER}/op-ckeditor.d.ts" diff --git a/eslint.config.mjs b/eslint.config.mjs index 8ad454c..64d32cf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,15 +1,17 @@ import globals from "globals"; import eslint from '@eslint/js'; import jestPlugin from 'eslint-plugin-jest'; +import tsParser from '@typescript-eslint/parser'; export default [ eslint.configs.recommended, { - ignores: ["tmp/", "coverage/", "node_modules/"], + ignores: ["tmp/", "coverage/", "node_modules/", "src/**/*.d.ts"], }, { - files: ["src/**/*.js"], + files: ["src/**/*.ts"], languageOptions: { + parser: tsParser, globals: { ...globals.browser, "jQuery": true, @@ -19,18 +21,7 @@ export default [ }, rules: { "no-cond-assign": "off", - "no-unused-vars": [ - "error", - { - // "args": "all", - "argsIgnorePattern": "^_", - // "caughtErrors": "all", - "caughtErrorsIgnorePattern": "^_", - // "destructuredArrayIgnorePattern": "^_", - "varsIgnorePattern": "^_", - // "ignoreRestSiblings": true - } - ], + "no-unused-vars": "off", "no-undef": "error" } }, diff --git a/jest.config.js b/jest.config.js index fe02d92..a4316c6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,7 +17,7 @@ module.exports = { ], // A map from regular expressions to paths to transformers transform: { - '^.+\\.js$': 'babel-jest', + '^.+\\.[jt]s$': 'babel-jest', }, // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module moduleNameMapper: { @@ -25,6 +25,11 @@ module.exports = { }, // The test environment that will be used for testing testEnvironment: "jsdom", + moduleFileExtensions: [ + "ts", + "js", + "json" + ], // The paths to modules that run some code to configure or set up the testing environment before each test setupFiles: ['/jest.setup.js'], // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation diff --git a/package-lock.json b/package-lock.json index 5ab2706..7767355 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@babel/core": "^7.26.10", "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/preset-env": "^7.26.9", + "@babel/preset-typescript": "^7.27.1", "@ckeditor/ckeditor5-adapter-ckfinder": "44.3.0", "@ckeditor/ckeditor5-autoformat": "44.3.0", "@ckeditor/ckeditor5-autosave": "^44.3.0", @@ -35,6 +36,7 @@ "@ckeditor/ckeditor5-image": "44.3.0", "@ckeditor/ckeditor5-link": "44.3.0", "@ckeditor/ckeditor5-list": "44.3.0", + "@ckeditor/ckeditor5-markdown-gfm": "^44.3.0", "@ckeditor/ckeditor5-media-embed": "44.3.0", "@ckeditor/ckeditor5-mention": "44.3.0", "@ckeditor/ckeditor5-page-break": "44.3.0", @@ -51,6 +53,7 @@ "@ckeditor/ckeditor5-widget": "44.3.0", "@eslint/js": "^9.16.0", "@rails/request.js": "^0.0.13", + "@typescript-eslint/parser": "^8.56.1", "babel-jest": "^29.7.0", "css-loader": "^7.1.2", "eslint": "^9.23.0", @@ -66,8 +69,10 @@ "raw-loader": "^4.0.2", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.14", + "ts-loader": "^9.5.2", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", + "typescript": "^5.8.2", "underscore": "^1.13.7", "webpack": "^5.98.0", "webpack-bundle-analyzer": "^4.10.2", @@ -105,15 +110,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -171,16 +176,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -188,13 +193,13 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -228,18 +233,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -303,44 +308,54 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -350,22 +365,22 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -391,15 +406,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -409,23 +424,23 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -433,9 +448,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -443,9 +458,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -482,13 +497,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -686,12 +701,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -788,12 +804,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1215,14 +1232,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1598,6 +1615,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", @@ -1772,6 +1809,26 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", @@ -1786,58 +1843,48 @@ } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -3907,18 +3954,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -3930,16 +3973,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", @@ -3951,15 +3984,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -4323,6 +4357,197 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", @@ -4341,6 +4566,23 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/@typescript-eslint/types": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", @@ -6177,12 +6419,13 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -6802,6 +7045,7 @@ "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -10436,10 +10680,11 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", @@ -12393,12 +12638,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -12406,17 +12649,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -13102,6 +13334,55 @@ "readable-stream": "2 || 3" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -13177,9 +13458,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -13189,6 +13470,37 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/turndown": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", @@ -14026,7 +14338,8 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/yaml": { "version": "1.10.2", @@ -14322,14 +14635,14 @@ } }, "@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" } }, "@babel/compat-data": { @@ -14371,25 +14684,25 @@ } }, "@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "requires": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "requires": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" } }, "@babel/helper-compilation-targets": { @@ -14414,17 +14727,17 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "dependencies": { @@ -14468,50 +14781,56 @@ "resolve": "^1.14.2" } }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true + }, "@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" } }, "@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" } }, "@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "requires": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" } }, "@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true }, "@babel/helper-remap-async-to-generator": { @@ -14526,42 +14845,42 @@ } }, "@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, "@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true }, "@babel/helper-wrap-function": { @@ -14586,12 +14905,12 @@ } }, "@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "requires": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.29.0" } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { @@ -14714,12 +15033,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "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==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -14786,12 +15105,12 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "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==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-syntax-unicode-sets-regex": { @@ -15038,13 +15357,13 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" } }, "@babel/plugin-transform-modules-systemjs": { @@ -15260,6 +15579,19 @@ "@babel/helper-plugin-utils": "^7.26.5" } }, + "@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + } + }, "@babel/plugin-transform-unicode-escapes": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", @@ -15395,6 +15727,19 @@ "esutils": "^2.0.2" } }, + "@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + } + }, "@babel/runtime": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", @@ -15405,47 +15750,39 @@ } }, "@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" } }, "@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "requires": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, "@bcoe/v8-coverage": { @@ -17030,13 +17367,12 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, @@ -17046,12 +17382,6 @@ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, "@jridgewell/source-map": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", @@ -17063,15 +17393,15 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -17400,6 +17730,113 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + } + }, + "@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "requires": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + } + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "requires": { + "balanced-match": "^4.0.2" + } + }, + "eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true + }, + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "requires": { + "brace-expansion": "^5.0.2" + } + } + } + }, + "@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true + } + } + }, "@typescript-eslint/scope-manager": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", @@ -17410,6 +17847,13 @@ "@typescript-eslint/visitor-keys": "8.29.0" } }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "requires": {} + }, "@typescript-eslint/types": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", @@ -18725,12 +19169,12 @@ "dev": true }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decamelize": { @@ -19168,6 +19612,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -21725,9 +22170,9 @@ "dev": true }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "nanoid": { @@ -22964,22 +23409,9 @@ } }, "semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "requires": { - "lru-cache": "^6.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - } - } + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" }, "serialize-javascript": { "version": "6.0.2", @@ -23450,6 +23882,32 @@ "readable-stream": "2 || 3" } }, + "tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "peer": true + } + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -23508,12 +23966,33 @@ } }, "ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "requires": {} }, + "ts-loader": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "dependencies": { + "source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true + } + } + }, "turndown": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", @@ -24094,7 +24573,8 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index f876a7e..34820e6 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,17 @@ "rich-text editor" ], "main": "./build/ckeditor.js", + "types": "./build/types/op-ckeditor.d.ts", + "exports": { + ".": { + "types": "./build/types/op-ckeditor.d.ts", + "default": "./build/ckeditor.js" + }, + "./types": { + "types": "./build/types/ckeditor-types.d.ts", + "default": "./build/types/ckeditor-types.d.ts" + } + }, "files": [ "build" ], @@ -17,6 +28,7 @@ "@babel/core": "^7.26.10", "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/preset-env": "^7.26.9", + "@babel/preset-typescript": "^7.27.1", "@ckeditor/ckeditor5-adapter-ckfinder": "44.3.0", "@ckeditor/ckeditor5-autoformat": "44.3.0", "@ckeditor/ckeditor5-autosave": "^44.3.0", @@ -36,6 +48,7 @@ "@ckeditor/ckeditor5-image": "44.3.0", "@ckeditor/ckeditor5-link": "44.3.0", "@ckeditor/ckeditor5-list": "44.3.0", + "@ckeditor/ckeditor5-markdown-gfm": "^44.3.0", "@ckeditor/ckeditor5-media-embed": "44.3.0", "@ckeditor/ckeditor5-mention": "44.3.0", "@ckeditor/ckeditor5-page-break": "44.3.0", @@ -52,6 +65,7 @@ "@ckeditor/ckeditor5-widget": "44.3.0", "@eslint/js": "^9.16.0", "@rails/request.js": "^0.0.13", + "@typescript-eslint/parser": "^8.56.1", "babel-jest": "^29.7.0", "css-loader": "^7.1.2", "eslint": "^9.23.0", @@ -67,8 +81,10 @@ "raw-loader": "^4.0.2", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.14", + "ts-loader": "^9.5.2", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", + "typescript": "^5.8.2", "underscore": "^1.13.7", "webpack": "^5.98.0", "webpack-bundle-analyzer": "^4.10.2", @@ -89,8 +105,10 @@ }, "scripts": { "prebuild": "sh bin/clean.sh", - "build": "NODE_ENV=production ./node_modules/.bin/webpack --mode production", - "preversion": "npm run build; if [ -n \"$(git status src/ckeditor.js build/ --porcelain)\" ]; then git add -u src/ckeditor.js build/ && git commit -m 'Internal: Build.'; fi", + "build": "NODE_ENV=production ./node_modules/.bin/webpack --mode production && npm run build:types", + "build:types": "tsc -p tsconfig.json && sh bin/copy-types.sh", + "typecheck": "tsc -p tsconfig.json --noEmit", + "preversion": "npm run build; if [ -n \"$(git status src/op-ckeditor.ts build/ --porcelain)\" ]; then git add -u src/op-ckeditor.ts build/ && git commit -m 'Internal: Build.'; fi", "prewatch": "sh bin/clean.sh", "postinstall": "patch-package", "watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --stats-error-details", diff --git a/src/ckeditor-types.ts b/src/ckeditor-types.ts new file mode 100644 index 0000000..8d0f387 --- /dev/null +++ b/src/ckeditor-types.ts @@ -0,0 +1,55 @@ +import type { Editor, EditorConfig, PluginConstructor } from '@ckeditor/ckeditor5-core'; +import type EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog'; +import type { WatchdogState } from '@ckeditor/ckeditor5-watchdog/src/watchdog'; +import type { CKEditorError } from '@ckeditor/ckeditor5-utils'; + +export interface CKEditorEvent { + stop: () => void; +} + +export interface CKEditorListenOptions { + priority?: string; +} + +export interface CKEditorDomEventData { + altKey: boolean; + shiftKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + keyCode: number; +} + +export type ICKEditorInstance = Editor; + +export interface ICKEditorStatic { + create(el: HTMLElement, config?: EditorConfig): Promise; + createCustomized(el: string | HTMLElement, config?: EditorConfig): Promise; + builtinPlugins: PluginConstructor[]; + defaultConfig?: EditorConfig; +} + +export type ICKEditorState = WatchdogState; + +export type ICKEditorError = CKEditorError; + +export type ICKEditorWatchdog = typeof EditorWatchdog; + +export type ICKEditorMentionType = "user" | "work_package"; + +type OpenProjectContextValue = string | number | boolean | null | undefined | string[]; + +export interface ICKEditorContext { + resource?: { + canAddAttachments?: boolean; + [key: string]: OpenProjectContextValue; + }; + field?: string; + removePlugins?: string[]; + macros?: false | string[]; + options?: { + rtl?: boolean; + }; + previewContext?: string; + disabledMentions?: ICKEditorMentionType[]; + storageKey?: string; +} diff --git a/src/commonmark/commonmark.js b/src/commonmark/commonmark.js deleted file mode 100644 index 4c3d718..0000000 --- a/src/commonmark/commonmark.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import CommonMarkDataProcessor from './commonmarkdataprocessor'; - -// Simple plugin which loads the data processor. -export default function CommonMarkPlugin(editor) { - editor.data.processor = new CommonMarkDataProcessor(editor.editing.view.document); -} - diff --git a/src/commonmark/commonmark.ts b/src/commonmark/commonmark.ts new file mode 100644 index 0000000..d8c82cb --- /dev/null +++ b/src/commonmark/commonmark.ts @@ -0,0 +1,34 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import CommonMarkDataProcessor from './commonmarkdataprocessor'; +import OpenProjectGFMDataProcessor from './op-gfm-data-processor'; + +interface CommonMarkEditorConfig { + get(path:string):unknown; +} + +interface CommonMarkEditor { + data:{ + processor:unknown; + }; + config?:CommonMarkEditorConfig; + editing:{ + view:{ + document:unknown; + }; + }; +} + +function useExperimentalGfmProcessor(editor:CommonMarkEditor):boolean { + return editor.config?.get('openProject.useExperimentalGfmDataProcessor') === true; +} + +// Simple plugin which loads the data processor. +export default function CommonMarkPlugin(editor:CommonMarkEditor) { + editor.data.processor = useExperimentalGfmProcessor(editor) + ? new OpenProjectGFMDataProcessor(editor.editing.view.document as never) + : new CommonMarkDataProcessor(editor.editing.view.document); +} diff --git a/src/commonmark/commonmarkdataprocessor.js b/src/commonmark/commonmarkdataprocessor.ts similarity index 90% rename from src/commonmark/commonmarkdataprocessor.js rename to src/commonmark/commonmarkdataprocessor.ts index c2d51db..187ca87 100644 --- a/src/commonmark/commonmarkdataprocessor.js +++ b/src/commonmark/commonmarkdataprocessor.ts @@ -28,7 +28,10 @@ export const originalSrcAttribute = 'data-original-src'; * @implements module:engine/dataprocessor/dataprocessor~DataProcessor */ export default class CommonMarkDataProcessor { - constructor(document) { + _htmlDP:any; + _domConverter:any; + + constructor(document:any) { this._htmlDP = new HtmlDataProcessor(document); this._domConverter = new DomConverter(document); } @@ -39,7 +42,7 @@ export default class CommonMarkDataProcessor { * @param {String} data A CommonMark string. * @returns {module:engine/view/documentfragment~DocumentFragment} The converted view element. */ - toView(data) { + toView(data:any) { const md = markdownIt({ // Output html html: true, @@ -91,7 +94,7 @@ export default class CommonMarkDataProcessor { * @param {module:engine/view/documentfragment~DocumentFragment} viewFragment * @returns {String} CommonMark string. */ - toData(viewFragment) { + toData(viewFragment:any) { // Convert view DocumentFragment to DOM DocumentFragment. const domFragment = this._domConverter.viewToDom(viewFragment, document); @@ -102,14 +105,15 @@ export default class CommonMarkDataProcessor { ['strong', 'em'], // Ensure tables are allowed to have HTML contents // OP#29457 - ['pre', 'code', 'table'] + ['pre', 'code', 'table'], + [] ); // Replace link attributes with their computed href attribute - linkPreprocessor(domFragment); + linkPreprocessor(domFragment, [], []); // Turndown is filtering out empty paragraphs

, so we need to fix that with


- breaksPreprocessor(domFragment); + breaksPreprocessor(domFragment, [], []); const blankReplacement = function (content, node) { if (node.tagName === 'CODE') { @@ -146,13 +150,13 @@ export default class CommonMarkDataProcessor { */ turndownService.addRule('taskListItems', { filter: function (node) { - const nodeIsCheckbox = node.type === "checkbox"; + const nodeIsCheckbox = (node as any).type === "checkbox"; const parentIsListItem = node.parentNode && node.parentNode.nodeName === 'LI'; const grandparentIsListItem = node.parentNode && node.parentNode.parentNode && node.parentNode.parentNode.nodeName === 'LI'; return nodeIsCheckbox && (parentIsListItem || grandparentIsListItem); }, replacement: function (content, node) { - return (node.checked ? '[x]' : '[ ]') + ' ' + return ((node as any).checked ? '[x]' : '[ ]') + ' ' } }) @@ -185,7 +189,7 @@ export default class CommonMarkDataProcessor { .replace(/^\n+/, '') // remove leading newlines .replace(/\n+$/, '\n'); // replace trailing newlines with just a single one - var parent = node.parentNode; + const parent:any = node.parentNode; var prefix = options.bulletListMarker + ' '; var number = 1; if (parent.nodeName === 'OL') { @@ -209,12 +213,12 @@ export default class CommonMarkDataProcessor { turndownService.addRule('imageFigure', { filter: 'img', replacement: function (content, node) { - const parent = node.parentElement; + const parent:any = node.parentElement; if (parent && parent.classList.contains('op-uc-figure--content')) { return parent.parentElement.outerHTML; } - return node.outerHTML; + return (node as any).outerHTML; } }); @@ -232,13 +236,13 @@ export default class CommonMarkDataProcessor { return node.nodeName === 'TABLE' && (!node.parentElement || node.parentElement.nodeName !== 'FIGURE'); }, replacement: function (_content, node) { - return node.outerHTML; // we do not convert back to markdown, but use HTML for tables + return (node as any).outerHTML; // we do not convert back to markdown, but use HTML for tables } }); // Keep HTML tables and remove filler elements - turndownService.addRule('htmlTables', { - filter: function (node) { + turndownService.addRule('htmlTables', { + filter: function (node:any) { const tables = node.getElementsByTagName('table'); // check if we're a todo list item return node.nodeName === 'FIGURE' && tables.length; @@ -251,22 +255,22 @@ export default class CommonMarkDataProcessor { } }); - return node.outerHTML; + return (node as any).outerHTML; } }); turndownService.addRule('strikethrough', { - filter: ['del', 's', 'strike'], + filter: ['del', 's', 'strike'] as any, replacement: function (content) { return '~~' + content + '~~' } }); turndownService.addRule('openProjectMacros', { - filter: ['macro'], + filter: ['macro'] as any, replacement: (_content, node) => { - node.innerHTML = ''; - const outer = node.outerHTML; + (node as any).innerHTML = ''; + const outer = (node as any).outerHTML; return outer.replace("", "\n") } }); @@ -278,7 +282,7 @@ export default class CommonMarkDataProcessor { node.classList.contains('mention') ) }, - replacement: (_content, node) => node.outerHTML, + replacement: (_content, node) => (node as any).outerHTML, }); turndownService.addRule('emptyParagraphs', { @@ -293,7 +297,7 @@ export default class CommonMarkDataProcessor { replacement: (_content, node) => { if (!node.parentElement && !node.nextSibling && !node.previousSibling) { //document with only one empty paragraph return ''; - } else if (node.childNodes.length === 1 && isPageBreakNode(node.childNodes[0])) { + } else if (node.childNodes.length === 1 && isPageBreakNode(node.childNodes[0] as any)) { return PAGE_BREAK_MARKDOWN + '\n\n' } else { return '
\n\n' diff --git a/src/commonmark/op-gfm-data-processor.ts b/src/commonmark/op-gfm-data-processor.ts new file mode 100644 index 0000000..487af17 --- /dev/null +++ b/src/commonmark/op-gfm-data-processor.ts @@ -0,0 +1,41 @@ +import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor'; +import type { ViewDocument, ViewDocumentFragment } from '@ckeditor/ckeditor5-engine'; +import CommonMarkDataProcessor from './commonmarkdataprocessor'; +import { PAGE_BREAK_MARKDOWN } from './utils/page-breaks'; + +export default class OpenProjectGFMDataProcessor { + private readonly gfm:GFMDataProcessor; + private readonly legacy:CommonMarkDataProcessor; + + constructor(document:ViewDocument) { + this.gfm = new GFMDataProcessor(document); + this.legacy = new CommonMarkDataProcessor(document); + + // Preserve OpenProject-specific HTML nodes in markdown output. + this.gfm.keepHtml('macro' as never); + this.gfm.keepHtml('mention' as never); + this.gfm.keepHtml('table'); + this.gfm.keepHtml('figure'); + this.gfm.keepHtml('div'); + } + + toView(data:string):ViewDocumentFragment { + return this.gfm.toView(data); + } + + toData(viewFragment:ViewDocumentFragment):string { + const markdown = this.gfm.toData(viewFragment); + const legacyMarkdown = this.legacy.toData(viewFragment); + + // Transitional shim: preserve OpenProject's legacy table serialization strategy. + if (legacyMarkdown.includes('<\/div>/g, PAGE_BREAK_MARKDOWN) + // Keep legacy macro formatting with a trailing newline before closing tag. + .replace(/]*)><\/macro>/g, '\\n'); + } +} diff --git a/src/commonmark/utils/fix-breaks.js b/src/commonmark/utils/fix-breaks.ts similarity index 56% rename from src/commonmark/utils/fix-breaks.js rename to src/commonmark/utils/fix-breaks.ts index dc710a7..21876d4 100644 --- a/src/commonmark/utils/fix-breaks.js +++ b/src/commonmark/utils/fix-breaks.ts @@ -7,26 +7,30 @@ import {isPageBreakNode} from "./page-breaks"; * e.g. `

Demo


End

` converted to `

Demo



End

` * to avoid this, we remove the breaks, so CKEditor can add `
` * e.g. `

Demo


End

` converted to `

Demo


End

` */ -export function fixBreaksInTables(root) { +export function fixBreaksInTables(root:Node) { const walker = document.createNodeIterator( root, // Only consider element nodes NodeFilter.SHOW_ELEMENT, - // Only except text nodes whose parent is one of parents { - acceptNode: function (node) { - if (node.tagName === 'P' && node.parentElement && - node.parentElement.tagName === 'TD' && - (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'BR')) { + acceptNode: function (node:Node) { + if (!(node instanceof Element)) { + return NodeFilter.FILTER_REJECT; + } + + const onlyBreak = node.childNodes.length === 1 && node.childNodes[0].nodeName === 'BR'; + if (node.tagName === 'P' && node.parentElement?.tagName === 'TD' && onlyBreak) { return NodeFilter.FILTER_ACCEPT; } + + return NodeFilter.FILTER_REJECT; } } ); - let node; - while (node = walker.nextNode()) { - node.childNodes[0].remove(); + let node:Node | null; + while ((node = walker.nextNode())) { + node.firstChild?.remove(); } } @@ -39,31 +43,33 @@ export function fixBreaksInTables(root) { * e.g. `

Demo



End

` will be converted to `

Demo

End

` * (except for page breaks, which are kept but are wrapped in a paragraph) */ -export function fixBreaksOnRootLevel(root) { - let walker = document.createNodeIterator( +export function fixBreaksOnRootLevel(root:Node) { + const walker = document.createNodeIterator( root, NodeFilter.SHOW_ELEMENT, { - acceptNode: function (node) { - if (node.tagName === 'BR' && !node.parentElement) { + acceptNode: function (node:Node) { + if (node instanceof Element && node.tagName === 'BR' && !node.parentElement) { return NodeFilter.FILTER_ACCEPT; } + + return NodeFilter.FILTER_REJECT; } } ); - let node; - let list = [] - while (node = walker.nextNode()) { + const list:Node[] = []; + let node:Node | null; + while ((node = walker.nextNode())) { list.push(node); } - for (const node of list) { - const p = document.createElement('p'); - root.insertBefore(p, node); - if (isPageBreakNode(node)) { - p.appendChild(node); + for (const breakNode of list) { + const paragraph = document.createElement('p'); + root.insertBefore(paragraph, breakNode); + if (breakNode instanceof Element && isPageBreakNode(breakNode)) { + paragraph.appendChild(breakNode); } else { - node.remove(); + breakNode.parentNode?.removeChild(breakNode); } } } @@ -78,26 +84,28 @@ export function fixBreaksOnRootLevel(root) { * e.g. `
  • Start



    End

  • ` will be converted to * `
  • Start

    End

  • >` */ -export function fixBreaksInLists(root) { +export function fixBreaksInLists(root:Node) { const walker = document.createNodeIterator( root, NodeFilter.SHOW_ELEMENT, { - acceptNode: function (node) { - if (node.tagName === 'BR' && node.parentElement && node.parentElement.tagName === 'LI') { + acceptNode: function (node:Node) { + if (node instanceof Element && node.tagName === 'BR' && node.parentElement?.tagName === 'LI') { return NodeFilter.FILTER_ACCEPT; } + + return NodeFilter.FILTER_REJECT; } } ); - let node; - let list = [] - while (node = walker.nextNode()) { + const list:Node[] = []; + let node:Node | null; + while ((node = walker.nextNode())) { list.push(node); } - for (const node of list) { - node.parentElement.insertBefore(document.createElement('p'), node); - node.remove(); + for (const breakNode of list) { + breakNode.parentElement?.insertBefore(document.createElement('p'), breakNode); + breakNode.parentNode?.removeChild(breakNode); } } diff --git a/src/commonmark/utils/fix-tasklist-whitespaces.js b/src/commonmark/utils/fix-tasklist-whitespaces.ts similarity index 64% rename from src/commonmark/utils/fix-tasklist-whitespaces.js rename to src/commonmark/utils/fix-tasklist-whitespaces.ts index 3c98211..53e92bd 100644 --- a/src/commonmark/utils/fix-tasklist-whitespaces.js +++ b/src/commonmark/utils/fix-tasklist-whitespaces.ts @@ -1,20 +1,19 @@ - /** * Remove multiple whitespaces in task list text nodes */ -export function fixTasklistWhitespaces(root) { +export function fixTasklistWhitespaces(root:Node) { let walker = document.createNodeIterator( root, // Only consider text nodes NodeFilter.SHOW_TEXT, ); - let node; - while(node = walker.nextNode()) { + let node:Text|null; + while((node = walker.nextNode() as Text | null)) { // Remove duplicate whitespace in tasklists if (node.previousElementSibling && node.previousElementSibling.classList.contains('task-list-item-checkbox')) { - node.textContent = node.textContent.replace(/^\s+/, ''); + node.textContent = (node.textContent || '').replace(/^\s+/, ''); } } } diff --git a/src/commonmark/utils/hoist-task-list-checkboxes.js b/src/commonmark/utils/hoist-task-list-checkboxes.ts similarity index 58% rename from src/commonmark/utils/hoist-task-list-checkboxes.js rename to src/commonmark/utils/hoist-task-list-checkboxes.ts index 1fc9842..1e99fd2 100644 --- a/src/commonmark/utils/hoist-task-list-checkboxes.js +++ b/src/commonmark/utils/hoist-task-list-checkboxes.ts @@ -1,7 +1,13 @@ -export function hoistTaskListCheckboxes(fragment) { +interface TaskListContainer { + querySelectorAll(selectors:string):{ + forEach(callback:(checkbox:HTMLInputElement) => void):void; + }; +} + +export function hoistTaskListCheckboxes(fragment:TaskListContainer) { const checkboxes = fragment.querySelectorAll('input.task-list-item-checkbox'); checkboxes.forEach(checkbox => { - const li = checkbox.closest('li.task-list-item'); + const li = checkbox.closest('li.task-list-item'); if (li && checkbox.parentElement !== li) { // Remove checkbox from its current parent checkbox.parentElement && checkbox.parentElement.removeChild(checkbox); @@ -9,4 +15,4 @@ export function hoistTaskListCheckboxes(fragment) { li.insertBefore(checkbox, li.firstChild); } }); -} \ No newline at end of file +} diff --git a/src/commonmark/utils/page-breaks.js b/src/commonmark/utils/page-breaks.ts similarity index 77% rename from src/commonmark/utils/page-breaks.js rename to src/commonmark/utils/page-breaks.ts index 08307ac..09cf01a 100644 --- a/src/commonmark/utils/page-breaks.js +++ b/src/commonmark/utils/page-breaks.ts @@ -1,6 +1,5 @@ - export const PAGE_BREAK_MARKDOWN = '
    '; -export function isPageBreakNode(node) { +export function isPageBreakNode(node:Element) { const style = node.getAttribute('style') || ''; return style.includes('page-break-'); } diff --git a/src/commonmark/utils/preprocessor.js b/src/commonmark/utils/preprocessor.js deleted file mode 100644 index d85de4b..0000000 --- a/src/commonmark/utils/preprocessor.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Replace whitespace of text nodes within the given parents in the given root element. - * @param {*} root An HTMLElement to look for text nodes within - * @param {*} allowed_whitespace_nodes String array of allowed text nodes ( ['STRONG', 'EM'] ... ) - * @param {*} allowed_raw_nodes String array of allowed raw text nodes ( ['PRE', 'CODE'] ... ) - */ -export function textNodesPreprocessor(root, allowed_whitespace_nodes, allowed_raw_nodes) { - allowed_whitespace_nodes = allowed_whitespace_nodes.map(el => el.toUpperCase()); - allowed_raw_nodes = allowed_raw_nodes.map(el => el.toUpperCase()); - - let walker = document.createNodeIterator( - root, - // Only consider text nodes - NodeFilter.SHOW_TEXT, - ); - - let node; - while (node = walker.nextNode()) { - // Strip NBSP whitespace in given nodes - if (node.parentElement && allowed_whitespace_nodes.indexOf(node.parentElement.nodeName) >= 0) { - node.nodeValue = node.nodeValue - .replace(/^[\u00a0]+/g, ' ') - .replace(/[\u00a0]+$/g, ' '); - } - - // Re-encode < and > that would otherwise be output as HTML by turndown - // https://github.com/domchristie/turndown/issues/106 - if (!hasParentOfType(node, allowed_raw_nodes)) { - node.nodeValue = _.escape(node.nodeValue); - } - } -} - -/** - * Replace links of A elements with their computed .href attribute - * https://community.ooject.com/wp/29742 - * @param {} root - * @param {*} allowed_whitespace_nodes - * @param {*} allowed_raw_nodes - */ -export function linkPreprocessor(root, _allowed_whitespace_nodes, _allowed_raw_nodes) { - let walker = document.createNodeIterator( - root, - // Only consider element nodes - NodeFilter.SHOW_ELEMENT, - // Accept only A tags - function (node) { - return node.nodeName.toLowerCase() === 'a' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; - } - ); - - let node; - while (node = walker.nextNode()) { - // node.href is properly escaped, while the attribute is not - // and turndown uses the getAttribute version - node.setAttribute('href', node.href); - } -} - -export function breaksPreprocessor(root, _allowed_whitespace_nodes, _allowed_raw_nodes) { - let walker = document.createNodeIterator( - root, - NodeFilter.SHOW_ELEMENT, - { - acceptNode: function (node) { - if (node.tagName === 'P' && node.childNodes.length === 0 && (!node.parentElement || node.parentElement.tagName === 'LI')) { - return NodeFilter.FILTER_ACCEPT; - } - } - } - ); - - let node; - while (node = walker.nextNode()) { - node.appendChild(document.createElement('br')); - } -} - -export function hasParentOfType(node, tagNames) { - let parent = node.parentElement; - - while (parent) { - if (tagNames.indexOf(parent.tagName) >= 0) { - return true; - } - - parent = parent.parentElement; - } - - return false; -} diff --git a/src/commonmark/utils/preprocessor.ts b/src/commonmark/utils/preprocessor.ts new file mode 100644 index 0000000..b8ddda6 --- /dev/null +++ b/src/commonmark/utils/preprocessor.ts @@ -0,0 +1,100 @@ +/** + * Replace whitespace of text nodes within the given parents in the given root element. + * @param {*} root An HTMLElement to look for text nodes within + * @param {*} allowed_whitespace_nodes String array of allowed text nodes ( ['STRONG', 'EM'] ... ) + * @param {*} allowed_raw_nodes String array of allowed raw text nodes ( ['PRE', 'CODE'] ... ) + */ +export function textNodesPreprocessor(root:Node, allowed_whitespace_nodes:string[], allowed_raw_nodes:string[], _unused:string[] = []) { + allowed_whitespace_nodes = allowed_whitespace_nodes.map(el => el.toUpperCase()); + allowed_raw_nodes = allowed_raw_nodes.map(el => el.toUpperCase()); + + const walker = document.createNodeIterator( + root, + // Only consider text nodes + NodeFilter.SHOW_TEXT, + ); + + let node:Node | null; + while ((node = walker.nextNode())) { + const textNode = node as Text; + const value = textNode.nodeValue ?? ''; + + // Strip NBSP whitespace in given nodes + if (textNode.parentElement && allowed_whitespace_nodes.includes(textNode.parentElement.nodeName)) { + textNode.nodeValue = value + .replace(/^[\u00a0]+/g, ' ') + .replace(/[\u00a0]+$/g, ' '); + } + + // Re-encode < and > that would otherwise be output as HTML by turndown + // https://github.com/domchristie/turndown/issues/106 + if (!hasParentOfType(textNode, allowed_raw_nodes)) { + textNode.nodeValue = _.escape(textNode.nodeValue ?? ''); + } + } +} + +/** + * Replace links of A elements with their computed .href attribute + * https://community.ooject.com/wp/29742 + * @param {} root + * @param {*} allowed_whitespace_nodes + * @param {*} allowed_raw_nodes + */ +export function linkPreprocessor(root:Node, _allowed_whitespace_nodes:string[], _allowed_raw_nodes:string[]) { + const walker = document.createNodeIterator( + root, + // Only consider element nodes + NodeFilter.SHOW_ELEMENT, + // Accept only A tags + function (node:Node) { + return node instanceof HTMLAnchorElement ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; + } + ); + + let node:Node | null; + while ((node = walker.nextNode())) { + const link = node as HTMLAnchorElement; + // node.href is properly escaped, while the attribute is not + // and turndown uses the getAttribute version + link.setAttribute('href', link.href); + } +} + +export function breaksPreprocessor(root:Node, _allowed_whitespace_nodes:string[], _allowed_raw_nodes:string[]) { + const walker = document.createNodeIterator( + root, + NodeFilter.SHOW_ELEMENT, + { + acceptNode: function (node:Node) { + if (node instanceof Element + && node.tagName === 'P' + && node.childNodes.length === 0 + && (!node.parentElement || node.parentElement.tagName === 'LI')) { + return NodeFilter.FILTER_ACCEPT; + } + + return NodeFilter.FILTER_REJECT; + } + } + ); + + let node:Node | null; + while ((node = walker.nextNode())) { + node.appendChild(document.createElement('br')); + } +} + +export function hasParentOfType(node:Node, tagNames:string[]) { + let parent = node.parentElement; + + while (parent) { + if (tagNames.includes(parent.tagName)) { + return true; + } + + parent = parent.parentElement; + } + + return false; +} diff --git a/src/helpers/button-disabler.js b/src/helpers/button-disabler.js deleted file mode 100644 index 4a82b8f..0000000 --- a/src/helpers/button-disabler.js +++ /dev/null @@ -1,47 +0,0 @@ -import {FileDialogButtonView} from '@ckeditor/ckeditor5-ui'; - -export function getToolbarItems(editor) { - editor.__currentlyDisabled = editor.__currentlyDisabled || []; - - if (!editor.ui.view.toolbar) { - return []; - } - - return editor.ui.view.toolbar.items._items; -} - -export function disableItems(editor, except) { - getToolbarItems(editor).forEach((item) => { - let toDisable = item; - - if (item instanceof FileDialogButtonView) { - toDisable = item.buttonView; - } else if (item === except || !Object.prototype.hasOwnProperty.call(item, 'isEnabled')) { - toDisable = null; - } - - if (!toDisable) { - // do nothing - } else if (toDisable.isEnabled) { - toDisable.isEnabled = false; - } else { - editor.__currentlyDisabled.push(toDisable); - } - }); -} - -export function enableItems(editor) { - getToolbarItems(editor).forEach((item) => { - let toEnable = item; - - if (item instanceof FileDialogButtonView) { - toEnable = item.buttonView; - } - - if (editor.__currentlyDisabled.indexOf(toEnable) < 0) { - toEnable.isEnabled = true - } - }); - - editor.__currentlyDisabled = []; -} diff --git a/src/helpers/button-disabler.ts b/src/helpers/button-disabler.ts new file mode 100644 index 0000000..17af811 --- /dev/null +++ b/src/helpers/button-disabler.ts @@ -0,0 +1,69 @@ +import {FileDialogButtonView} from '@ckeditor/ckeditor5-ui'; +import type {Editor} from '@ckeditor/ckeditor5-core'; + +interface ToggleableView { + isEnabled:boolean; +} + +type ToolbarItem = FileDialogButtonView | ToggleableView; + +interface EditorUIViewWithToolbar { + toolbar?:{ + items:Iterable; + }; +} + +interface ToolbarEditor extends Editor { + __currentlyDisabled?:ToggleableView[]; +} + +export function getToolbarItems(editor:ToolbarEditor):ToolbarItem[] { + editor.__currentlyDisabled = editor.__currentlyDisabled || []; + const editorUIView = editor.ui.view as EditorUIViewWithToolbar; + + if (!editorUIView.toolbar) { + return []; + } + + return Array.from(editorUIView.toolbar.items); +} + +export function disableItems(editor:ToolbarEditor, except:ToolbarItem | null) { + getToolbarItems(editor).forEach((item) => { + let toDisable:ToggleableView | null = null; + + if (item instanceof FileDialogButtonView) { + toDisable = item.buttonView as ToggleableView; + } else if (item !== except && typeof item === 'object' && item !== null && 'isEnabled' in item) { + toDisable = item as ToggleableView; + } + + if (toDisable?.isEnabled) { + toDisable.isEnabled = false; + } else if (toDisable) { + editor.__currentlyDisabled.push(toDisable); + } + }); +} + +export function enableItems(editor:ToolbarEditor) { + getToolbarItems(editor).forEach((item) => { + let toEnable:ToggleableView | null = null; + + if (item instanceof FileDialogButtonView) { + toEnable = item.buttonView as ToggleableView; + } else if (typeof item === 'object' && item !== null && 'isEnabled' in item) { + toEnable = item as ToggleableView; + } + + if (!toEnable) { + return; + } + + if (editor.__currentlyDisabled.indexOf(toEnable) < 0) { + toEnable.isEnabled = true + } + }); + + editor.__currentlyDisabled = []; +} diff --git a/src/helpers/create-toolbar-edit-button.js b/src/helpers/create-toolbar-edit-button.ts similarity index 65% rename from src/helpers/create-toolbar-edit-button.js rename to src/helpers/create-toolbar-edit-button.ts index 5d1e7a8..47ebea9 100644 --- a/src/helpers/create-toolbar-edit-button.js +++ b/src/helpers/create-toolbar-edit-button.ts @@ -1,7 +1,11 @@ import imageIcon from '../icons/edit.svg'; import { ButtonView } from '@ckeditor/ckeditor5-ui'; +import type {Editor} from '@ckeditor/ckeditor5-core'; +import type ModelElement from '@ckeditor/ckeditor5-engine/src/model/element'; -export function createToolbarEditButton(editor, name, callback) { +type EditToolbarCallback = (widget:ModelElement) => void; + +export function createToolbarEditButton(editor:Editor, name:string, callback:EditToolbarCallback) { // Add editing button editor.ui.componentFactory.add( name, locale => { const view = new ButtonView( locale ); diff --git a/src/helpers/create-toolbar.js b/src/helpers/create-toolbar.ts similarity index 81% rename from src/helpers/create-toolbar.js rename to src/helpers/create-toolbar.ts index 1606a11..a536d61 100644 --- a/src/helpers/create-toolbar.js +++ b/src/helpers/create-toolbar.ts @@ -1,27 +1,32 @@ import { ToolbarView } from '@ckeditor/ckeditor5-ui'; import { BalloonPanelView } from '@ckeditor/ckeditor5-ui'; +import { ContextualBalloon } from '@ckeditor/ckeditor5-ui'; +import type {Editor, Plugin} from '@ckeditor/ckeditor5-core'; +import type DocumentSelection from '@ckeditor/ckeditor5-engine/src/view/documentselection'; const balloonClassName = 'ck-toolbar-container'; +type IsWidgetSelected = (selection:DocumentSelection) => boolean; + export function createEditToolbar( // Plugin instance - plugin, + plugin:Plugin, // Editor instance - editor, + editor:Editor, // Configuration namespace in op-ckeditor.js - config_namespace, + config_namespace:string, // Callback to check if widget is selected - isWidgetSelected + isWidgetSelected:IsWidgetSelected ) { - const toolbarConfig = editor.config.get( config_namespace + '.toolbar' ); + const toolbarConfig = editor.config.get( config_namespace + '.toolbar' ) as string[] | undefined; // Don't add the toolbar if there is no configuration. if ( !toolbarConfig || !toolbarConfig.length ) { return; } - const _balloon = editor.plugins.get( 'ContextualBalloon' ); + const _balloon = editor.plugins.get( 'ContextualBalloon' ) as ContextualBalloon; const _toolbar = new ToolbarView( editor.locale ); function _checkIsVisible() { @@ -79,8 +84,8 @@ export function createEditToolbar( * * @param {module:core/editor/editor~Editor} editor The editor instance. */ -function repositionContextualBalloon( editor, selectionCallback ) { - const balloon = editor.plugins.get( 'ContextualBalloon' ); +function repositionContextualBalloon( editor:Editor, selectionCallback:IsWidgetSelected ) { + const balloon = editor.plugins.get( 'ContextualBalloon' ) as ContextualBalloon; if ( selectionCallback( editor.editing.view.document.selection ) ) { const position = getBalloonPositionData( editor ); @@ -98,7 +103,7 @@ function repositionContextualBalloon( editor, selectionCallback ) { * @param {module:core/editor/editor~Editor} editor The editor instance. * @returns {module:utils/dom/position~Options} */ -function getBalloonPositionData( editor ) { +function getBalloonPositionData( editor:Editor ) { const editingView = editor.editing.view; const defaultPositions = BalloonPanelView.defaultPositions; diff --git a/src/mentions/emoji-mentions.js b/src/mentions/emoji-mentions.ts similarity index 77% rename from src/mentions/emoji-mentions.js rename to src/mentions/emoji-mentions.ts index 29d126d..7907eb6 100644 --- a/src/mentions/emoji-mentions.js +++ b/src/mentions/emoji-mentions.ts @@ -1,7 +1,7 @@ import emojis from './emojis.json'; -export function emojiMentions(query) { - function isNameOrKeywords( query, name, keywords ) { +export function emojiMentions(query:string) { + function isNameOrKeywords( query:string, name:string, keywords:string[] ) { if ( name.includes(query) ) { return true; } @@ -15,7 +15,7 @@ export function emojiMentions(query) { return false; } - return new Promise((resolve, _reject) => { + return new Promise((resolve) => { const emojiStore = emojis; const matches = emojiStore .filter((emoji) => isNameOrKeywords(query, emoji.id, emoji.keywords)) diff --git a/src/mentions/mentions-caster.js b/src/mentions/mentions-caster.ts similarity index 100% rename from src/mentions/mentions-caster.js rename to src/mentions/mentions-caster.ts diff --git a/src/mentions/mentions-item-renderer.js b/src/mentions/mentions-item-renderer.ts similarity index 60% rename from src/mentions/mentions-item-renderer.js rename to src/mentions/mentions-item-renderer.ts index 3edfea9..037d76a 100644 --- a/src/mentions/mentions-item-renderer.js +++ b/src/mentions/mentions-item-renderer.ts @@ -1,4 +1,11 @@ -export function customItemRenderer( item ) { +interface MentionItem { + type?:string; + link?:string; + name?:string; + text?:string; +} + +export function customItemRenderer( item:MentionItem ) { const itemElement = document.createElement( 'span' ); if (item.type === 'user' || item.type === 'work_package') { @@ -7,16 +14,16 @@ export function customItemRenderer( item ) { } itemElement.classList.add( 'mention-list-item' ); - itemElement.textContent = item.name; + itemElement.textContent = item.name || ''; return itemElement; } -export function emojiItemRenderer( item ) { +export function emojiItemRenderer( item:MentionItem ) { const itemElement = document.createElement( 'span' ); itemElement.classList.add('mention-list-item' ); - itemElement.textContent = `${item.text} ${item.name}`; + itemElement.textContent = `${item.text || ''} ${item.name || ''}`; return itemElement; } diff --git a/src/mentions/user-mentions.js b/src/mentions/user-mentions.ts similarity index 71% rename from src/mentions/user-mentions.js rename to src/mentions/user-mentions.ts index cc4f329..653db40 100644 --- a/src/mentions/user-mentions.js +++ b/src/mentions/user-mentions.ts @@ -4,8 +4,15 @@ import { getPluginContext, } from "../plugins/op-context/op-context"; import { get } from '@rails/request.js'; +import type {Editor} from "@ckeditor/ckeditor5-core"; -export function userMentions(queryText) { +interface MentionablePrincipal { + _type:string; + id:string|number; + name:string; +} + +export function userMentions(this:Editor, queryText:string) { const editor = this; let resource = getOPResource(editor); @@ -21,7 +28,8 @@ export function userMentions(queryText) { return []; } - if (editor.config.get('disabledMentions').includes('user')) { + const disabledMentions = editor.config.get('disabledMentions') as string[] | undefined; + if (disabledMentions?.includes('user')) { return []; } @@ -33,7 +41,10 @@ export function userMentions(queryText) { get(url, { responseKind: 'json', query: { select: 'elements/_type,elements/id,elements/name' } }) .then(response => response.json) .then(collection => { - resolve(_.uniqBy(collection._embedded.elements, (el) => el.id).map(mention => { + const mentions = _.uniqBy( + (collection._embedded.elements || []) as MentionablePrincipal[], + (el:MentionablePrincipal) => el.id + ).map((mention:MentionablePrincipal) => { const type = mention._type.toLowerCase(); const text = `@${mention.name}`; const id = `@${mention.id}`; @@ -42,7 +53,9 @@ export function userMentions(queryText) { const link = `${base}/${typeSegment}/${idNumber}`; return {type, id, text, link, idNumber, name: mention.name}; - })); + }); + + resolve(mentions); }) .catch(error => { console.error('Error fetching user mentions:', error); diff --git a/src/mentions/work-package-mentions.js b/src/mentions/work-package-mentions.ts similarity index 65% rename from src/mentions/work-package-mentions.js rename to src/mentions/work-package-mentions.ts index a26e7a0..81040ba 100644 --- a/src/mentions/work-package-mentions.js +++ b/src/mentions/work-package-mentions.ts @@ -1,12 +1,14 @@ import { get } from '@rails/request.js'; +import type {Editor} from "@ckeditor/ckeditor5-core"; -export function workPackageMentions(prefix) { - return function (query) { - let editor = this; +export function workPackageMentions(prefix:string) { + return function (this:Editor, query:string) { + const editor = this; const url = window.OpenProject.urlRoot + `/work_packages/auto_complete.json`; - let base = window.OpenProject.urlRoot + `/work_packages/`; + const base = window.OpenProject.urlRoot + `/work_packages/`; - if (editor.config.get("disabledMentions").includes("work_package")) { + const disabledMentions = editor.config.get("disabledMentions") as string[] | undefined; + if (disabledMentions?.includes("work_package")) { return []; } diff --git a/src/op-ckeditor-config.js b/src/op-ckeditor-config.ts similarity index 100% rename from src/op-ckeditor-config.js rename to src/op-ckeditor-config.ts diff --git a/src/op-ckeditor.js b/src/op-ckeditor.js deleted file mode 100644 index 909fef6..0000000 --- a/src/op-ckeditor.js +++ /dev/null @@ -1,75 +0,0 @@ -import { DecoupledEditor } from '@ckeditor/ckeditor5-editor-decoupled'; -import { EditorWatchdog } from '@ckeditor/ckeditor5-watchdog'; -import {builtinPlugins} from './op-plugins'; -import {defaultConfig} from "./op-ckeditor-config"; -import {configurationCustomizer} from './op-config-customizer'; - -export class ConstrainedEditor extends DecoupledEditor {} -export class FullEditor extends DecoupledEditor {} - -// Export the two common interfaces -window.OPConstrainedEditor = ConstrainedEditor; -window.OPClassicEditor = FullEditor; - -// Export the Watchdog feature -window.OPEditorWatchdog = EditorWatchdog; - -FullEditor.createCustomized = configurationCustomizer(FullEditor); -FullEditor.builtinPlugins = builtinPlugins; -FullEditor.defaultConfig = Object.assign({}, defaultConfig); -FullEditor.defaultConfig.toolbar = { - items: [ - 'heading', - '|', - 'bold', - 'italic', - 'strikethrough', - 'code', - 'insertCodeBlock', - 'link', - 'bulletedList', - 'numberedList', - 'todoList', - 'imageUpload', - 'blockQuote', - '|', - 'insertTable', - 'macroList', - '|', - 'opContentRevisions', - 'undo', - 'redo', - 'openProjectShowFormattingHelp', - '|', - 'pageBreak', - '|', - 'preview', - 'opShowSource', - ] -}; - -ConstrainedEditor.createCustomized = configurationCustomizer(ConstrainedEditor); -ConstrainedEditor.builtinPlugins = builtinPlugins; -ConstrainedEditor.defaultConfig = Object.assign({}, defaultConfig); -ConstrainedEditor.defaultConfig.toolbar = { - items: [ - 'bold', - 'italic', - 'strikethrough', - 'code', - 'insertCodeBlock', - 'link', - 'bulletedList', - 'numberedList', - 'todoList', - 'imageUpload', - 'blockQuote', - '|', - 'opContentRevisions', - 'undo', - 'redo', - 'openProjectShowFormattingHelp', - 'preview', - 'opShowSource' - ] -}; diff --git a/src/op-ckeditor.ts b/src/op-ckeditor.ts new file mode 100644 index 0000000..7c12e09 --- /dev/null +++ b/src/op-ckeditor.ts @@ -0,0 +1,107 @@ +import { DecoupledEditor } from '@ckeditor/ckeditor5-editor-decoupled'; +import { EditorWatchdog } from '@ckeditor/ckeditor5-watchdog'; +import {builtinPlugins} from './op-plugins'; +import {defaultConfig} from "./op-ckeditor-config"; +import {configurationCustomizer} from './op-config-customizer'; +import type { Editor, EditorConfig } from '@ckeditor/ckeditor5-core'; +import type { ICKEditorWatchdog } from './ckeditor-types'; +import type { OpenProjectEditorConfig, OpenProjectEditorClass } from './op-config-customizer'; +import type { OpenProjectPluginConstructor } from './op-plugins'; +export type { + CKEditorEvent, + CKEditorListenOptions, + CKEditorDomEventData, + ICKEditorInstance, + ICKEditorStatic, + ICKEditorState, + ICKEditorError, + ICKEditorWatchdog, + ICKEditorMentionType, + ICKEditorContext +} from './ckeditor-types'; + +export class ConstrainedEditor extends DecoupledEditor {} +export class FullEditor extends DecoupledEditor {} + +export const OPEditorWatchdog:ICKEditorWatchdog = EditorWatchdog; + +type OpenProjectEditorDefaultConfig = EditorConfig & { + toolbar?:{ + items?:string[]; + }; +}; + +type OpenProjectEditorStatics = typeof DecoupledEditor & OpenProjectEditorClass & { + createCustomized:(wrapper:string|HTMLElement, configuration:OpenProjectEditorConfig) => Promise; + builtinPlugins:OpenProjectPluginConstructor[]; + defaultConfig:OpenProjectEditorDefaultConfig; +}; + +// Export the two common interfaces +window.OPConstrainedEditor = ConstrainedEditor; +window.OPClassicEditor = FullEditor; + +// Export the Watchdog feature +window.OPEditorWatchdog = OPEditorWatchdog; + +const fullEditorClass = FullEditor as OpenProjectEditorStatics; +fullEditorClass.createCustomized = configurationCustomizer(fullEditorClass); +fullEditorClass.builtinPlugins = builtinPlugins; +fullEditorClass.defaultConfig = Object.assign({}, defaultConfig) as OpenProjectEditorDefaultConfig; +fullEditorClass.defaultConfig.toolbar = { + items: [ + 'heading', + '|', + 'bold', + 'italic', + 'strikethrough', + 'code', + 'insertCodeBlock', + 'link', + 'bulletedList', + 'numberedList', + 'todoList', + 'imageUpload', + 'blockQuote', + '|', + 'insertTable', + 'macroList', + '|', + 'opContentRevisions', + 'undo', + 'redo', + 'openProjectShowFormattingHelp', + '|', + 'pageBreak', + '|', + 'preview', + 'opShowSource', + ] +}; + +const constrainedEditorClass = ConstrainedEditor as OpenProjectEditorStatics; +constrainedEditorClass.createCustomized = configurationCustomizer(constrainedEditorClass); +constrainedEditorClass.builtinPlugins = builtinPlugins; +constrainedEditorClass.defaultConfig = Object.assign({}, defaultConfig) as OpenProjectEditorDefaultConfig; +constrainedEditorClass.defaultConfig.toolbar = { + items: [ + 'bold', + 'italic', + 'strikethrough', + 'code', + 'insertCodeBlock', + 'link', + 'bulletedList', + 'numberedList', + 'todoList', + 'imageUpload', + 'blockQuote', + '|', + 'opContentRevisions', + 'undo', + 'redo', + 'openProjectShowFormattingHelp', + 'preview', + 'opShowSource' + ] +}; diff --git a/src/op-config-customizer.js b/src/op-config-customizer.js deleted file mode 100644 index 49f0e48..0000000 --- a/src/op-config-customizer.js +++ /dev/null @@ -1,41 +0,0 @@ -import {opImageUploadPlugins, opMacroPlugins} from './op-plugins'; - -export function configurationCustomizer(editorClass) { - return (wrapper, configuration) => { - const context = configuration.openProject.context; - - // We're going to remove some plugins from the default configuration - // when we detect they are unsupported in the current context - configuration.removePlugins = configuration.removePlugins || []; - - // Disable uploading if there is no resource with uploadAttachment - const resource = context.resource; - if (!(resource && resource.canAddAttachments)) { - configuration.removePlugins.push(...opImageUploadPlugins.map(el => el.pluginName)) - } - - // Disable macros entirely - if (context.macros === false) { - configuration.openProject.disableAllMacros = true; - configuration.removePlugins.push(...opMacroPlugins.map(el => el.pluginName)) - } - - // Enable selective macros - if (Array.isArray(context.macros)) { - const disabledMacros = opMacroPlugins.filter(plugin => context.macros.indexOf(plugin.pluginName) === -1); - configuration.removePlugins.push(...disabledMacros); - } - - // Disable specific mentions - configuration.disabledMentions = []; - const disabledMentions = context.disabledMentions; - if (Array.isArray(disabledMentions)) { - configuration.disabledMentions = disabledMentions; - } - - // Return the original promise for instance creation - return editorClass.create(wrapper, configuration).then(editor => { - return editor; - }); - }; -} diff --git a/src/op-config-customizer.ts b/src/op-config-customizer.ts new file mode 100644 index 0000000..0ebb8b2 --- /dev/null +++ b/src/op-config-customizer.ts @@ -0,0 +1,81 @@ +import type { Editor, EditorConfig, PluginConstructor } from '@ckeditor/ckeditor5-core'; +import {opImageUploadPlugins, opMacroPlugins} from './op-plugins'; +import type { ICKEditorMentionType } from './ckeditor-types'; + +interface OpenProjectContext { + resource?:{ + canAddAttachments?:boolean; + }; + macros?:false|string[]; + disabledMentions?:ICKEditorMentionType[]; +} + +type OpenProjectConfigValue = + | string + | number + | boolean + | null + | undefined + | string[] + | { + context:OpenProjectContext; + disableAllMacros?:boolean; + } + | OpenProjectContext + | Array> + | ICKEditorMentionType[]; + +export interface OpenProjectEditorConfig { + openProject:{ + context:OpenProjectContext; + disableAllMacros?:boolean; + }; + removePlugins?:Array>; + disabledMentions?:ICKEditorMentionType[]; + [key:string]:OpenProjectConfigValue; +} + +export interface OpenProjectEditorClass { + create(wrapper:string|HTMLElement, configuration?:EditorConfig):Promise; +} + +export function configurationCustomizer(editorClass:OpenProjectEditorClass) { + return (wrapper:string|HTMLElement, configuration:OpenProjectEditorConfig) => { + const context = configuration.openProject.context; + + // We're going to remove some plugins from the default configuration + // when we detect they are unsupported in the current context + configuration.removePlugins = configuration.removePlugins || []; + + // Disable uploading if there is no resource with uploadAttachment + const resource = context.resource; + if (!(resource && resource.canAddAttachments)) { + configuration.removePlugins.push(...opImageUploadPlugins.map(el => el.pluginName)) + } + + // Disable macros entirely + if (context.macros === false) { + configuration.openProject.disableAllMacros = true; + configuration.removePlugins.push(...opMacroPlugins.map(el => el.pluginName)) + } + + // Enable selective macros + const macros = context.macros; + if (Array.isArray(macros)) { + const disabledMacros = opMacroPlugins.filter(plugin => macros.indexOf(plugin.pluginName) === -1); + configuration.removePlugins.push(...disabledMacros); + } + + // Disable specific mentions + configuration.disabledMentions = []; + const disabledMentions = context.disabledMentions; + if (Array.isArray(disabledMentions)) { + configuration.disabledMentions = disabledMentions; + } + + // Return the original promise for instance creation + return editorClass.create(wrapper, configuration as EditorConfig).then((editor:Editor) => { + return editor; + }); + }; +} diff --git a/src/op-plugins.js b/src/op-plugins.ts similarity index 85% rename from src/op-plugins.js rename to src/op-plugins.ts index 178b442..261abc1 100644 --- a/src/op-plugins.js +++ b/src/op-plugins.ts @@ -42,22 +42,29 @@ import { ImageInline } from '@ckeditor/ckeditor5-image'; import { PageBreak } from '@ckeditor/ckeditor5-page-break'; import { Autosave } from '@ckeditor/ckeditor5-autosave'; import OpContentRevisions from "./plugins/op-content-revisions/op-content-revisions"; +import type { Editor, PluginConstructor } from '@ckeditor/ckeditor5-core'; + +export type OpenProjectPluginConstructor = PluginConstructor; +export type OpenProjectNamedPluginConstructor = OpenProjectPluginConstructor & { + pluginName:string; + buttonName?:string; +}; // We divide our plugins into separate concerns here // in order to enable / disable each group by configuration -export const opMacroPlugins = [ +export const opMacroPlugins:OpenProjectNamedPluginConstructor[] = [ OPMacroTocPlugin, OPMacroEmbeddedTable, OPMacroWpButtonPlugin, OPChildPagesPlugin, ]; -export const opImageUploadPlugins = [ +export const opImageUploadPlugins:OpenProjectNamedPluginConstructor[] = [ OpUploadPlugin, OPAttachmentListenerPlugin ]; -export const builtinPlugins = [ +const coreBuiltinPlugins:OpenProjectPluginConstructor[] = [ Essentials, CKFinderUploadAdapter, Autoformat, @@ -100,12 +107,11 @@ export const builtinPlugins = [ TableCellProperties, OPMacroListPlugin, - OpCustomCssClassesPlugin, -].concat( - // OpenProject Macro plugin group - opMacroPlugins, +]; - // OpenProject image upload plugins - opImageUploadPlugins, -); +export const builtinPlugins:OpenProjectPluginConstructor[] = [ + ...coreBuiltinPlugins, + ...opMacroPlugins, + ...opImageUploadPlugins, +]; diff --git a/src/plugins/code-block/click-observer.js b/src/plugins/code-block/click-observer.ts similarity index 61% rename from src/plugins/code-block/click-observer.js rename to src/plugins/code-block/click-observer.ts index 504b316..083e3dd 100644 --- a/src/plugins/code-block/click-observer.js +++ b/src/plugins/code-block/click-observer.ts @@ -1,13 +1,9 @@ import { DomEventObserver } from '@ckeditor/ckeditor5-engine'; -export default class DoubleClickObserver extends DomEventObserver { - constructor( view ) { - super( view ); +export default class DoubleClickObserver extends DomEventObserver<'dblclick'> { + public readonly domEventType = 'dblclick'; - this.domEventType = 'dblclick'; - } - - onDomEvent( domEvent ) { + onDomEvent( domEvent:MouseEvent ) { this.fire( domEvent.type, domEvent ); } } diff --git a/src/plugins/code-block/code-block-editing.js b/src/plugins/code-block/code-block-editing.ts similarity index 100% rename from src/plugins/code-block/code-block-editing.js rename to src/plugins/code-block/code-block-editing.ts diff --git a/src/plugins/code-block/code-block-toolbar.js b/src/plugins/code-block/code-block-toolbar.ts similarity index 100% rename from src/plugins/code-block/code-block-toolbar.js rename to src/plugins/code-block/code-block-toolbar.ts diff --git a/src/plugins/code-block/code-block.js b/src/plugins/code-block/code-block.ts similarity index 99% rename from src/plugins/code-block/code-block.js rename to src/plugins/code-block/code-block.ts index 000f89e..a6a6f8c 100644 --- a/src/plugins/code-block/code-block.js +++ b/src/plugins/code-block/code-block.ts @@ -1,4 +1,3 @@ - import { Plugin } from '@ckeditor/ckeditor5-core'; import CodeBlockEditing from './code-block-editing'; import CodeBlockToolbar from './code-block-toolbar'; diff --git a/src/plugins/code-block/converters.js b/src/plugins/code-block/converters.ts similarity index 87% rename from src/plugins/code-block/converters.js rename to src/plugins/code-block/converters.ts index 064e8f7..01258bf 100644 --- a/src/plugins/code-block/converters.js +++ b/src/plugins/code-block/converters.ts @@ -1,7 +1,9 @@ import { Range } from '@ckeditor/ckeditor5-engine'; +import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element'; +import type ViewNode from '@ckeditor/ckeditor5-engine/src/view/node'; +import type ViewText from '@ckeditor/ckeditor5-engine/src/view/text'; import {renderCodeBlockContent} from './widget'; - export function modelCodeBlockToView() { return dispatcher => { dispatcher.on( 'insert:codeblock', converter, { priority: 'high' } ); @@ -52,7 +54,10 @@ export function viewCodeBlockToModel() { } // Find an code element inside the pre element. - const codeBlock = Array.from( data.viewItem.getChildren() ).find( viewChild => viewChild.is('element', 'code')); + const viewItem = data.viewItem as ViewElement; + const codeBlock = Array.from(viewItem.getChildren()).find((viewChild:ViewNode) => { + return viewChild.is('element', 'code'); + }) as ViewElement | undefined; // Do not convert if code block is absent if ( !codeBlock || !conversionApi.consumable.consume( codeBlock, { name: true } ) ) { @@ -74,11 +79,12 @@ export function viewCodeBlockToModel() { // Convert text child of codeblock const child = codeBlock.getChild(0); - if (child) { - conversionApi.consumable.consume(child, { name: true }); + if (child && child.is('$text')) { + const textChild = child as ViewText; + conversionApi.consumable.consume(textChild, { name: true }); // Replace last newline since that text is incorrectly mapped // Regression OP#28609 - const content = child.data.replace(/\n$/, ""); + const content = textChild.data.replace(/\n$/, ""); conversionApi.writer.setAttribute( 'opCodeblockContent', content, modelCodeBlock ); } @@ -108,7 +114,7 @@ export function codeBlockContentToView() { conversionApi.consumable.consume( data.item, evt.name ); // Get mapped view element to update. - const viewElement = conversionApi.mapper.toViewElement( modelElement ); + const viewElement = conversionApi.mapper.toViewElement( modelElement ) as ViewElement; // Remove current
    element contents. conversionApi.writer.remove( conversionApi.writer.createRangeOn( viewElement.getChild( 1 ) ) ); diff --git a/src/plugins/code-block/widget.js b/src/plugins/code-block/widget.ts similarity index 100% rename from src/plugins/code-block/widget.js rename to src/plugins/code-block/widget.ts diff --git a/src/plugins/op-attachment-listener-plugin.js b/src/plugins/op-attachment-listener-plugin.js deleted file mode 100644 index 3657343..0000000 --- a/src/plugins/op-attachment-listener-plugin.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Plugin } from '@ckeditor/ckeditor5-core'; -import Selection from '@ckeditor/ckeditor5-engine/src/model/selection'; - -export default class OPAttachmentListenerPlugin extends Plugin { - static get pluginName() { - return 'OPAttachmentListener'; - } - - init() { - let editor = this.editor; - - editor.model.on('op:attachment-removed', (_, urls) => { - this.removeDeletedImage(urls) - }); - } - - removeDeletedImage(urls) { - let root = this.editor.model.document.getRoot(); - - for (const child of Array.from(root.getChildren())) { - if (child.name === 'image' && urls.indexOf(child.getAttribute('src')) > -1) { - const selection = new Selection( child, 'on' ); - - this.editor.model.deleteContent(selection); - } - } - - } -} diff --git a/src/plugins/op-attachment-listener-plugin.ts b/src/plugins/op-attachment-listener-plugin.ts new file mode 100644 index 0000000..6c05f77 --- /dev/null +++ b/src/plugins/op-attachment-listener-plugin.ts @@ -0,0 +1,46 @@ +import { Plugin } from '@ckeditor/ckeditor5-core'; +import type {Editor} from '@ckeditor/ckeditor5-core'; +import Selection from '@ckeditor/ckeditor5-engine/src/model/selection'; + +interface ImageModelElement { + name:string; + getAttribute(key:string):string | null; +} + +export default class OPAttachmentListenerPlugin extends Plugin { + static get pluginName() { + return 'OPAttachmentListener'; + } + + init() { + const editor = this.editor as Editor; + + editor.model.on('op:attachment-removed', (_, urls) => { + this.removeDeletedImage(urls) + }); + } + + removeDeletedImage(urls:string[]) { + const editor = this.editor as Editor; + const root = editor.model.document.getRoot(); + if (!root) { + return; + } + + for (const child of Array.from(root.getChildren())) { + const modelChild = child as Partial; + if (typeof modelChild.name !== 'string' || typeof modelChild.getAttribute !== 'function') { + continue; + } + + const sourceUrl = modelChild.getAttribute('src'); + + if (modelChild.name === 'image' && typeof sourceUrl === 'string' && urls.includes(sourceUrl)) { + const selection = new Selection(child, 'on'); + + editor.model.deleteContent(selection); + } + } + + } +} diff --git a/src/plugins/op-content-revisions/command.js b/src/plugins/op-content-revisions/command.ts similarity index 75% rename from src/plugins/op-content-revisions/command.js rename to src/plugins/op-content-revisions/command.ts index 32e059b..d78eed3 100644 --- a/src/plugins/op-content-revisions/command.js +++ b/src/plugins/op-content-revisions/command.ts @@ -1,12 +1,13 @@ import {Command} from "ckeditor5/src/core"; +import type {Editor} from "@ckeditor/ckeditor5-core"; import {loadFromLocalStorage} from "./storage"; import {OP_CONTENT_REVISION_KEY} from "./op-content-revisions"; export default class OpContentRevisionsCommand extends Command { - async execute (timestamp) { - const editor = this.editor; - const key = editor.config.get(OP_CONTENT_REVISION_KEY); + async execute (timestamp:number) { + const editor = this.editor as Editor; + const key = editor.config.get(OP_CONTENT_REVISION_KEY) as string; const record = await loadFromLocalStorage(key); if (!record) { diff --git a/src/plugins/op-content-revisions/op-content-revisions.js b/src/plugins/op-content-revisions/op-content-revisions.ts similarity index 78% rename from src/plugins/op-content-revisions/op-content-revisions.js rename to src/plugins/op-content-revisions/op-content-revisions.ts index f825b10..b55f235 100644 --- a/src/plugins/op-content-revisions/op-content-revisions.js +++ b/src/plugins/op-content-revisions/op-content-revisions.ts @@ -1,4 +1,5 @@ import {Plugin} from "ckeditor5/src/core"; +import type {Editor} from "@ckeditor/ckeditor5-core"; import OpContentRevisionsUI from "./ui"; import {loadFromLocalStorage} from "./storage"; import OpContentRevisionsCommand from "./command"; @@ -9,6 +10,12 @@ export const OP_CONTENT_REVISION_KEY = "opContentRevisionKey"; export const OP_CONTENT_REVISION_PREFIX = "op_ckeditor_rev"; export const STORAGE_KEY_OVERRIDE = "storageKey"; +interface AutosavePluginWithDomEmitter { + _domEmitter?: { + stopListening(target:Window, event:string):void; + }; +} + export default class OpContentRevisions extends Plugin { static get requires() { @@ -19,7 +26,7 @@ export default class OpContentRevisions extends Plugin { return "OpContentRevisions"; } - constructor(editor) { + constructor(editor:Editor) { super(editor); // Define a storage key for this instance @@ -40,7 +47,9 @@ export default class OpContentRevisions extends Plugin { const now = Date.now(); // disable beforeunload hook, we have our own - editor.plugins.get("Autosave")._domEmitter.stopListening(window, "beforeunload"); + const autosavePlugin = editor.plugins.get("Autosave") as Autosave; + const autosaveDomEmitter = Reflect.get(autosavePlugin, "_domEmitter") as AutosavePluginWithDomEmitter["_domEmitter"]; + autosaveDomEmitter?.stopListening(window, "beforeunload"); Object .keys(localStorage) @@ -62,8 +71,8 @@ export default class OpContentRevisions extends Plugin { * If a StorageKey is defined in the editor configuration, * use that instead of the default key. */ - getStorageKey(editor) { - const storageKey = editor.config.get(STORAGE_KEY_OVERRIDE); + getStorageKey(editor:Editor):string { + const storageKey = editor.config.get(STORAGE_KEY_OVERRIDE) as string | undefined; if (storageKey) { return storageKey; @@ -76,7 +85,7 @@ export default class OpContentRevisions extends Plugin { * Create a storage key from the given resource, if available. * Fall back to using the current URL path. */ - createLocalStorageKey(editor) { + createLocalStorageKey(editor:Editor):string { const resource = getOPResource(editor); const field = getOPFieldName(editor); diff --git a/src/plugins/op-content-revisions/storage.js b/src/plugins/op-content-revisions/storage.js deleted file mode 100644 index 12c9f83..0000000 --- a/src/plugins/op-content-revisions/storage.js +++ /dev/null @@ -1,56 +0,0 @@ -import * as LZString from "lz-string"; -import {generateHash} from "./utils"; -import {OP_CONTENT_REVISION_KEY} from "./op-content-revisions"; -import {getOPService} from "../op-context/op-context"; - -export function loadFromLocalStorage(storageKey) { - const compressed = localStorage.getItem(storageKey); - - if (!compressed) { - return null; - } - - try { - return JSON.parse(LZString.decompress(compressed)); - } catch (e) { - console.error("Failed to load CKEditor revisions from localStorage: " + e.toString()); - return null; - } -} - -export async function saveInLocalStorage(editor) { - const timestamp = Date.now(); - const key = editor.config.get(OP_CONTENT_REVISION_KEY); - const content = await editor.getData(); - - // Do not try to save if content is undefined - if (!content) { - console.warn("Trying to save snapshot but data is not defined."); - } - - const item = { - timestamp, - hash: generateHash(content), - content, - }; - - const record = loadFromLocalStorage(key); - const items = record?.items || []; - - // Unless there is a entry with a matching hash, append new save - const match = items.find(saved => item.hash === saved.hash); - if (!match) { - items.push(item); - } - - try { - const compressed = LZString.compress(JSON.stringify({ items, updatedAt: timestamp})); - - localStorage.setItem(key, compressed); - } catch (e) { - const notifications = getOPService(editor, "notifications"); - notifications.addError("Failed to save CKEditor data to localStorage: " + e.toString()); - } - - return true; -} diff --git a/src/plugins/op-content-revisions/storage.ts b/src/plugins/op-content-revisions/storage.ts new file mode 100644 index 0000000..7072572 --- /dev/null +++ b/src/plugins/op-content-revisions/storage.ts @@ -0,0 +1,83 @@ +import * as LZString from "lz-string"; +import type {Editor} from "@ckeditor/ckeditor5-core"; +import {generateHash} from "./utils"; +import {OP_CONTENT_REVISION_KEY} from "./op-content-revisions"; +import {getOPService} from "../op-context/op-context"; + +interface RevisionItem { + timestamp:number; + hash:number; + content:string; +} + +interface RevisionRecord { + items:RevisionItem[]; + updatedAt:number; +} + +interface NotificationService { + addError(message:string):void; +} + +function errorToString(error:Error):string { + return error.toString(); +} + +export function loadFromLocalStorage(storageKey:string):RevisionRecord|null { + const compressed = localStorage.getItem(storageKey); + + if (!compressed) { + return null; + } + + try { + const decompressed = LZString.decompress(compressed); + if (!decompressed) { + return null; + } + + return JSON.parse(decompressed) as RevisionRecord; + } catch (e) { + const error = e instanceof Error ? e : new Error(String(e)); + console.error("Failed to load CKEditor revisions from localStorage: " + errorToString(error)); + return null; + } +} + +export async function saveInLocalStorage(editor:Editor):Promise { + const timestamp = Date.now(); + const key = editor.config.get(OP_CONTENT_REVISION_KEY) as string; + const content = editor.getData(); + + // Do not try to save if content is undefined + if (!content) { + console.warn("Trying to save snapshot but data is not defined."); + } + + const item = { + timestamp, + hash: generateHash(content), + content, + }; + + const record = loadFromLocalStorage(key); + const items = record?.items || []; + + // Unless there is a entry with a matching hash, append new save + const match = items.find((saved:RevisionItem) => item.hash === saved.hash); + if (!match) { + items.push(item); + } + + try { + const compressed = LZString.compress(JSON.stringify({ items, updatedAt: timestamp})); + + localStorage.setItem(key, compressed); + } catch (e) { + const error = e instanceof Error ? e : new Error(String(e)); + const notifications = getOPService(editor, "notifications") as NotificationService; + notifications.addError("Failed to save CKEditor data to localStorage: " + errorToString(error)); + } + + return true; +} diff --git a/src/plugins/op-content-revisions/ui.js b/src/plugins/op-content-revisions/ui.ts similarity index 70% rename from src/plugins/op-content-revisions/ui.js rename to src/plugins/op-content-revisions/ui.ts index 8012265..006944f 100644 --- a/src/plugins/op-content-revisions/ui.js +++ b/src/plugins/op-content-revisions/ui.ts @@ -4,6 +4,9 @@ import {Plugin} from "ckeditor5/src/core"; import {addListToDropdown, createDropdown} from "ckeditor5/src/ui"; import {Collection} from "ckeditor5/src/utils"; +import type {Editor} from "@ckeditor/ckeditor5-core"; +import { ViewModel } from "@ckeditor/ckeditor5-ui"; +import type {ListDropdownButtonDefinition, ListDropdownItemDefinition} from "@ckeditor/ckeditor5-ui"; import {loadFromLocalStorage} from "./storage"; import {countWords, generateHash} from "./utils"; @@ -11,6 +14,18 @@ import imageIcon from "./../../icons/revisions.svg"; import {getOPI18n, getOPService} from "../op-context/op-context"; import {OP_CONTENT_REVISION_KEY} from "./op-content-revisions"; +interface ExecuteEventSource { + timestamp?:number; +} + +interface ExecuteEvent { + source:ExecuteEventSource; +} + +interface TimezoneService { + formattedRelativeDateTime(timestamp:number):string; +} + export default class OpContentRevisionsUI extends Plugin { init() { @@ -19,7 +34,7 @@ export default class OpContentRevisionsUI extends Plugin { editor.ui.componentFactory.add("opContentRevisions", locale => { const dropdownView = createDropdown(locale); - const collection = new Collection(); + const collection = new Collection(); // Create a dropdown with a list inside the panel. addListToDropdown(dropdownView, collection, { @@ -40,7 +55,7 @@ export default class OpContentRevisionsUI extends Plugin { addAvailableRevisions(editor, collection); }); - dropdownView.on("execute", (evt) => { + dropdownView.on("execute", (evt:ExecuteEvent) => { const { timestamp } = evt.source; if (timestamp) { @@ -54,19 +69,19 @@ export default class OpContentRevisionsUI extends Plugin { } -function addAvailableRevisions(editor, collection) { - const key = editor.config.get(OP_CONTENT_REVISION_KEY); +function addAvailableRevisions(editor:Editor, collection:Collection) { + const key = editor.config.get(OP_CONTENT_REVISION_KEY) as string; const record = loadFromLocalStorage(key); const i18n = getOPI18n(editor); - const timezoneService = getOPService(editor, "timezone"); + const timezoneService = getOPService(editor, "timezone") as TimezoneService; - if (!record?.items || record.items.count <= 0) { - const def = { + if (!record?.items || record.items.length <= 0) { + const def:ListDropdownButtonDefinition = { type: "button", - model: { + model: new ViewModel({ label: i18n.t('js.editor.no_revisions'), withText: true, - }, + }), }; collection.add(def); @@ -85,13 +100,13 @@ function addAvailableRevisions(editor, collection) { const matches = data.hash === currentHash ? `${i18n.t('js.label_current')} - ` : ""; const label = `${matches}${time} (${words})`; - const def = { + const def:ListDropdownButtonDefinition = { type: "button", - model: { + model: new ViewModel({ timestamp: data.timestamp, label, withText: true, - }, + }), }; collection.add(def); diff --git a/src/plugins/op-content-revisions/utils.js b/src/plugins/op-content-revisions/utils.ts similarity index 78% rename from src/plugins/op-content-revisions/utils.js rename to src/plugins/op-content-revisions/utils.ts index 45e9648..7a6f6c6 100644 --- a/src/plugins/op-content-revisions/utils.js +++ b/src/plugins/op-content-revisions/utils.ts @@ -1,13 +1,13 @@ // Description: Utility functions for the history log plugin. -export function countWords(str) { +export function countWords(str:string) { return str.trim().split(/\s+/).length; } /** * Basic hash function based on DJB "33-times" algorithm. */ -export function generateHash(str) { +export function generateHash(str:string) { const len = str.length; let h = 5381; diff --git a/src/plugins/op-context/op-context.js b/src/plugins/op-context/op-context.ts similarity index 52% rename from src/plugins/op-context/op-context.js rename to src/plugins/op-context/op-context.ts index 042775f..20ce60a 100644 --- a/src/plugins/op-context/op-context.js +++ b/src/plugins/op-context/op-context.ts @@ -1,35 +1,37 @@ -export function getOP(editor) { +import type { Editor } from '@ckeditor/ckeditor5-core'; + +export function getOP(editor:Editor) { return _.get(editor.config, '_config.openProject'); } -export function getOPResource(editor) { +export function getOPResource(editor:Editor) { return _.get(editor.config, '_config.openProject.context.resource'); } -export function getOPFieldName(editor) { +export function getOPFieldName(editor:Editor) { return _.get(editor.config, '_config.openProject.context.field'); } -export function getOPPreviewContext(editor) { +export function getOPPreviewContext(editor:Editor) { return _.get(editor.config, '_config.openProject.context.previewContext'); } -export function getPluginContext(editor) { +export function getPluginContext(editor:Editor) { return _.get(editor.config, '_config.openProject.pluginContext'); } -export function getOPService(editor, name) { +export function getOPService(editor:Editor, name:string) { return getPluginContext(editor).services[name]; } -export function getOPHelper(editor, name) { +export function getOPHelper(editor:Editor, name:string) { return getPluginContext(editor).helpers[name]; } -export function getOPPath(editor) { +export function getOPPath(editor:Editor) { return getOPService(editor,'pathHelperService'); } -export function getOPI18n(editor) { +export function getOPI18n(editor:Editor) { return getOPService(editor,'i18n'); } diff --git a/src/plugins/op-custom-css-classes-plugin.js b/src/plugins/op-custom-css-classes-plugin.ts similarity index 88% rename from src/plugins/op-custom-css-classes-plugin.js rename to src/plugins/op-custom-css-classes-plugin.ts index 49f055d..eb49ff1 100644 --- a/src/plugins/op-custom-css-classes-plugin.js +++ b/src/plugins/op-custom-css-classes-plugin.ts @@ -1,4 +1,10 @@ import { Plugin } from '@ckeditor/ckeditor5-core'; +import type ViewNode from '@ckeditor/ckeditor5-engine/src/view/node'; +import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element'; + +function isViewElement(value:ViewNode | null):value is ViewElement { + return !!value && value.is('element'); +} export default class OpCustomCssClassesPlugin extends Plugin { @@ -86,7 +92,7 @@ export default class OpCustomCssClassesPlugin extends Plugin { this.editor .conversion .for('upcast') - .add(dispatcher => dispatcher.on(`element:table`, this._manageTableUpcast(config)), {priority: 'high'}); + .add(dispatcher => dispatcher.on(`element:table`, this._manageTableUpcast(config))); this.editor .conversion @@ -163,16 +169,22 @@ export default class OpCustomCssClassesPlugin extends Plugin { viewElements = this._manageListItems(viewWriter, modelElement, viewElement, viewElements, config); } else { const figureViewElement = viewElement; - const viewChildren = Array.from(viewWriter.createRangeIn(viewElement).getItems()); + const viewChildren = Array.from(viewWriter.createRangeIn(viewElement).getItems()) as ViewNode[]; if (elementName === 'imageBlock') { - const image = viewChildren.find(item => item.is('element', 'img')); + const image = viewChildren.find((item) => isViewElement(item) && item.is('element', 'img')); + + if (!image) { + return; + } this._wrapInFigureContentContainer(image, figureViewElement, config, viewWriter); viewElements = [...viewElements, image]; } else if (elementName === 'table' || elementName === 'tableRow') { - const childrenToAdd = viewChildren.filter(viewChild => elementsWithCustomClasses.includes(viewChild.name)); + const childrenToAdd = viewChildren.filter((viewChild) => { + return isViewElement(viewChild) && elementsWithCustomClasses.includes(viewChild.name); + }); viewElements = [...viewElements, ...childrenToAdd]; @@ -255,8 +267,10 @@ export default class OpCustomCssClassesPlugin extends Plugin { }); } else if (attributeName === 'headingColumns') { const addHeadingColumns = data.attributeNewValue; - const viewChildren = Array.from(viewWriter.createRangeIn(viewElement).getItems()); - const viewElements = viewChildren.filter(viewChild => Object.keys(config.elementsWithCustomClassesMap).includes(viewChild.name)); + const viewChildren = Array.from(viewWriter.createRangeIn(viewElement).getItems()) as ViewNode[]; + const viewElements = viewChildren.filter((viewChild): viewChild is ViewElement => { + return isViewElement(viewChild) && Object.keys(config.elementsWithCustomClassesMap).includes(viewChild.name); + }); if (addHeadingColumns) { viewElements.forEach(viewElement => { @@ -266,15 +280,15 @@ export default class OpCustomCssClassesPlugin extends Plugin { viewWriter.addClass(elementClasses, viewElement); }); } else { - viewElements - .filter(viewElement => viewElement.hasClass(config.elementsWithCustomClassesMap.th[1])) - .forEach(viewElement => { - const nextSibling = viewElement.nextSibling; - - if (nextSibling && nextSibling.name !== 'th') { - viewWriter.removeClass(config.elementsWithCustomClassesMap.th[1], viewElement); - } - }); + viewElements + .filter((headingCell) => headingCell.hasClass(config.elementsWithCustomClassesMap.th[1])) + .forEach((headingCell) => { + const nextSibling = headingCell.nextSibling; + + if (isViewElement(nextSibling) && nextSibling.name !== 'th') { + viewWriter.removeClass(config.elementsWithCustomClassesMap.th[1], headingCell); + } + }); } } else if (attributeName === 'width') { if (viewElement.hasClass('image_resized')) { @@ -282,8 +296,10 @@ export default class OpCustomCssClassesPlugin extends Plugin { } } else if (attributeName === 'uploadStatus') { if (data.attributeNewValue === 'complete') { - const viewChildren = Array.from(viewWriter.createRangeIn(viewElement).getItems()); - let placeholderElement = viewChildren.find(viewChild => viewChild.hasClass('ck-upload-placeholder-loader')); + const viewChildren = Array.from(viewWriter.createRangeIn(viewElement).getItems()) as ViewNode[]; + const placeholderElement = viewChildren.find((viewChild) => { + return isViewElement(viewChild) && viewChild.hasClass('ck-upload-placeholder-loader'); + }); if (placeholderElement) { viewWriter.remove(viewWriter.createRangeOn(placeholderElement)); diff --git a/src/plugins/op-help-link-plugin/op-help-link-plugin.js b/src/plugins/op-help-link-plugin/op-help-link-plugin.ts similarity index 91% rename from src/plugins/op-help-link-plugin/op-help-link-plugin.js rename to src/plugins/op-help-link-plugin/op-help-link-plugin.ts index 250bc99..cbf243c 100644 --- a/src/plugins/op-help-link-plugin/op-help-link-plugin.js +++ b/src/plugins/op-help-link-plugin/op-help-link-plugin.ts @@ -14,7 +14,7 @@ export default class OPHelpLinkPlugin extends Plugin { init() { const editor = this.editor; - const helpURL = editor.config.get('openProject.helpURL'); + const helpURL = String(editor.config.get('openProject.helpURL') || ''); editor.ui.componentFactory.add( 'openProjectShowFormattingHelp', locale => { const view = new ButtonView( locale ); diff --git a/src/plugins/op-image-attachment-lookup/op-image-attachment-lookup-plugin.js b/src/plugins/op-image-attachment-lookup/op-image-attachment-lookup-plugin.ts similarity index 100% rename from src/plugins/op-image-attachment-lookup/op-image-attachment-lookup-plugin.js rename to src/plugins/op-image-attachment-lookup/op-image-attachment-lookup-plugin.ts diff --git a/src/plugins/op-macro-child-pages/op-macro-child-pages-editing.js b/src/plugins/op-macro-child-pages/op-macro-child-pages-editing.ts similarity index 100% rename from src/plugins/op-macro-child-pages/op-macro-child-pages-editing.js rename to src/plugins/op-macro-child-pages/op-macro-child-pages-editing.ts diff --git a/src/plugins/op-macro-child-pages/op-macro-child-pages-plugin.js b/src/plugins/op-macro-child-pages/op-macro-child-pages-plugin.ts similarity index 100% rename from src/plugins/op-macro-child-pages/op-macro-child-pages-plugin.js rename to src/plugins/op-macro-child-pages/op-macro-child-pages-plugin.ts diff --git a/src/plugins/op-macro-child-pages/op-macro-child-pages-toolbar.js b/src/plugins/op-macro-child-pages/op-macro-child-pages-toolbar.ts similarity index 93% rename from src/plugins/op-macro-child-pages/op-macro-child-pages-toolbar.js rename to src/plugins/op-macro-child-pages/op-macro-child-pages-toolbar.ts index 551553c..dc21fc7 100644 --- a/src/plugins/op-macro-child-pages/op-macro-child-pages-toolbar.js +++ b/src/plugins/op-macro-child-pages/op-macro-child-pages-toolbar.ts @@ -28,7 +28,7 @@ export default class OPChildPagesToolbar extends Plugin { const macroService = pluginContext.services.macros; const pageAttribute = widget.getAttribute('page'); const includeParent = widget.getAttribute('includeParent'); - const page = (pageAttribute && pageAttribute.length > 0) ? pageAttribute : ''; + const page = typeof pageAttribute === 'string' && pageAttribute.length > 0 ? pageAttribute : ''; macroService .configureChildPages(page, includeParent) .then((macroConf) => model.change(writer => { diff --git a/src/plugins/op-macro-child-pages/utils.js b/src/plugins/op-macro-child-pages/utils.ts similarity index 100% rename from src/plugins/op-macro-child-pages/utils.js rename to src/plugins/op-macro-child-pages/utils.ts diff --git a/src/plugins/op-macro-embedded-table/embedded-table-editing.js b/src/plugins/op-macro-embedded-table/embedded-table-editing.ts similarity index 96% rename from src/plugins/op-macro-embedded-table/embedded-table-editing.js rename to src/plugins/op-macro-embedded-table/embedded-table-editing.ts index 4cbc0a7..e94f3fc 100644 --- a/src/plugins/op-macro-embedded-table/embedded-table-editing.js +++ b/src/plugins/op-macro-embedded-table/embedded-table-editing.ts @@ -5,7 +5,14 @@ import { Plugin } from '@ckeditor/ckeditor5-core'; import {toEmbeddedTableWidget} from './utils'; import {getPluginContext} from '../op-context/op-context'; +interface EmbeddedTableI18n { + button:string; + macro_text:string; +} + export default class EmbeddedTableEditing extends Plugin { + text:EmbeddedTableI18n = { button: '', macro_text: '' }; + label = ''; static get pluginName() { return 'EmbeddedTableEditing'; diff --git a/src/plugins/op-macro-embedded-table/embedded-table-plugin.js b/src/plugins/op-macro-embedded-table/embedded-table-plugin.ts similarity index 100% rename from src/plugins/op-macro-embedded-table/embedded-table-plugin.js rename to src/plugins/op-macro-embedded-table/embedded-table-plugin.ts diff --git a/src/plugins/op-macro-embedded-table/embedded-table-toolbar.js b/src/plugins/op-macro-embedded-table/embedded-table-toolbar.ts similarity index 100% rename from src/plugins/op-macro-embedded-table/embedded-table-toolbar.js rename to src/plugins/op-macro-embedded-table/embedded-table-toolbar.ts diff --git a/src/plugins/op-macro-embedded-table/utils.js b/src/plugins/op-macro-embedded-table/utils.ts similarity index 100% rename from src/plugins/op-macro-embedded-table/utils.js rename to src/plugins/op-macro-embedded-table/utils.ts diff --git a/src/plugins/op-macro-list-plugin.js b/src/plugins/op-macro-list-plugin.ts similarity index 77% rename from src/plugins/op-macro-list-plugin.js rename to src/plugins/op-macro-list-plugin.ts index 3b9b2ae..6722bc5 100644 --- a/src/plugins/op-macro-list-plugin.js +++ b/src/plugins/op-macro-list-plugin.ts @@ -3,6 +3,15 @@ import { Plugin } from '@ckeditor/ckeditor5-core'; import { createDropdown, addToolbarToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; import {opMacroPlugins} from "../op-plugins"; +import type { PluginConstructor, Editor } from '@ckeditor/ckeditor5-core'; + +function pluginNameOf(plugin:string|PluginConstructor):string { + if (typeof plugin === 'string') { + return plugin; + } + + return (plugin as { pluginName?:string }).pluginName || ''; +} /** * Adding a drop down list of macros to the toolbar. @@ -12,7 +21,8 @@ import {opMacroPlugins} from "../op-plugins"; export default class OPMacroListPlugin extends Plugin { init() { const editor = this.editor; - const disabledPluginNames = (editor.config.get('removePlugins') || []).map(p => p.pluginName); + const removePlugins = editor.config.get('removePlugins') || []; + const disabledPluginNames = removePlugins.map(pluginNameOf); const dropdownTooltip = window.I18n.t('js.editor.macro.dropdown.chose_macro'); // Skip if we don't have any macros here diff --git a/src/plugins/op-macro-toc-plugin.js b/src/plugins/op-macro-toc-plugin.ts similarity index 100% rename from src/plugins/op-macro-toc-plugin.js rename to src/plugins/op-macro-toc-plugin.ts diff --git a/src/plugins/op-macro-wp-button/op-macro-wp-button-editing.js b/src/plugins/op-macro-wp-button/op-macro-wp-button-editing.ts similarity index 99% rename from src/plugins/op-macro-wp-button/op-macro-wp-button-editing.js rename to src/plugins/op-macro-wp-button/op-macro-wp-button-editing.ts index bc83343..a8122ae 100644 --- a/src/plugins/op-macro-wp-button/op-macro-wp-button-editing.js +++ b/src/plugins/op-macro-wp-button/op-macro-wp-button-editing.ts @@ -101,7 +101,7 @@ export default class OPMacroWpButtonEditing extends Plugin { } ); } - macroLabel(type) { + macroLabel(type?:string) { if (type) { return window.I18n.t('js.editor.macro.work_package_button.with_type', { typename: type }); } else { diff --git a/src/plugins/op-macro-wp-button/op-macro-wp-button-plugin.js b/src/plugins/op-macro-wp-button/op-macro-wp-button-plugin.ts similarity index 100% rename from src/plugins/op-macro-wp-button/op-macro-wp-button-plugin.js rename to src/plugins/op-macro-wp-button/op-macro-wp-button-plugin.ts diff --git a/src/plugins/op-macro-wp-button/op-macro-wp-button-toolbar.js b/src/plugins/op-macro-wp-button/op-macro-wp-button-toolbar.ts similarity index 100% rename from src/plugins/op-macro-wp-button/op-macro-wp-button-toolbar.js rename to src/plugins/op-macro-wp-button/op-macro-wp-button-toolbar.ts diff --git a/src/plugins/op-macro-wp-button/utils.js b/src/plugins/op-macro-wp-button/utils.ts similarity index 100% rename from src/plugins/op-macro-wp-button/utils.js rename to src/plugins/op-macro-wp-button/utils.ts diff --git a/src/plugins/op-preview.plugin.js b/src/plugins/op-preview.plugin.ts similarity index 100% rename from src/plugins/op-preview.plugin.js rename to src/plugins/op-preview.plugin.ts diff --git a/src/plugins/op-source-code.plugin.js b/src/plugins/op-source-code.plugin.ts similarity index 98% rename from src/plugins/op-source-code.plugin.js rename to src/plugins/op-source-code.plugin.ts index 391ee3e..f51d155 100644 --- a/src/plugins/op-source-code.plugin.js +++ b/src/plugins/op-source-code.plugin.ts @@ -34,7 +34,7 @@ export default class OPSourceCodePlugin extends Plugin { } ); - let showSource = function(_preview) { + let showSource = function() { const editableElement = editor.ui.getEditableElement(); const reference = editableElement?.parentElement; if (!reference?.parentElement) { diff --git a/src/plugins/op-upload-plugin.js b/src/plugins/op-upload-plugin.ts similarity index 88% rename from src/plugins/op-upload-plugin.js rename to src/plugins/op-upload-plugin.ts index e9e7f03..b67c2a5 100644 --- a/src/plugins/op-upload-plugin.js +++ b/src/plugins/op-upload-plugin.ts @@ -1,5 +1,6 @@ import { Plugin } from '@ckeditor/ckeditor5-core'; import { FileRepository } from '@ckeditor/ckeditor5-upload'; +import type {FileLoader} from '@ckeditor/ckeditor5-upload'; import OpUploadResourceAdapter from './op-upload-resource-adapter'; import {getOPResource} from './op-context/op-context'; import { ImageUpload } from '@ckeditor/ckeditor5-image'; @@ -15,7 +16,7 @@ export default class OpUploadPlugin extends Plugin { } init() { - this.editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { + this.editor.plugins.get('FileRepository').createUploadAdapter = (loader:FileLoader) => { const resource = getOPResource(this.editor); return new OpUploadResourceAdapter(loader, resource, this.editor); } diff --git a/src/plugins/op-upload-resource-adapter.js b/src/plugins/op-upload-resource-adapter.js deleted file mode 100644 index de60730..0000000 --- a/src/plugins/op-upload-resource-adapter.js +++ /dev/null @@ -1,42 +0,0 @@ -import {getOPService} from './op-context/op-context'; - -export default class OpUploadResourceAdapter { - constructor(loader, resource, editor) { - this.loader = loader; - this.resource = resource; - this.editor = editor; - } - - upload() { - const resource = this.resource; - const resourceService = getOPService(this.editor, 'attachmentsResourceService'); - - if (!resource) { - console.warn(`resource not available in this CKEditor instance`); - return Promise.reject("Not possible to upload attachments without resource"); - } - - return this.loader.file - .then(file => { - return resourceService - .attachFiles(resource, [file]) - .toPromise() - .then((result) => { - this.editor.model.fire('op:attachment-added', result); - - return this.buildResponse(result[0]) - }).catch((error) => { - console.error("Failed upload %O", error); - }); - }) - - } - - buildResponse(result) { - return { default: result._links.staticDownloadLocation.href }; - } - - abort() { - return false; - } -} diff --git a/src/plugins/op-upload-resource-adapter.ts b/src/plugins/op-upload-resource-adapter.ts new file mode 100644 index 0000000..b4f93b7 --- /dev/null +++ b/src/plugins/op-upload-resource-adapter.ts @@ -0,0 +1,75 @@ +import {getOPService} from './op-context/op-context'; +import type {Editor} from "@ckeditor/ckeditor5-core"; +import type {FileLoader} from "@ckeditor/ckeditor5-upload"; +import type {UploadResponse} from "@ckeditor/ckeditor5-upload"; + +type OpenProjectResource = Record; + +interface UploadResourceService { + attachFiles(resource:OpenProjectResource, files:File[]):{ + toPromise():Promise; + }; +} + +interface AttachmentUploadResult { + _links:{ + staticDownloadLocation:{ + href:string; + }; + }; +} + +export default class OpUploadResourceAdapter { + loader:FileLoader; + resource:OpenProjectResource | null; + editor:Editor; + + constructor(loader:FileLoader, resource:OpenProjectResource | null, editor:Editor) { + this.loader = loader; + this.resource = resource; + this.editor = editor; + } + + upload():Promise { + const resource = this.resource; + const resourceService = getOPService(this.editor, 'attachmentsResourceService') as UploadResourceService; + + if (!resource) { + console.warn(`resource not available in this CKEditor instance`); + return Promise.reject(new Error("Not possible to upload attachments without resource")); + } + + return this.loader.file + .then((file) => { + if (!file) { + throw new Error("Not possible to upload empty file payload"); + } + + return resourceService + .attachFiles(resource, [file]) + .toPromise() + .then((result) => { + if (!result[0]) { + throw new Error("Attachment upload succeeded without a response payload"); + } + + this.editor.model.fire('op:attachment-added', result); + + return this.buildResponse(result[0]) + }); + }) + .catch((error:Error) => { + console.error("Failed upload %O", error); + throw error; + }); + + } + + buildResponse(result:AttachmentUploadResult):UploadResponse { + return { default: result._links.staticDownloadLocation.href }; + } + + abort() { + return false; + } +} diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..4d5a192 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,20 @@ +declare module "*.svg" { + const value: string; + export default value; +} + +declare const jQuery: any; +declare const I18n: any; +declare const _: any; + +interface Window { + OPConstrainedEditor: any; + OPClassicEditor: any; + OPEditorWatchdog: any; + OpenProject: any; + I18n: any; +} + +interface HTMLElement { + ckeditorInstance?:import("./op-ckeditor").ICKEditorInstance; +} diff --git a/tests/commonmark/gfm-spike-gaps.test.js b/tests/commonmark/gfm-spike-gaps.test.js new file mode 100644 index 0000000..85f8bf9 --- /dev/null +++ b/tests/commonmark/gfm-spike-gaps.test.js @@ -0,0 +1,36 @@ +import CommonMarkDataProcessor from '../../src/commonmark/commonmarkdataprocessor'; +import OpenProjectGFMDataProcessor from '../../src/commonmark/op-gfm-data-processor'; +import { StylesProcessor, ViewDocument } from '@ckeditor/ckeditor5-engine'; + +function createProcessors() { + const viewDocument = new ViewDocument(new StylesProcessor()); + + return { + legacy: new CommonMarkDataProcessor(viewDocument), + gfm: new OpenProjectGFMDataProcessor(viewDocument), + }; +} + +function roundTripViaView(markdown) { + const { legacy, gfm } = createProcessors(); + const viewFragment = legacy.toView(markdown); + + return { + legacyOut: legacy.toData(viewFragment), + gfmOut: gfm.toData(viewFragment), + }; +} + +describe('OpenProject GFM spike gap closure', () => { + it('matches table markdown-vs-html round-trip via transitional shim', () => { + const markdown = + '| Heading 1 | Heading 2\n' + + '| --- | ---\n' + + '| Cell 1 | Cell 2\n'; + + const { legacyOut, gfmOut } = roundTripViaView(markdown); + + expect(gfmOut).toEqual(legacyOut); + expect(legacyOut).toContain(''); + }); +}); diff --git a/tests/commonmark/gfm-spike-parity.test.js b/tests/commonmark/gfm-spike-parity.test.js new file mode 100644 index 0000000..29d02cd --- /dev/null +++ b/tests/commonmark/gfm-spike-parity.test.js @@ -0,0 +1,45 @@ +import CommonMarkDataProcessor from '../../src/commonmark/commonmarkdataprocessor'; +import OpenProjectGFMDataProcessor from '../../src/commonmark/op-gfm-data-processor'; +import { StylesProcessor, ViewDocument } from '@ckeditor/ckeditor5-engine'; + +function createProcessors() { + const viewDocument = new ViewDocument(new StylesProcessor()); + + return { + legacy: new CommonMarkDataProcessor(viewDocument), + gfm: new OpenProjectGFMDataProcessor(viewDocument), + }; +} + +function roundTripViaView(markdown) { + const { legacy, gfm } = createProcessors(); + const viewFragment = legacy.toView(markdown); + + return { + legacyOut: legacy.toData(viewFragment), + gfmOut: gfm.toData(viewFragment), + }; +} + +describe('OpenProject GFM spike parity', () => { + it('keeps macro markup compatible', () => { + const markdown = ''; + const { legacyOut, gfmOut } = roundTripViaView(markdown); + + expect(gfmOut).toEqual(legacyOut); + }); + + it('keeps mention markup compatible', () => { + const markdown = '@admin'; + const { legacyOut, gfmOut } = roundTripViaView(markdown); + + expect(gfmOut).toEqual(legacyOut); + }); + + it('keeps page-break markdown token compatible', () => { + const markdown = '
    '; + const { legacyOut, gfmOut } = roundTripViaView(markdown); + + expect(gfmOut).toEqual(legacyOut); + }); +}); diff --git a/tests/mentions/emoji-mentions.test.js b/tests/mentions/emoji-mentions.test.js index 5be7793..dd333e1 100644 --- a/tests/mentions/emoji-mentions.test.js +++ b/tests/mentions/emoji-mentions.test.js @@ -1,4 +1,4 @@ -import { emojiMentions } from "../../src/mentions/emoji-mentions.js"; +import { emojiMentions } from "../../src/mentions/emoji-mentions"; describe('emojiMentions', () => { test('it matches a full emoji name', () => { diff --git a/tsconfig.json b/tsconfig.json index 2485344..0c233fb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,31 @@ { - "compilerOptions": { - "lib": [ - "DOM", - "DOM.Iterable" - ], - "module": "es6", - "target": "es2019", - "moduleResolution": "node" - } + "compilerOptions": { + "lib": [ + "DOM", + "DOM.Iterable", + "ES2019" + ], + "module": "ESNext", + "target": "ES2019", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types", + "rootDir": "src", + "strict": false, + "noImplicitAny": false, + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts" + ], + "exclude": [ + "node_modules", + "tests" + ] } diff --git a/webpack.config.js b/webpack.config.js index 1fae267..de481bd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,7 +25,7 @@ module.exports = { devtool: 'source-map', performance: { hints: false }, - entry: path.resolve( __dirname, 'src', 'op-ckeditor.js' ), + entry: path.resolve( __dirname, 'src', 'op-ckeditor.ts' ), mode: mode, @@ -37,6 +37,10 @@ module.exports = { libraryExport: 'default', }, + resolve: { + extensions: [ '.ts', '.js' ] + }, + optimization: { minimizer: [ new TerserPlugin( { @@ -68,6 +72,18 @@ module.exports = { module: { rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true + } + } + ] + }, { test: /\.svg$/, use: [ 'raw-loader' ]