diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 00000000..0e6951f0 --- /dev/null +++ b/.cspell.json @@ -0,0 +1,98 @@ +{ + "version": "0.2", + "language": "en", + "words": [ + "autofocused", + "behaviour", + "behaviours", + "Behaviour", + "cacheable", + "Cantarell", + "chainer", + "Chainer", + "chatbots", + "childs", + "codexteam", + "colspan", + "constructables", + "contenteditable", + "Contenteditable", + "contentless", + "Contentless", + "convertable", + "Convertable", + "convertible", + "Convertible", + "cssnano", + "cssnext", + "Debouncer", + "devserver", + "editorjs", + "Editorjs", + "entrypoints", + "Flippable", + "flippable", + "GRAMMARLY", + "Gfycat", + "hsablonniere", + "hspace", + "intellij", + "keydown", + "keydowns", + "Kilian", + "leftarrow", + "licence", + "mergeable", + "movetostart", + "mouseleave", + "navigatable", + "nofollow", + "opencollective", + "preconfigured", + "radiobutton", + "resetors", + "rowspan", + "Segoe", + "selectall", + "sometool", + "strongs", + "stylelint", + "textareas", + "toolname", + "twitterwidget", + "typeof", + "UPLUCID", + "Unmergeable", + "rgba", + "Roboto", + "Fira", + "Neue", + "grayscale", + "Menlo", + "Consolas", + "viewports", + "sonarjs", + "Unregisters", + "IAPI", + "Jamison", + "Minzipped", + "srcset", + "youtu", + "Dont", + "CONTENTLESS", + "autofocusable", + "Pettit" + ], + "ignorePaths": [ + "dist/**", + "node_modules/**", + "test-results/**", + "cypress/downloads/**" + ], + "flagWords": [], + "ignoreRegExpList": [ + "/[\\u0080-\\uFFFF]+/", + "/\"[A-Za-z0-9]{8,}\"/" + ] +} + diff --git a/.gitignore b/.gitignore index 96cbfa3e..dc4ddcb7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ dist/ coverage/ .nyc_output/ .vscode/launch.json +jscpd-report/ diff --git a/.jscpdrc.json b/.jscpdrc.json new file mode 100644 index 00000000..d10f6dfc --- /dev/null +++ b/.jscpdrc.json @@ -0,0 +1,24 @@ +{ + "threshold": 0, + "reporters": ["console", "html", "json"], + "ignore": [ + "**/node_modules/**", + "**/dist/**", + "**/test-results/**", + "**/cypress/downloads/**", + "**/*.d.ts", + "**/yarn.lock", + "**/package-lock.json", + "**/.git/**" + ], + "format": [ + "typescript", + "javascript", + "css" + ], + "minLines": 5, + "minTokens": 50, + "absolute": true, + "output": "./jscpd-report" +} + diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e1abfd4..eeb50194 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,43 +13,5 @@ "source.fixAll.eslint": "always" }, "eslint.useFlatConfig": true, - "cSpell.words": [ - "autofocused", - "Behaviour", - "cacheable", - "childs", - "codexteam", - "colspan", - "contenteditable", - "contentless", - "Convertable", - "cssnano", - "cssnext", - "Debouncer", - "devserver", - "editorjs", - "entrypoints", - "Flippable", - "GRAMMARLY", - "hsablonniere", - "intellij", - "keydown", - "keydowns", - "Kilian", - "mergeable", - "movetostart", - "nofollow", - "opencollective", - "preconfigured", - "resetors", - "rowspan", - "selectall", - "sometool", - "stylelint", - "textareas", - "twitterwidget", - "typeof", - "Unmergeable", - "viewports" - ] + } diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index eb879d80..26e3511b 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7eff455e..1ba80a3d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,7 +12,7 @@ - `Fix` - codex-notifier and codex-tooltip moved from devDependencies to dependencies in package.json to solve type errors - `Fix` - Handle whitespace input in empty placeholder elements to prevent caret from moving unexpectedly to the end of the placeholder - `Fix` - Fix the memory leak issue in `Shortcuts` class -- `Fix` - Fix when / overides selected text outside of the editor +- `Fix` - Fix when / overrides selected text outside of the editor - `DX` - Tools submodules removed from the repository - `Improvement` - Shift + Down/Up will allow to select next/previous line instead of Inline Toolbar flipping - `Improvement` - The API `caret.setToBlock()` offset now works across the entire block content, not just the first or last node. @@ -63,7 +63,7 @@ - `New` – Inline Toolbar has new look 💅 - `New` – Inline Tool's `render()` now supports [Menu Config](https://editorjs.io/menu-config/) format - `New` – *ToolsAPI* – All installed block tools now accessible via ToolsAPI `getBlockTools()` method -- `New` – *SelectionAPI* – Exposed methods `save()` and `restore()` that allow to save selection to be able to temporally move focus away, methods `setFakeBackground()` and `removeFakeBackground()` that allow to immitate selection while focus moved away +- `New` – *SelectionAPI* – Exposed methods `save()` and `restore()` that allow to save selection to be able to temporally move focus away, methods `setFakeBackground()` and `removeFakeBackground()` that allow to imitate selection while focus moved away - `New` – *BlocksAPI* – Exposed `getBlockByElement()` method that helps find block by any child html element - `New` – "Convert to" control is now also available in Block Tunes - `New` — Editor.js now supports contenteditable placeholders out of the box. Just add `data-placeholder` or `data-placeholder-active` attribute to make it work. The first one will work like native placeholder while the second one will show placeholder only when block is current. @@ -85,7 +85,7 @@ - `Fix` – Unwanted scroll on first typing on iOS devices - `Fix` - Unwanted soft line break on Enter press after period and space (". |") on iOS devices - `Fix` - Caret lost after block conversion on mobile devices. -- `Fix` - Caret lost after Backspace at the start of block when previoius block is not convertable +- `Fix` - Caret lost after Backspace at the start of block when previous block is not convertable – `Fix` — Deleting whitespaces at the start/end of the block - `Fix` — The problem caused by missed "import type" in block mutation event types resolved diff --git a/docs/api.md b/docs/api.md index 4c7f708d..bfde79da 100644 --- a/docs/api.md +++ b/docs/api.md @@ -197,7 +197,7 @@ It makes following steps: 3. Delete all properties from instance object and set it\`s prototype to `null` -After executing the `destroy` method, editor inctance becomes an empty object. This way you will free occupied JS Heap on your page. +After executing the `destroy` method, editor instance becomes an empty object. This way you will free occupied JS Heap on your page. ### Tooltip API diff --git a/docs/block-tunes.md b/docs/block-tunes.md index 6544bb95..adbb7339 100644 --- a/docs/block-tunes.md +++ b/docs/block-tunes.md @@ -21,7 +21,7 @@ At the constructor of Tune's class exemplar you will receive an object with foll | Parameter | Description | | --------- | ----------- | -| api | Editor's [API](api.md) obejct | +| api | Editor's [API](api.md) object | | config | Configuration of Block Tool Tune is connected to (might be useful in some cases) | | block | [Block API](api.md#block-api) methods for block Tune is connected to | | data | Saved Tune data | diff --git a/docs/tools-inline.md b/docs/tools-inline.md index 604a5e4c..6ed6cae1 100644 --- a/docs/tools-inline.md +++ b/docs/tools-inline.md @@ -1,11 +1,11 @@ # Tools for the Inline Toolbar -Similar with [Tools](tools.md) represented Blocks, you can create Tools for the Inline Toolbar. It will work with +Similar with [Tools](tools.md) represented Blocks, you can create Tools for the Inline Toolbar. It will work with selected fragment of text. The simplest example is `bold` or `italic` Tools. ## Base structure -First of all, Tool's class should have a `isInline` property (static getter) set as `true`. +First of all, Tool's class should have a `isInline` property (static getter) set as `true`. After that Inline Tool should implement next methods. @@ -33,7 +33,7 @@ Method does not accept any parameters #### Return value -type | description | +type | description | -- | -- | `HTMLElement` | element that will be added to the Inline Toolbar | @@ -45,7 +45,7 @@ Method that accepts selected range and wrap it somehow #### Parameters -name | type | description | +name | type | description | -- |-- | -- | range | Range | first range of current Selection | @@ -61,13 +61,13 @@ Get Selection and detect if Tool was applied. For example, after that Tool can h #### Parameters -name | type | description | +name | type | description | -- |-- | -- | selection | Selection | current Selection | #### Return value -type | description | +type | description | -- | -- | `Boolean` | `true` if Tool is active, otherwise `false` | @@ -75,8 +75,8 @@ type | description | ### renderActions() -Optional method that returns additional Element with actions. -For example, input for the 'link' tool or textarea for the 'comment' tool. +Optional method that returns additional Element with actions. +For example, input for the 'link' tool or textarea for the 'comment' tool. It will be places below the buttons list at Inline Toolbar. #### Parameters @@ -85,7 +85,7 @@ Method does not accept any parameters #### Return value -type | description | +type | description | -- | -- | `HTMLElement` | element that will be added to the Inline Toolbar | @@ -93,7 +93,7 @@ type | description | ### clear() -Optional method that will be called on opening/closing of Inline Toolbar. +Optional method that will be called on opening/closing of Inline Toolbar. Can contain logic for clearing Tool's stuff, such as inputs, states and other. #### Parameters @@ -102,17 +102,17 @@ Method does not accept any parameters #### Return value -Method should not return a value. +Method should not return a value. ### static get sanitize() -We recommend to specify the Sanitizer config that corresponds with inline tags that is used by your Tool. -In that case, your config will be merged with sanitizer configuration of Block Tool +We recommend to specify the Sanitizer config that corresponds with inline tags that is used by your Tool. +In that case, your config will be merged with sanitizer configuration of Block Tool that is using the Inline Toolbar with your Tool. Example: -If your Tool wrapps selected text with `` tag, the sanitizer config should looks like this: +If your Tool wraps selected text with `` tag, the sanitizer config should looks like this: ```js static get sanitize() { @@ -120,14 +120,14 @@ static get sanitize() { b: {} // {} means clean all attributes. true — leave all attributes } } -``` +``` Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitize) ### Specifying a title -You can pass your Tool's title via `title` static getter. It can be used, for example, in the Tooltip with -icon description that appears by hover. +You can pass your Tool's title via `title` static getter. It can be used, for example, in the Tooltip with +icon description that appears by hover. ```ts export default class BoldInlineTool implements InlineTool { diff --git a/docs/tools.md b/docs/tools.md index 7cc4fd21..4fcf6e20 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -124,10 +124,10 @@ Both methods might be async. Editor.js handles paste on Blocks and provides API for Tools to process the pasted data. -When user pastes content into Editor, pasted content will be splitted into blocks. +When user pastes content into Editor, pasted content will be split into blocks. -1. If plain text will be pasted, it will be splitted by new line characters -2. If HTML string will be pasted, it will be splitted by block tags +1. If plain text will be pasted, it will be split by new line characters +2. If HTML string will be pasted, it will be split by block tags Also Editor API allows you to define your own pasting scenario. You can either: @@ -199,7 +199,7 @@ Pattern will be processed only if paste was on `defaultBlock` Tool and pasted st > Example -You can handle YouTube links and insert embeded video instead: +You can handle YouTube links and insert embedded video instead: ```javascript static get pasteConfig() { @@ -222,7 +222,7 @@ To handle file you should provide `files` property in your `pasteConfig` config | Name | Type | Description | | ---- | ---- | ----------- | | `extensions` | `string[]` | _Optional_ Array of extensions your Tool can handle | -| `mimeTypes` | `sring[]` | _Optional_ Array of MIME types your Tool can handle | +| `mimeTypes` | `string[]` | _Optional_ Array of MIME types your Tool can handle | Example @@ -456,7 +456,7 @@ class ListTool { constructor(){ this.data = { items: [ - 'Fisrt item', + 'First item', 'Second item', 'Third item' ], diff --git a/eslint.config.mjs b/eslint.config.mjs index 1a58ab50..cf093018 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'; import { FlatCompat } from '@eslint/eslintrc'; import cypress from 'eslint-plugin-cypress'; import playwright from 'eslint-plugin-playwright'; +import sonarjs from 'eslint-plugin-sonarjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -110,6 +111,18 @@ export default [ }, ], }), + { + files: ['src/**/*.ts', 'src/**/*.tsx'], + plugins: { + sonarjs, + }, + rules: { + // Duplicate code detection + 'sonarjs/no-duplicate-string': ['error', { threshold: 3 }], + 'sonarjs/no-identical-functions': 'error', + 'sonarjs/no-identical-expressions': 'error', + }, + }, { files: ['test/cypress/**/*.ts'], plugins: { diff --git a/package.json b/package.json index 01a233e6..f5f92446 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "test:e2e": "yarn build:test && cypress run", "test:e2e:open": "yarn build:test && cypress open", "test:e2e:playwright": "playwright test", - "test:e2e:playwright:ui": "playwright test --ui" + "test:e2e:playwright:ui": "playwright test --ui", + "jscpd": "jscpd src/ test/", + "jscpd:report": "jscpd . --reporters html,json --output .jscpd-report" }, "author": "CodeX", "license": "Apache-2.0", @@ -59,7 +61,9 @@ "eslint-plugin-chai-friendly": "^0.7.2", "eslint-plugin-cypress": "2.12.1", "eslint-plugin-playwright": "^2.3.0", + "eslint-plugin-sonarjs": "^3.0.5", "html-janitor": "^2.0.4", + "jscpd": "^4.0.5", "nanoid": "^4.0.2", "postcss-apply": "^0.12.0", "postcss-nested": "4.1.2", diff --git a/src/components/block-tunes/block-tune-delete.ts b/src/components/block-tunes/block-tune-delete.ts index 390ea76e..bd37c1ed 100644 --- a/src/components/block-tunes/block-tune-delete.ts +++ b/src/components/block-tunes/block-tune-delete.ts @@ -28,7 +28,7 @@ export default class DeleteTune implements BlockTune { * * @param {API} api - Editor's API */ - constructor({ api }) { + constructor({ api }: { api: API }) { this.api = api; } diff --git a/src/components/flipper.ts b/src/components/flipper.ts index 693a41f6..a002fd53 100644 --- a/src/components/flipper.ts +++ b/src/components/flipper.ts @@ -168,7 +168,7 @@ export default class Flipper { } /** - * Registeres function that should be executed on each navigation action + * Registers a function that should be executed on each navigation action * * @param cb - function to execute */ @@ -177,7 +177,7 @@ export default class Flipper { } /** - * Unregisteres function that is executed on each navigation action + * Unregisters a function that is executed on each navigation action * * @param cb - function to stop executing */ @@ -260,8 +260,8 @@ export default class Flipper { */ private handleTabPress(event: KeyboardEvent): void { /** this property defines leaf direction */ - const shiftKey = event.shiftKey, - direction = shiftKey ? DomIterator.directions.LEFT : DomIterator.directions.RIGHT; + const shiftKey = event.shiftKey; + const direction = shiftKey ? DomIterator.directions.LEFT : DomIterator.directions.RIGHT; switch (direction) { case DomIterator.directions.RIGHT: diff --git a/src/components/modules/api/events.ts b/src/components/modules/api/events.ts index 74484a23..2a24b0ac 100644 --- a/src/components/modules/api/events.ts +++ b/src/components/modules/api/events.ts @@ -1,5 +1,6 @@ import Module from '../../__module'; import type { Events } from '../../../../types/api'; +import type { EditorEventMap } from '../../events'; /** * @class EventsAPI @@ -13,9 +14,9 @@ export default class EventsAPI extends Module { */ public get methods(): Events { return { - emit: (eventName: string, data: object): void => this.emit(eventName, data), - off: (eventName: string, callback: () => void): void => this.off(eventName, callback), - on: (eventName: string, callback: () => void): void => this.on(eventName, callback), + emit: (eventName: keyof EditorEventMap, data: EditorEventMap[keyof EditorEventMap] | undefined): void => this.emit(eventName, data), + off: (eventName: keyof EditorEventMap, callback: (data?: unknown) => void): void => this.off(eventName, callback), + on: (eventName: keyof EditorEventMap, callback: () => void): void => this.on(eventName, callback), }; } @@ -25,7 +26,7 @@ export default class EventsAPI extends Module { * @param {string} eventName - event name to subscribe * @param {Function} callback - event handler */ - public on(eventName, callback): void { + public on(eventName: keyof EditorEventMap, callback: (data?: unknown) => void): void { this.eventsDispatcher.on(eventName, callback); } @@ -35,8 +36,11 @@ export default class EventsAPI extends Module { * @param {string} eventName - event to emit * @param {object} data - event's data */ - public emit(eventName, data): void { - this.eventsDispatcher.emit(eventName, data); + public emit(eventName: keyof EditorEventMap, data: EditorEventMap[keyof EditorEventMap] | undefined): void { + this.eventsDispatcher.emit( + eventName, + data + ); } /** @@ -45,7 +49,7 @@ export default class EventsAPI extends Module { * @param {string} eventName - event to unsubscribe * @param {Function} callback - event handler */ - public off(eventName, callback): void { + public off(eventName: keyof EditorEventMap, callback: (data?: unknown) => void): void { this.eventsDispatcher.off(eventName, callback); } } diff --git a/src/components/modules/api/inlineToolbar.ts b/src/components/modules/api/inlineToolbar.ts index 2091d9c0..9af71251 100644 --- a/src/components/modules/api/inlineToolbar.ts +++ b/src/components/modules/api/inlineToolbar.ts @@ -22,7 +22,7 @@ export default class InlineToolbarAPI extends Module { * Open Inline Toolbar */ public open(): void { - this.Editor.InlineToolbar.tryToShow(); + void this.Editor.InlineToolbar.tryToShow(); } /** diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 5aa5f7da..b12443a8 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -57,6 +57,11 @@ export default class InlineToolbar extends Module { */ private tools: Map = new Map(); + /** + * Shortcuts registered for inline tools + */ + private registeredShortcuts: Map = new Map(); + /** * @param moduleConfiguration - Module Configuration * @param moduleConfiguration.config - Editor's config @@ -70,6 +75,7 @@ export default class InlineToolbar extends Module { window.requestIdleCallback(() => { this.make(); + this.registerInitialShortcuts(); }, { timeout: 2000 }); } @@ -106,16 +112,7 @@ export default class InlineToolbar extends Module { return; } - for (const [tool, toolInstance] of this.tools) { - const shortcut = this.getToolShortcut(tool.name); - - if (shortcut !== undefined) { - Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut); - } - - /** - * @todo replace 'clear' with 'destroy' - */ + for (const toolInstance of this.tools.values()) { if (_.isFunction(toolInstance.clear)) { toolInstance.clear(); } @@ -162,6 +159,8 @@ export default class InlineToolbar extends Module { ...(this.isRtl ? [ this.Editor.UI.CSS.editorRtlFix ] : []), ]); + this.nodes.wrapper.setAttribute('data-interface', 'inline-toolbar'); + if (import.meta.env.MODE === 'test') { this.nodes.wrapper.setAttribute('data-cy', 'inline-toolbar'); } @@ -208,6 +207,8 @@ export default class InlineToolbar extends Module { this.nodes.wrapper?.append(this.popover.getElement()); this.popover.show(); + + this.checkToolsState(); } /** @@ -364,19 +365,15 @@ export default class InlineToolbar extends Module { private async getPopoverItems(): Promise { const popoverItems = [] as PopoverItemParams[]; - let i = 0; + const toolsEntries = Array.from(this.tools.entries()); - for (const [tool, instance] of this.tools) { + for (const [index, [tool, instance] ] of toolsEntries.entries()) { const renderedTool = await instance.render(); /** Enable tool shortcut */ const shortcut = this.getToolShortcut(tool.name); - if (shortcut !== undefined) { - try { - this.enableShortcuts(tool.name, shortcut); - } catch (e) {} - } + this.tryEnableShortcut(tool.name, shortcut); const shortcutBeautified = shortcut !== undefined ? _.beautifyShortcut(shortcut) : undefined; @@ -386,107 +383,202 @@ export default class InlineToolbar extends Module { ); [ renderedTool ].flat().forEach((item) => { - const commonPopoverItemParams = { - name: tool.name, - onActivate: () => { - this.toolClicked(instance); - }, - hint: { - title: toolTitle, - description: shortcutBeautified, - }, - } as PopoverItemParams; - - if ($.isElement(item)) { - /** - * Deprecated way to add custom html elements to the Inline Toolbar - */ - - const popoverItem = { - ...commonPopoverItemParams, - element: item, - type: PopoverItemType.Html, - } as PopoverItemParams; - - /** - * If tool specifies actions in deprecated manner, append them as children - */ - if (_.isFunction(instance.renderActions)) { - const actions = instance.renderActions(); - - (popoverItem as WithChildren).children = { - isOpen: instance.checkState?.(SelectionUtils.get()), - /** Disable keyboard navigation in actions, as it might conflict with enter press handling */ - isFlippable: false, - items: [ - { - type: PopoverItemType.Html, - element: actions, - }, - ], - }; - } else { - /** - * Legacy inline tools might perform some UI mutating logic in checkState method, so, call it just in case - */ - instance.checkState?.(SelectionUtils.get()); - } - - popoverItems.push(popoverItem); - } else if (item.type === PopoverItemType.Html) { - /** - * Actual way to add custom html elements to the Inline Toolbar - */ - popoverItems.push({ - ...commonPopoverItemParams, - ...item, - type: PopoverItemType.Html, - }); - } else if (item.type === PopoverItemType.Separator) { - /** - * Separator item - */ - popoverItems.push({ - type: PopoverItemType.Separator, - }); - } else { - /** - * Default item - */ - const popoverItem = { - ...commonPopoverItemParams, - ...item, - type: PopoverItemType.Default, - } as PopoverItemParams; - - /** - * Prepend the separator if item has children and not the first one - */ - if ('children' in popoverItem && i !== 0) { - popoverItems.push({ - type: PopoverItemType.Separator, - }); - } - - popoverItems.push(popoverItem); - - /** - * Append a separator after the item if it has children and not the last one - */ - if ('children' in popoverItem && i < this.tools.size - 1) { - popoverItems.push({ - type: PopoverItemType.Separator, - }); - } - } + this.processPopoverItem( + item, + tool.name, + instance, + toolTitle, + shortcutBeautified, + popoverItems, + index + ); }); - - i++; } return popoverItems; } + /** + * Try to enable shortcut for a tool, catching any errors silently + * + * @param toolName - tool name + * @param shortcut - shortcut to enable, or undefined + */ + private tryEnableShortcut(toolName: string, shortcut: string | undefined): void { + if (shortcut === undefined) { + return; + } + + try { + this.enableShortcuts(toolName, shortcut); + } catch (e) { + // Ignore errors when enabling shortcuts + } + } + + /** + * Process a single popover item and add it to the popoverItems array + * + * @param item - item to process + * @param toolName - name of the tool + * @param instance - tool instance + * @param toolTitle - localized tool title + * @param shortcutBeautified - beautified shortcut string or undefined + * @param popoverItems - array to add the processed item to + * @param index - current tool index + */ + private processPopoverItem( + item: HTMLElement | PopoverItemParams, + toolName: string, + instance: IInlineTool, + toolTitle: string, + shortcutBeautified: string | undefined, + popoverItems: PopoverItemParams[], + index: number + ): void { + const commonPopoverItemParams = { + name: toolName, + onActivate: () => { + this.toolClicked(instance); + }, + hint: { + title: toolTitle, + description: shortcutBeautified, + }, + } as PopoverItemParams; + + if ($.isElement(item)) { + this.processElementItem(item, instance, commonPopoverItemParams, popoverItems); + } else if (item.type === PopoverItemType.Html) { + /** + * Actual way to add custom html elements to the Inline Toolbar + */ + popoverItems.push({ + ...commonPopoverItemParams, + ...item, + type: PopoverItemType.Html, + }); + } else if (item.type === PopoverItemType.Separator) { + /** + * Separator item + */ + popoverItems.push({ + type: PopoverItemType.Separator, + }); + } else { + this.processDefaultItem(item, commonPopoverItemParams, popoverItems, index); + } + } + + /** + * Process an element-based popover item (deprecated way) + * + * @param item - HTML element + * @param instance - tool instance + * @param commonPopoverItemParams - common parameters for popover item + * @param popoverItems - array to add the processed item to + */ + private processElementItem( + item: HTMLElement, + instance: IInlineTool, + commonPopoverItemParams: PopoverItemParams, + popoverItems: PopoverItemParams[] + ): void { + /** + * Deprecated way to add custom html elements to the Inline Toolbar + */ + + const popoverItem = { + ...commonPopoverItemParams, + element: item, + type: PopoverItemType.Html, + } as PopoverItemParams; + + /** + * If tool specifies actions in deprecated manner, append them as children + */ + if (_.isFunction(instance.renderActions)) { + const actions = instance.renderActions(); + const selection = SelectionUtils.get(); + + (popoverItem as WithChildren).children = { + isOpen: selection ? instance.checkState?.(selection) ?? false : false, + /** Disable keyboard navigation in actions, as it might conflict with enter press handling */ + isFlippable: false, + items: [ + { + type: PopoverItemType.Html, + element: actions, + }, + ], + }; + } else { + this.checkLegacyToolState(instance); + } + + popoverItems.push(popoverItem); + } + + /** + * Check state for legacy inline tools that might perform UI mutating logic + * + * @param instance - tool instance + */ + private checkLegacyToolState(instance: IInlineTool): void { + /** + * Legacy inline tools might perform some UI mutating logic in checkState method, so, call it just in case + */ + const selection = SelectionUtils.get(); + + if (selection) { + instance.checkState?.(selection); + } + } + + /** + * Process a default popover item + * + * @param item - item to process + * @param commonPopoverItemParams - common parameters for popover item + * @param popoverItems - array to add the processed item to + * @param index - current tool index + */ + private processDefaultItem( + item: PopoverItemParams, + commonPopoverItemParams: PopoverItemParams, + popoverItems: PopoverItemParams[], + index: number + ): void { + /** + * Default item + */ + const popoverItem = { + ...commonPopoverItemParams, + ...item, + type: PopoverItemType.Default, + } as PopoverItemParams; + + /** + * Prepend the separator if item has children and not the first one + */ + if ('children' in popoverItem && index !== 0) { + popoverItems.push({ + type: PopoverItemType.Separator, + }); + } + + popoverItems.push(popoverItem); + + /** + * Append a separator after the item if it has children and not the last one + */ + if ('children' in popoverItem && index < this.tools.size - 1) { + popoverItems.push({ + type: PopoverItemType.Separator, + }); + } + } + /** * Get shortcut name for tool * @@ -522,6 +614,17 @@ export default class InlineToolbar extends Module { * @param shortcut - shortcut according to the ShortcutData Module format */ private enableShortcuts(toolName: string, shortcut: string): void { + const registeredShortcut = this.registeredShortcuts.get(toolName); + + if (registeredShortcut === shortcut) { + return; + } + + if (registeredShortcut !== undefined) { + Shortcuts.remove(document, registeredShortcut); + this.registeredShortcuts.delete(toolName); + } + Shortcuts.add({ name: shortcut, handler: (event) => { @@ -541,19 +644,21 @@ export default class InlineToolbar extends Module { */ // if (SelectionUtils.isCollapsed) return; - if (!currentBlock.tool.enabledInlineTools) { + if (currentBlock.tool.enabledInlineTools === false) { return; } event.preventDefault(); - this.popover?.activateItemByName(toolName); + void this.activateToolByShortcut(toolName); }, /** * We need to bind shortcut to the document to make it work in read-only mode */ on: document, }); + + this.registeredShortcuts.set(toolName, shortcut); } /** @@ -568,12 +673,49 @@ export default class InlineToolbar extends Module { this.checkToolsState(); } + /** + * Activates inline tool triggered by keyboard shortcut + * + * @param toolName - tool to activate + */ + private async activateToolByShortcut(toolName: string): Promise { + if (!this.opened) { + await this.tryToShow(); + } + + const selection = SelectionUtils.get(); + + if (!selection) { + this.popover?.activateItemByName(toolName); + + return; + } + + const toolEntry = Array.from(this.tools.entries()) + .find(([ toolAdapter ]) => toolAdapter.name === toolName); + + const toolInstance = toolEntry?.[1]; + const isToolActive = toolInstance?.checkState?.(selection) ?? false; + + if (isToolActive) { + return; + } + + this.popover?.activateItemByName(toolName); + } + /** * Check Tools` state by selection */ private checkToolsState(): void { + const selection = SelectionUtils.get(); + + if (!selection) { + return; + } + this.tools?.forEach((toolInstance) => { - toolInstance.checkState?.(SelectionUtils.get()); + toolInstance.checkState?.(selection); }); } @@ -592,4 +734,17 @@ export default class InlineToolbar extends Module { return result; } + + /** + * Register shortcuts for inline tools ahead of time so they are available before the toolbar opens + */ + private registerInitialShortcuts(): void { + const toolNames = Array.from(this.Editor.Tools.inlineTools.keys()); + + toolNames.forEach((toolName) => { + const shortcut = this.getToolShortcut(toolName); + + this.tryEnableShortcut(toolName, shortcut); + }); + } } diff --git a/src/components/utils/api.ts b/src/components/utils/api.ts index 2aa0e255..fa7924d6 100644 --- a/src/components/utils/api.ts +++ b/src/components/utils/api.ts @@ -8,7 +8,7 @@ import type Block from '../block'; * @param attribute - either BlockAPI or Block id or Block index * @param editor - Editor instance */ -export function resolveBlock(attribute: BlockAPI | BlockAPI['id'] | number, editor: EditorModules): Block | undefined { +export const resolveBlock = (attribute: BlockAPI | BlockAPI['id'] | number, editor: EditorModules): Block | undefined => { if (typeof attribute === 'number') { return editor.BlockManager.getBlockByIndex(attribute); } @@ -18,4 +18,4 @@ export function resolveBlock(attribute: BlockAPI | BlockAPI['id'] | number, edit } return editor.BlockManager.getBlockById(attribute.id); -} +}; diff --git a/src/components/utils/keyboard.ts b/src/components/utils/keyboard.ts index 65d8e614..9a4175a5 100644 --- a/src/components/utils/keyboard.ts +++ b/src/components/utils/keyboard.ts @@ -40,7 +40,7 @@ declare global { * @param code - {@link https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system} * @param fallback - fallback value to be returned if Keyboard API is not supported (Safari, Firefox) */ -export async function getKeyboardKeyForCode(code: string, fallback: string): Promise { +export const getKeyboardKeyForCode = async (code: string, fallback: string): Promise => { const keyboard = navigator.keyboard; if (!keyboard) { @@ -58,4 +58,4 @@ export async function getKeyboardKeyForCode(code: string, fallback: string): Pro return fallback; } -} +}; diff --git a/src/components/utils/mutations.ts b/src/components/utils/mutations.ts index 3d5e8f25..2a8e669b 100644 --- a/src/components/utils/mutations.ts +++ b/src/components/utils/mutations.ts @@ -4,7 +4,7 @@ * @param mutationRecord - mutation to check * @param element - element that is expected to contain mutation */ -export function isMutationBelongsToElement(mutationRecord: MutationRecord, element: Element): boolean { +export const isMutationBelongsToElement = (mutationRecord: MutationRecord, element: Element): boolean => { const { type, target, addedNodes, removedNodes } = mutationRecord; /** @@ -39,4 +39,4 @@ export function isMutationBelongsToElement(mutationRecord: MutationRecord, eleme } return false; -} +}; diff --git a/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts b/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts index 7e9889ac..1daf3d13 100644 --- a/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +++ b/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts @@ -9,7 +9,7 @@ import { PopoverItem } from '../popover-item'; import { css } from './popover-item-default.const'; /** - * Represents sigle popover item node + * Represents single popover item node * * @todo move nodes initialization to constructor * @todo replace multiple make() usages with constructing separate instances @@ -293,7 +293,7 @@ export class PopoverItemDefault extends PopoverItem { } /** - * Animates item which symbolizes that error occured while executing 'onActivate()' callback + * Animates item which symbolizes that error occurred while executing 'onActivate()' callback */ private animateError(): void { if (this.nodes.icon?.classList.contains(css.wobbleAnimation)) { diff --git a/src/components/utils/popover/components/search-input/search-input.types.ts b/src/components/utils/popover/components/search-input/search-input.types.ts index ecddc47b..363b53ef 100644 --- a/src/components/utils/popover/components/search-input/search-input.types.ts +++ b/src/components/utils/popover/components/search-input/search-input.types.ts @@ -14,7 +14,7 @@ export interface SearchableItem { */ export enum SearchInputEvent { /** - * When search quert applied + * When search query is applied */ Search = 'search' } @@ -24,7 +24,7 @@ export enum SearchInputEvent { */ export interface SearchInputEventMap { /** - * Fired when search quert applied + * Fired when search query is applied */ [SearchInputEvent.Search]: { query: string; items: SearchableItem[]}; } diff --git a/src/components/utils/popover/popover-inline.ts b/src/components/utils/popover/popover-inline.ts index ebe91223..5e014f60 100644 --- a/src/components/utils/popover/popover-inline.ts +++ b/src/components/utils/popover/popover-inline.ts @@ -149,7 +149,7 @@ export class PopoverInline extends PopoverDesktop { const nestedPopoverEl = nestedPopover.getElement(); /** - * We need to add class with nesting level, shich will help position nested popover. + * We need to add class with nesting level, which will help position nested popover. * Currently only '.ce-popover--nested-level-1' class is used */ nestedPopoverEl.classList.add(css.getPopoverNestedClass(nestedPopover.nestingLevel)); diff --git a/src/components/utils/resolve-aliases.ts b/src/components/utils/resolve-aliases.ts index b529a914..c44a51b9 100644 --- a/src/components/utils/resolve-aliases.ts +++ b/src/components/utils/resolve-aliases.ts @@ -6,18 +6,21 @@ * @param obj - object with aliases to be resolved * @param aliases - object with aliases info where key is an alias property name and value is an aliased property name */ -export function resolveAliases(obj: ObjectType, aliases: { [alias: string]: string }): ObjectType { +export const resolveAliases = >( + obj: ObjectType, + aliases: { [alias: string]: string } +): ObjectType => { const result = {} as ObjectType; Object.keys(obj).forEach(property => { const aliasedProperty = aliases[property]; if (aliasedProperty !== undefined) { - result[aliasedProperty] = obj[property]; + result[aliasedProperty as keyof ObjectType] = obj[property as keyof ObjectType]; } else { - result[property] = obj[property]; + result[property as keyof ObjectType] = obj[property as keyof ObjectType]; } }); return result; -} +}; diff --git a/src/components/utils/tools.ts b/src/components/utils/tools.ts index 4c384ca6..2a91c7cb 100644 --- a/src/components/utils/tools.ts +++ b/src/components/utils/tools.ts @@ -7,7 +7,7 @@ import { isFunction, isString } from '../utils'; * @param tool - tool to check * @param direction - export for tool to merge from, import for tool to merge to */ -export function isToolConvertable(tool: BlockToolAdapter, direction: 'export' | 'import'): boolean { +export const isToolConvertable = (tool: BlockToolAdapter, direction: 'export' | 'import'): boolean => { if (!tool.conversionConfig) { return false; } @@ -15,4 +15,4 @@ export function isToolConvertable(tool: BlockToolAdapter, direction: 'export' | const conversionProp = tool.conversionConfig[direction]; return isFunction(conversionProp) || isString(conversionProp); -} +}; diff --git a/test/cypress/fixtures/test.html b/test/cypress/fixtures/test.html index 38d93ec7..dd94c80e 100644 --- a/test/cypress/fixtures/test.html +++ b/test/cypress/fixtures/test.html @@ -1,5 +1,8 @@ + + + diff --git a/test/cypress/fixtures/tools/SimpleHeader.ts b/test/cypress/fixtures/tools/SimpleHeader.ts index bfcb91b4..23421374 100644 --- a/test/cypress/fixtures/tools/SimpleHeader.ts +++ b/test/cypress/fixtures/tools/SimpleHeader.ts @@ -22,6 +22,7 @@ export class SimpleHeader implements BaseTool { /** * Return Tool's view + * * @returns {HTMLHeadingElement} * @public */ @@ -43,6 +44,7 @@ export class SimpleHeader implements BaseTool { /** * Extract Tool's data from the view + * * @param toolsContent - Text tools rendered view */ public save(toolsContent: HTMLHeadingElement): BlockToolData { diff --git a/test/cypress/support/e2e.ts b/test/cypress/support/e2e.ts index ea7e192b..13093e4a 100644 --- a/test/cypress/support/e2e.ts +++ b/test/cypress/support/e2e.ts @@ -1,25 +1,27 @@ import '@cypress/code-coverage/support'; -/* global chai */ // because this file is imported from cypress/support/e2e.js // that means all other spec files will have this assertion plugin // available to them because the supportFile is bundled and served // prior to any spec files loading +// eslint-disable-next-line no-undef declare const chai: Chai.ChaiStatic; import type PartialBlockMutationEvent from '../fixtures/types/PartialBlockMutationEvent'; /** * Chai plugin for checking if passed onChange method is called with an array of passed events + * * @param _chai - Chai instance */ const beCalledWithBatchedEvents = (_chai: typeof chai): void => { /** * Check if passed onChange method is called with an array of passed events + * * @param expectedEvents - batched events to check */ - function assertToBeCalledWithBatchedEvents(this: Chai.AssertionStatic, expectedEvents: PartialBlockMutationEvent[]): void { + const assertToBeCalledWithBatchedEvents = function (this: typeof chai.Assertion.prototype, expectedEvents: PartialBlockMutationEvent[]): void { /** * EditorJS API is passed as the first parameter of the onChange callback */ @@ -50,7 +52,7 @@ const beCalledWithBatchedEvents = (_chai: typeof chai): void => { 'expected #{this} to not be called with #{exp}, but it was called with #{act} ', expectedEvents ); - } + }; _chai.Assertion.addMethod('calledWithBatchedEvents', assertToBeCalledWithBatchedEvents); }; diff --git a/test/cypress/support/index.ts b/test/cypress/support/index.ts index 39557412..c4c52730 100644 --- a/test/cypress/support/index.ts +++ b/test/cypress/support/index.ts @@ -27,6 +27,7 @@ import chaiSubset from 'chai-subset'; /** * "containSubset" object properties matcher */ +// eslint-disable-next-line no-undef chai.use(chaiSubset); /** diff --git a/test/cypress/support/utils/createEditorWithTextBlocks.ts b/test/cypress/support/utils/createEditorWithTextBlocks.ts index c9381a38..56981549 100644 --- a/test/cypress/support/utils/createEditorWithTextBlocks.ts +++ b/test/cypress/support/utils/createEditorWithTextBlocks.ts @@ -5,10 +5,11 @@ import type EditorJS from '../../../../types/index'; /** * Creates Editor instance with list of Paragraph blocks of passed texts + * * @param textBlocks - list of texts for Paragraph blocks * @param editorConfig - config to pass to the editor */ -export function createEditorWithTextBlocks(textBlocks: string[], editorConfig?: Omit): Chainable { +export const createEditorWithTextBlocks = (textBlocks: string[], editorConfig?: Omit): Chainable => { return cy.createEditor(Object.assign(editorConfig || {}, { data: { blocks: textBlocks.map((text) => ({ @@ -19,4 +20,4 @@ export function createEditorWithTextBlocks(textBlocks: string[], editorConfig?: })), }, })); -} +}; diff --git a/test/cypress/support/utils/createParagraphMock.ts b/test/cypress/support/utils/createParagraphMock.ts index 2fca16cd..8d93a7cf 100644 --- a/test/cypress/support/utils/createParagraphMock.ts +++ b/test/cypress/support/utils/createParagraphMock.ts @@ -2,17 +2,18 @@ import { nanoid } from 'nanoid'; /** * Creates a paragraph mock + * * @param text - text for the paragraph * @returns paragraph mock */ -export function createParagraphMock(text: string): { +export const createParagraphMock = (text: string): { id: string; type: string; data: { text: string }; -} { +} => { return { id: nanoid(), type: 'paragraph', data: { text }, }; -} \ No newline at end of file +}; diff --git a/test/cypress/tests/api/blocks.cy.ts b/test/cypress/tests/api/blocks.cy.ts index 328f8f2e..a6d16dab 100644 --- a/test/cypress/tests/api/blocks.cy.ts +++ b/test/cypress/tests/api/blocks.cy.ts @@ -372,7 +372,7 @@ describe('api.blocks', () => { /** * Call the 'convert' api method with nonexisting Block id */ - const fakeId = 'WRNG_ID'; + const fakeId = 'WRONG_ID'; const { convert } = editor.blocks; return convert(fakeId, 'convertableTool') @@ -401,7 +401,7 @@ describe('api.blocks', () => { /** * Call the 'convert' api method with nonexisting tool name */ - const nonexistingToolName = 'WRNG_TOOL_NAME'; + const nonexistingToolName = 'WRONG_TOOL_NAME'; const { convert } = editor.blocks; return convert(existingBlock.id, nonexistingToolName) diff --git a/test/cypress/tests/api/tools.cy.ts b/test/cypress/tests/api/tools.cy.ts index 196f0b33..adcbc04c 100644 --- a/test/cypress/tests/api/tools.cy.ts +++ b/test/cypress/tests/api/tools.cy.ts @@ -664,7 +664,7 @@ describe('Editor Tools Api', () => { .click() .paste({ // eslint-disable-next-line @typescript-eslint/naming-convention - 'text/html': '
  1. Orderd List
  2. Unorderd List
', // all attributes should be sanitized,
  • should be preserved + 'text/html': '
    1. Ordered List
    2. Unordered List
    ', // all attributes should be sanitized,
  • s should be preserved }) .then(() => { expect(pastedElement).not.to.be.undefined; diff --git a/test/cypress/tests/modules/InlineToolbar.cy.ts b/test/cypress/tests/modules/InlineToolbar.cy.ts index 124ac621..e5c2a9ae 100644 --- a/test/cypress/tests/modules/InlineToolbar.cy.ts +++ b/test/cypress/tests/modules/InlineToolbar.cy.ts @@ -50,7 +50,7 @@ describe('Inline Toolbar', () => { { type: 'paragraph', data: { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor.', + text: 'Writing is a powerful tool for communication and expression. When crafting content, it is important to consider your audience and the message you want to convey. Good writing requires careful thought, clear structure, and attention to detail. The process of editing helps refine your ideas and improve clarity.', }, }, ], @@ -263,13 +263,13 @@ describe('Inline Toolbar', () => { { type: 'paragraph', data: { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + text: 'Document editing requires precision and attention to detail. Every word matters when crafting clear and effective content.', }, }, { type: 'nestedEditor', data: { - text: 'Nunc pellentesque, tortor nec luctus venenatis', + text: 'The nested editor allows for complex document structures and hierarchical content organization', }, }, ], @@ -278,7 +278,7 @@ describe('Inline Toolbar', () => { cy.get(`[data-cy=${NESTED_EDITOR_ID}]`) .find('.ce-paragraph') - .selectText('tortor nec luctus'); + .selectText('document structures'); cy.get(`[data-cy=${NESTED_EDITOR_ID}]`) .find('[data-item-name=link]') diff --git a/test/cypress/tests/ui/BlockTunes.cy.ts b/test/cypress/tests/ui/BlockTunes.cy.ts index 8cbf901d..93df8bfd 100644 --- a/test/cypress/tests/ui/BlockTunes.cy.ts +++ b/test/cypress/tests/ui/BlockTunes.cy.ts @@ -152,7 +152,7 @@ describe('BlockTunes', () => { .contains('Convert to') .click(); - /** Check nected popover with "Heading" option is present */ + /** Check connected popover with the "Heading" option is present */ cy.get('[data-cy=editorjs]') .get('.ce-popover--nested [data-item-name=header]') .should('exist'); diff --git a/test/cypress/tests/utils/popover.cy.ts b/test/cypress/tests/utils/popover.cy.ts index 6efac34d..40fc777e 100644 --- a/test/cypress/tests/utils/popover.cy.ts +++ b/test/cypress/tests/utils/popover.cy.ts @@ -882,7 +882,7 @@ describe('Popover', () => { .should('exist'); }); - it('shoould support i18n in nested popover', () => { + it('should support i18n in nested popover', () => { /** * */ @@ -1018,7 +1018,7 @@ describe('Popover', () => { .should('exist'); }); - it('should support keyboard nevigation between items', () => { + it('should support keyboard navigation between items', () => { cy.createEditor({ tools: { header: { diff --git a/test/playwright/tests/inline-tools/link.spec.ts b/test/playwright/tests/inline-tools/link.spec.ts index 9239cbf9..c765e5fa 100644 --- a/test/playwright/tests/inline-tools/link.spec.ts +++ b/test/playwright/tests/inline-tools/link.spec.ts @@ -10,11 +10,10 @@ const TEST_PAGE_URL = pathToFileURL( ).href; const HOLDER_ID = 'editorjs'; -const EDITOR_SELECTOR = '[data-cy=editorjs]'; -const PARAGRAPH_SELECTOR = `${EDITOR_SELECTOR} .ce-paragraph`; -const INLINE_TOOLBAR_SELECTOR = `${EDITOR_SELECTOR} [data-cy=inline-toolbar]`; +const PARAGRAPH_SELECTOR = '.ce-paragraph'; +const INLINE_TOOLBAR_SELECTOR = '[data-interface=inline-toolbar]'; const LINK_BUTTON_SELECTOR = `${INLINE_TOOLBAR_SELECTOR} [data-item-name="link"] button`; -const LINK_INPUT_SELECTOR = `${INLINE_TOOLBAR_SELECTOR} [data-link-tool-input-opened]`; +const LINK_INPUT_SELECTOR = `input[data-link-tool-input-opened]`; const NOTIFIER_SELECTOR = '.cdx-notifies'; const MODIFIER_KEY = process.platform === 'darwin' ? 'Meta' : 'Control'; @@ -100,47 +99,42 @@ const createEditorWithBlocks = async (page: Page, blocks: OutputData['blocks']): }; /** - * Select text content within a locator by string match + * Select text content within a locator by string match using Playwright's built-in methods * * @param locator - The Playwright locator for the element containing the text * @param text - The text string to select within the element */ const selectText = async (locator: Locator, text: string): Promise => { - await locator.evaluate((element, targetText) => { - const el = element as HTMLElement; - const doc = el.ownerDocument; - const walker = doc.createTreeWalker(el, NodeFilter.SHOW_TEXT); - let textNode: Node | null = null; - let start = -1; + // Get the full text content to find the position + const fullText = await locator.textContent(); - while (walker.nextNode()) { - const node = walker.currentNode; - const content = node.textContent ?? ''; - const idx = content.indexOf(targetText); + if (!fullText || !fullText.includes(text)) { + throw new Error(`Text "${text}" was not found in element`); + } - if (idx !== -1) { - textNode = node; - start = idx; - break; - } - } + const startIndex = fullText.indexOf(text); + const endIndex = startIndex + text.length; - if (!textNode || start === -1) { - throw new Error(`Text "${targetText}" was not found in element`); - } + // Click on the element to focus it + await locator.click(); - const range = doc.createRange(); + // Get the page from the locator to use keyboard API + const page = locator.page(); - range.setStart(textNode, start); - range.setEnd(textNode, start + targetText.length); + // Move cursor to the start of the element + await page.keyboard.press('Home'); - const selection = doc.getSelection(); + // Navigate to the start position of the target text + for (let i = 0; i < startIndex; i++) { + await page.keyboard.press('ArrowRight'); + } - selection?.removeAllRanges(); - selection?.addRange(range); - - doc.dispatchEvent(new Event('selectionchange')); - }, text); + // Select the target text by holding Shift and moving right + await page.keyboard.down('Shift'); + for (let i = startIndex; i < endIndex; i++) { + await page.keyboard.press('ArrowRight'); + } + await page.keyboard.up('Shift'); }; /** @@ -163,7 +157,7 @@ test.describe('Inline Tool Link', () => { await page.waitForFunction(() => typeof window.EditorJS === 'function'); }); - test('should create a link by pressing Enter in the inline input', async ({ page }) => { + test('should create a link via Enter key', async ({ page }) => { await createEditorWithBlocks(page, [ { type: 'paragraph', @@ -177,41 +171,13 @@ test.describe('Inline Tool Link', () => { await selectText(paragraph, 'First block text'); await page.keyboard.press(`${MODIFIER_KEY}+k`); + await submitLink(page, 'https://codex.so'); await expect(paragraph.locator('a')).toHaveAttribute('href', 'https://codex.so'); }); - test('should remove fake background on selection change', async ({ page }) => { - await createEditorWithBlocks(page, [ - { - type: 'paragraph', - data: { - text: 'First block text', - }, - }, - { - type: 'paragraph', - data: { - text: 'Second block text', - }, - }, - ]); - - const firstParagraph = page.locator(PARAGRAPH_SELECTOR).first(); - const secondParagraph = page.locator(PARAGRAPH_SELECTOR).nth(1); - - await selectText(firstParagraph, 'First block text'); - await page.keyboard.press(`${MODIFIER_KEY}+k`); - - await expect(page.locator(`${PARAGRAPH_SELECTOR} span[style]`)).toHaveCount(1); - - await selectText(secondParagraph, 'Second block text'); - - await expect(page.locator(`${PARAGRAPH_SELECTOR} span[style]`)).toHaveCount(0); - }); - - test('should create a link via the inline toolbar button', async ({ page }) => { + test('should create a link via toolbar button', async ({ page }) => { await createEditorWithBlocks(page, [ { type: 'paragraph', @@ -238,7 +204,7 @@ test.describe('Inline Tool Link', () => { await expect(anchor).toHaveText('Link me'); }); - test('should keep link input open and show validation notice for invalid URLs', async ({ page }) => { + test('should show validation error for invalid URL', async ({ page }) => { await createEditorWithBlocks(page, [ { type: 'paragraph', @@ -269,7 +235,7 @@ test.describe('Inline Tool Link', () => { }, { notifierSelector: NOTIFIER_SELECTOR }); }); - test('should prefill inline input and update an existing link', async ({ page }) => { + test('should fill in and update existing link', async ({ page }) => { await createEditorWithBlocks(page, [ { type: 'paragraph', @@ -282,20 +248,26 @@ test.describe('Inline Tool Link', () => { const paragraph = page.locator(PARAGRAPH_SELECTOR).first(); await selectAll(paragraph); + // Use keyboard shortcut to trigger the link tool (this will open the toolbar and input) + await page.keyboard.press(`${MODIFIER_KEY}+k`); - const linkButton = page.locator(LINK_BUTTON_SELECTOR); const linkInput = page.locator(LINK_INPUT_SELECTOR); - await expect(linkButton).toHaveAttribute('data-link-tool-unlink', 'true'); - await expect(linkButton).toHaveAttribute('data-link-tool-active', 'true'); + // Wait for the input to appear (it should open automatically when a link is detected) + await expect(linkInput).toBeVisible(); await expect(linkInput).toHaveValue('https://codex.so'); + // Verify button state - find button by data attributes directly + const linkButton = page.locator('button[data-link-tool-unlink="true"][data-link-tool-active="true"]'); + + await expect(linkButton).toBeVisible(); + await submitLink(page, 'example.org'); await expect(paragraph.locator('a')).toHaveAttribute('href', 'http://example.org'); }); - test('should remove link when toggled while selection is inside anchor', async ({ page }) => { + test('should remove link when toggled', async ({ page }) => { await createEditorWithBlocks(page, [ { type: 'paragraph', @@ -308,15 +280,19 @@ test.describe('Inline Tool Link', () => { const paragraph = page.locator(PARAGRAPH_SELECTOR).first(); await selectAll(paragraph); + // Use keyboard shortcut to trigger the link tool + await page.keyboard.press(`${MODIFIER_KEY}+k`); - const linkButton = page.locator(LINK_BUTTON_SELECTOR); + // Find the unlink button by its data attributes + const linkButton = page.locator('button[data-link-tool-unlink="true"]'); + await expect(linkButton).toBeVisible(); await linkButton.click(); await expect(paragraph.locator('a')).toHaveCount(0); }); - test('should persist link markup in saved output', async ({ page }) => { + test('should persist link in saved output', async ({ page }) => { await createEditorWithBlocks(page, [ { type: 'paragraph', @@ -342,6 +318,57 @@ test.describe('Inline Tool Link', () => { expect(paragraphBlock?.data.text).toContain('Persist me'); }); + + test('should work in read-only mode', async ({ page }) => { + await createEditorWithBlocks(page, [ + { + type: 'paragraph', + data: { + text: 'Clickable link', + }, + }, + ]); + + const paragraph = page.locator(PARAGRAPH_SELECTOR).first(); + + // Create a link + await selectText(paragraph, 'Clickable link'); + await page.keyboard.press(`${MODIFIER_KEY}+k`); + await submitLink(page, 'https://example.com'); + + // Verify link was created + const anchor = paragraph.locator('a'); + await expect(anchor).toHaveAttribute('href', 'https://example.com'); + await expect(anchor).toHaveText('Clickable link'); + + // Enable read-only mode + await page.evaluate(async () => { + if (window.editorInstance) { + await window.editorInstance.readOnly.toggle(true); + } + }); + + // Verify read-only mode is enabled + const isReadOnly = await page.evaluate(() => { + return window.editorInstance?.readOnly.isEnabled ?? false; + }); + expect(isReadOnly).toBe(true); + + // Verify link still exists and has correct href + await expect(anchor).toHaveAttribute('href', 'https://example.com'); + await expect(anchor).toHaveText('Clickable link'); + + // Verify link is clickable by checking it's not disabled and has proper href + const href = await anchor.getAttribute('href'); + expect(href).toBe('https://example.com'); + + // Verify link element is visible and interactive + await expect(anchor).toBeVisible(); + const isDisabled = await anchor.evaluate((el) => { + return el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true'; + }); + expect(isDisabled).toBe(false); + }); }); declare global { diff --git a/test/testcases.md b/test/testcases.md index 353c30be..22ff10ea 100644 --- a/test/testcases.md +++ b/test/testcases.md @@ -66,7 +66,7 @@ This document will describe various test cases of the editor.js functionality. F - [ ] If `object` passed Editor.js should initialize `tool` and pass this object as `config` parameter of the tool's constructor - [ ] Checking the `shortcut` property - [ ] If `string` passed Editor.js should append the `tool` when such keys combination executed. - - [ ] Checking the `inilineToolbar` property + - [ ] Checking the `inlineToolbar` property - [ ] If `true` passed, the Editor.js should show the Inline Toolbar for this tool with [common](https://editorjs.io/configuration#inline-toolbar-order) settings. - [ ] If `false` passed, the Editor.js should not show the Inline Toolbar for this tool. - [ ] If `array` passed, the Editor.js should show the Inline Toolbar for this tool with a passed list of tools and their order. @@ -90,7 +90,7 @@ This document will describe various test cases of the editor.js functionality. F - [ ] If `object` passed - [ ] Checking the `blocks` property - [ ] If `array` of `object` passed, - - [ ] for each `object` + - [ ] for each `object` - [ ] Checking the `type` and `data` property - [ ] the Editor.js should be initialize with `block` of class `type` - [ ] If `type` not present in `tools`, the Editor.js should throw an error. @@ -101,8 +101,8 @@ This document will describe various test cases of the editor.js functionality. F - [ ] `readOnly` property - [ ] If `true` passed, - [ ] If any `tool` have not readOnly getter defined,The Editor.js should throw an error. - - [ ] otherwise, the Editor.js should be initialize with readOnly mode. + - [ ] otherwise, the Editor.js should be initialize with readOnly mode. - [ ] If `false` passed,the Editor.js should be initialized with the `tools` only. - [ ] If omitted,the Editor.js should be initialized with the `tools` only. -- [ ] `i18n` property \ No newline at end of file +- [ ] `i18n` property diff --git a/types/api/selection.d.ts b/types/api/selection.d.ts index 606d0373..9aa26f7e 100644 --- a/types/api/selection.d.ts +++ b/types/api/selection.d.ts @@ -18,10 +18,10 @@ export interface Selection { /** * Sets fake background. - * Allows to immitate selection while focus moved away + * Allows to imitate selection while focus moved away */ setFakeBackground(): void; - + /** * Removes fake background */ @@ -30,7 +30,7 @@ export interface Selection { /** * Save selection range. * Allows to save selection to be able to temporally move focus away. - * Might be usefull for inline tools + * Might be useful for inline tools */ save(): void; diff --git a/types/block-tunes/block-tune.d.ts b/types/block-tunes/block-tune.d.ts index 0e7b9d03..c522c542 100644 --- a/types/block-tunes/block-tune.d.ts +++ b/types/block-tunes/block-tune.d.ts @@ -9,7 +9,7 @@ export interface BlockTune { /** * Returns BlockTune's UI. * Should return either MenuConfig (recommended) (@see https://editorjs.io/menu-config/) - * or HTMLElement (UI consitency is not guaranteed) + * or an HTMLElement (UI consistency is not guaranteed) */ render(): HTMLElement | MenuConfig; diff --git a/yarn.lock b/yarn.lock index 8eaa3028..e94b57f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -144,7 +144,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": +"@babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": version: 7.28.5 resolution: "@babel/parser@npm:7.28.5" dependencies: @@ -196,7 +196,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.27.1, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": +"@babel/types@npm:^7.27.1, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": version: 7.28.5 resolution: "@babel/types@npm:7.28.5" dependencies: @@ -779,7 +779,9 @@ __metadata: eslint-plugin-chai-friendly: "npm:^0.7.2" eslint-plugin-cypress: "npm:2.12.1" eslint-plugin-playwright: "npm:^2.3.0" + eslint-plugin-sonarjs: "npm:^3.0.5" html-janitor: "npm:^2.0.4" + jscpd: "npm:^4.0.5" lodash: "npm:^4.17.21" nanoid: "npm:^4.0.2" postcss-apply: "npm:^0.12.0" @@ -1005,7 +1007,14 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.5.1, @eslint-community/regexpp@npm:^4.6.1": +"@eslint-community/regexpp@npm:4.12.1": + version: 4.12.1 + resolution: "@eslint-community/regexpp@npm:4.12.1" + checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.5.1, @eslint-community/regexpp@npm:^4.6.1, @eslint-community/regexpp@npm:^4.8.0": version: 4.12.2 resolution: "@eslint-community/regexpp@npm:4.12.2" checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d @@ -1165,6 +1174,55 @@ __metadata: languageName: node linkType: hard +"@jscpd/core@npm:4.0.1": + version: 4.0.1 + resolution: "@jscpd/core@npm:4.0.1" + dependencies: + eventemitter3: "npm:^5.0.1" + checksum: 10c0/a3df1f9f232532f696abcda9a1f74c61ca1dfe9b9d64ac9797c2a5442ce51acfe2bb4553f6f02a3089440188dd925b28a197480f2d1c8ebd8bb500ac260744c3 + languageName: node + linkType: hard + +"@jscpd/finder@npm:4.0.1": + version: 4.0.1 + resolution: "@jscpd/finder@npm:4.0.1" + dependencies: + "@jscpd/core": "npm:4.0.1" + "@jscpd/tokenizer": "npm:4.0.1" + blamer: "npm:^1.0.6" + bytes: "npm:^3.1.2" + cli-table3: "npm:^0.6.5" + colors: "npm:^1.4.0" + fast-glob: "npm:^3.3.2" + fs-extra: "npm:^11.2.0" + markdown-table: "npm:^2.0.0" + pug: "npm:^3.0.3" + checksum: 10c0/5ec96776029c267ede7a5b069740ec385d0f88a7915489e58f59e4e73086d83fde1842967aa1acbe26df97c3122e9f6d13bf1f07e55bdb7949109e7a7f38afe3 + languageName: node + linkType: hard + +"@jscpd/html-reporter@npm:4.0.1": + version: 4.0.1 + resolution: "@jscpd/html-reporter@npm:4.0.1" + dependencies: + colors: "npm:1.4.0" + fs-extra: "npm:^11.2.0" + pug: "npm:^3.0.3" + checksum: 10c0/0d35a4b8bd729510734e06a80a7b49334e43894fef4c8a771142885b1f69058cf1f5b014b5ca0e67daa9cefe507dbe29d0a04d02272a735441764f9413e2f09e + languageName: node + linkType: hard + +"@jscpd/tokenizer@npm:4.0.1": + version: 4.0.1 + resolution: "@jscpd/tokenizer@npm:4.0.1" + dependencies: + "@jscpd/core": "npm:4.0.1" + reprism: "npm:^0.0.11" + spark-md5: "npm:^3.0.2" + checksum: 10c0/b00a8b2c0b1a65d7c6abd0a4e3931627688b5d62c02c0ca286523f30475559e2b5a68600827685753e8c172b343aa70cda3ec4b1099362ca3b03c7f74f9dd1bb + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -1292,6 +1350,13 @@ __metadata: languageName: node linkType: hard +"@types/sarif@npm:^2.1.4": + version: 2.1.7 + resolution: "@types/sarif@npm:2.1.7" + checksum: 10c0/983d593735c42b288c3d95bb1655b036652438d267eecb2cd5d0f8c613ac98ae198eb7828dc171776e64a6ebe93e88c920ec9b80cf3c780e015e081ac5d26c01 + languageName: node + linkType: hard + "@types/semver@npm:^7.5.0": version: 7.7.1 resolution: "@types/semver@npm:7.7.1" @@ -1468,6 +1533,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^7.1.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: 10c0/bd0b2c2b0f334bbee48828ff897c12bd2eb5898d03bf556dcc8942022cec795ac5bb5b6b585e2de687db6231faf07e096b59a361231dd8c9344d5df5f7f0e526 + languageName: node + linkType: hard + "acorn@npm:^8.15.0, acorn@npm:^8.9.0": version: 8.15.0 resolution: "acorn@npm:8.15.0" @@ -1740,6 +1814,13 @@ __metadata: languageName: node linkType: hard +"asap@npm:~2.0.3": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d + languageName: node + linkType: hard + "asn1@npm:~0.2.3": version: 0.2.6 resolution: "asn1@npm:0.2.6" @@ -1749,6 +1830,13 @@ __metadata: languageName: node linkType: hard +"assert-never@npm:^1.2.1": + version: 1.4.0 + resolution: "assert-never@npm:1.4.0" + checksum: 10c0/494db08b89fb43d6231c9b4c48da22824f1912d88992bf0268e43b3dad0f64bd56d380addbb997d2dea7d859421d5e2904e8bd01243794f2bb5bfbc8d32d1fc6 + languageName: node + linkType: hard + "assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0": version: 1.0.0 resolution: "assert-plus@npm:1.0.0" @@ -1846,6 +1934,15 @@ __metadata: languageName: node linkType: hard +"babel-walk@npm:3.0.0-canary-5": + version: 3.0.0-canary-5 + resolution: "babel-walk@npm:3.0.0-canary-5" + dependencies: + "@babel/types": "npm:^7.9.6" + checksum: 10c0/17b689874d15c37714cedf6797dd9321dcb998d8e0dda9a8fe8c8bbbf128bbdeb8935cf56e8630d6b67eae76d2a0bc1e470751e082c3b0e30b80d58beafb5e64 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -1892,6 +1989,16 @@ __metadata: languageName: node linkType: hard +"blamer@npm:^1.0.6": + version: 1.0.7 + resolution: "blamer@npm:1.0.7" + dependencies: + execa: "npm:^4.0.0" + which: "npm:^2.0.2" + checksum: 10c0/55d35d08d5fda9dab089c88010f222426eedaf769972ab5f18ba8d4aa5717b20993f1d1b30f9386a3d73db1a5a2c137506b1b5002f07d77bef939dcbee16294f + languageName: node + linkType: hard + "blob-util@npm:^2.0.2": version: 2.0.2 resolution: "blob-util@npm:2.0.2" @@ -1987,6 +2094,13 @@ __metadata: languageName: node linkType: hard +"builtin-modules@npm:3.3.0, builtin-modules@npm:^3.3.0": + version: 3.3.0 + resolution: "builtin-modules@npm:3.3.0" + checksum: 10c0/2cb3448b4f7306dc853632a4fcddc95e8d4e4b9868c139400027b71938fc6806d4ff44007deffb362ac85724bd40c2c6452fb6a0aa4531650eeddb98d8e5ee8a + languageName: node + linkType: hard + "builtin-modules@npm:^1.1.1": version: 1.1.1 resolution: "builtin-modules@npm:1.1.1" @@ -1994,13 +2108,6 @@ __metadata: languageName: node linkType: hard -"builtin-modules@npm:^3.3.0": - version: 3.3.0 - resolution: "builtin-modules@npm:3.3.0" - checksum: 10c0/2cb3448b4f7306dc853632a4fcddc95e8d4e4b9868c139400027b71938fc6806d4ff44007deffb362ac85724bd40c2c6452fb6a0aa4531650eeddb98d8e5ee8a - languageName: node - linkType: hard - "builtins@npm:^5.0.1": version: 5.1.0 resolution: "builtins@npm:5.1.0" @@ -2010,6 +2117,13 @@ __metadata: languageName: node linkType: hard +"bytes@npm:3.1.2, bytes@npm:^3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + "cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -2156,6 +2270,15 @@ __metadata: languageName: node linkType: hard +"character-parser@npm:^2.2.0": + version: 2.2.0 + resolution: "character-parser@npm:2.2.0" + dependencies: + is-regex: "npm:^1.0.3" + checksum: 10c0/5a8d3eff2c912a6878c84e2ebf9d42524e858aa7e1a1c7e8bb79ab54da109ad008fe9057a9d2b3230541d7ff858eda98983a2ae15db57ba01af2e989d29e932e + languageName: node + linkType: hard + "check-more-types@npm:^2.24.0": version: 2.24.0 resolution: "check-more-types@npm:2.24.0" @@ -2221,7 +2344,7 @@ __metadata: languageName: node linkType: hard -"cli-table3@npm:~0.6.1": +"cli-table3@npm:^0.6.5, cli-table3@npm:~0.6.1": version: 0.6.5 resolution: "cli-table3@npm:0.6.5" dependencies: @@ -2337,6 +2460,13 @@ __metadata: languageName: node linkType: hard +"colors@npm:1.4.0, colors@npm:^1.4.0": + version: 1.4.0 + resolution: "colors@npm:1.4.0" + checksum: 10c0/9af357c019da3c5a098a301cf64e3799d27549d8f185d86f79af23069e4f4303110d115da98483519331f6fb71c8568d5688fa1c6523600044fd4a54e97c4efb + languageName: node + linkType: hard + "combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -2353,6 +2483,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^5.0.0": + version: 5.1.0 + resolution: "commander@npm:5.1.0" + checksum: 10c0/da9d71dbe4ce039faf1fe9eac3771dca8c11d66963341f62602f7b66e36d2a3f8883407af4f9a37b1db1a55c59c0c1325f186425764c2e963dc1d67aec2a4b6d + languageName: node + linkType: hard + "commander@npm:^6.2.1": version: 6.2.1 resolution: "commander@npm:6.2.1" @@ -2395,6 +2532,16 @@ __metadata: languageName: node linkType: hard +"constantinople@npm:^4.0.1": + version: 4.0.1 + resolution: "constantinople@npm:4.0.1" + dependencies: + "@babel/parser": "npm:^7.6.0" + "@babel/types": "npm:^7.6.1" + checksum: 10c0/15129adef19b1af2c3ade8bd38f97c34781bf461472a30ab414384b28d072be83070c8d2175787c045ef7c222c415101ae609936e7903427796a0c0eca8449fd + languageName: node + linkType: hard + "convert-source-map@npm:^1.7.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" @@ -2837,6 +2984,13 @@ __metadata: languageName: node linkType: hard +"doctypes@npm:^1.1.0": + version: 1.1.0 + resolution: "doctypes@npm:1.1.0" + checksum: 10c0/b3f9d597ad8b9ac6aeba9d64df61f0098174f7570e3d34f7ee245ebc736c7bee122d9738a18e22010b98983fd9a340d63043d3841f02d8a7742a2d96d2c72610 + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -3353,6 +3507,26 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-sonarjs@npm:^3.0.5": + version: 3.0.5 + resolution: "eslint-plugin-sonarjs@npm:3.0.5" + dependencies: + "@eslint-community/regexpp": "npm:4.12.1" + builtin-modules: "npm:3.3.0" + bytes: "npm:3.1.2" + functional-red-black-tree: "npm:1.0.1" + jsx-ast-utils-x: "npm:0.1.0" + lodash.merge: "npm:4.6.2" + minimatch: "npm:9.0.5" + scslre: "npm:0.3.0" + semver: "npm:7.7.2" + typescript: "npm:>=5" + peerDependencies: + eslint: ^8.0.0 || ^9.0.0 + checksum: 10c0/e771084f7d01ed32be882cf341a3e78f76655e73bd7916080a798d2323ea650a0330f4cd6c5b9cd020696eab4a931184df37de1fd38a3109083ecf28b97362f5 + languageName: node + linkType: hard + "eslint-plugin-standard@npm:5.0.0": version: 5.0.0 resolution: "eslint-plugin-standard@npm:5.0.0" @@ -3505,7 +3679,14 @@ __metadata: languageName: node linkType: hard -"execa@npm:4.1.0": +"eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 + languageName: node + linkType: hard + +"execa@npm:4.1.0, execa@npm:^4.0.0": version: 4.1.0 resolution: "execa@npm:4.1.0" dependencies: @@ -3583,7 +3764,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.1": +"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.1, fast-glob@npm:^3.3.2": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -3831,7 +4012,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^10.1.0": +"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" dependencies: @@ -3842,6 +4023,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.2.0": + version: 11.3.2 + resolution: "fs-extra@npm:11.3.2" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/f5d629e1bb646d5dedb4d8b24c5aad3deb8cc1d5438979d6f237146cd10e113b49a949ae1b54212c2fbc98e2d0995f38009a9a1d0520f0287943335e65fe919b + languageName: node + linkType: hard + "fs-extra@npm:^9.1.0": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" @@ -3929,6 +4121,13 @@ __metadata: languageName: node linkType: hard +"functional-red-black-tree@npm:1.0.1": + version: 1.0.1 + resolution: "functional-red-black-tree@npm:1.0.1" + checksum: 10c0/5959eed0375803d9924f47688479bb017e0c6816a0e5ac151e22ba6bfe1d12c41de2f339188885e0aa8eeea2072dad509d8e4448467e816bde0a2ca86a0670d3 + languageName: node + linkType: hard + "functions-have-names@npm:^1.2.3": version: 1.2.3 resolution: "functions-have-names@npm:1.2.3" @@ -4042,6 +4241,13 @@ __metadata: languageName: node linkType: hard +"gitignore-to-glob@npm:^0.3.0": + version: 0.3.0 + resolution: "gitignore-to-glob@npm:0.3.0" + checksum: 10c0/3c1baa2b7ca61ebd3d4784ef13a71ca2b98ee478ccafe234d6947b6f1b75343492304a7972fe15885b5c5e8f8d2c006cb6ca431dd7e9f270885766288f038f6a + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -4595,6 +4801,16 @@ __metadata: languageName: node linkType: hard +"is-expression@npm:^4.0.0": + version: 4.0.0 + resolution: "is-expression@npm:4.0.0" + dependencies: + acorn: "npm:^7.1.1" + object-assign: "npm:^4.1.1" + checksum: 10c0/541831d39d3e7bfc8cecd966d6b0f3c0e6d9055342f17b634fb23e74f51ce90f1bfc3cf231c722fe003a61e8d4f0b9e07244fdaba57f4fc70a163c74006fd5a0 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -4718,7 +4934,14 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.2.1": +"is-promise@npm:^2.0.0": + version: 2.2.2 + resolution: "is-promise@npm:2.2.2" + checksum: 10c0/2dba959812380e45b3df0fb12e7cb4d4528c989c7abb03ececb1d1fd6ab1cbfee956ca9daa587b9db1d8ac3c1e5738cf217bdb3dfd99df8c691be4c00ae09069 + languageName: node + linkType: hard + +"is-regex@npm:^1.0.3, is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" dependencies: @@ -4952,6 +5175,13 @@ __metadata: languageName: node linkType: hard +"js-stringify@npm:^1.0.2": + version: 1.0.2 + resolution: "js-stringify@npm:1.0.2" + checksum: 10c0/a450c04fde3a7e1c27f1c3c4300433f8d79322f9e3c2e76266843cef8c0b5a69b5f11b5f173212b2f15f2df09e068ef7ddf46ef775e2486f3006a6f4e912578d + languageName: node + linkType: hard + "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -4989,6 +5219,36 @@ __metadata: languageName: node linkType: hard +"jscpd-sarif-reporter@npm:4.0.3": + version: 4.0.3 + resolution: "jscpd-sarif-reporter@npm:4.0.3" + dependencies: + colors: "npm:^1.4.0" + fs-extra: "npm:^11.2.0" + node-sarif-builder: "npm:^2.0.3" + checksum: 10c0/bc8d45e60937946ca6457f4a8486376ff5b40b3d756e92b753445dbadeb4758a568c1191d5393a605e0172e7876c1145a96885437a82336380ba122a6cdda295 + languageName: node + linkType: hard + +"jscpd@npm:^4.0.5": + version: 4.0.5 + resolution: "jscpd@npm:4.0.5" + dependencies: + "@jscpd/core": "npm:4.0.1" + "@jscpd/finder": "npm:4.0.1" + "@jscpd/html-reporter": "npm:4.0.1" + "@jscpd/tokenizer": "npm:4.0.1" + colors: "npm:^1.4.0" + commander: "npm:^5.0.0" + fs-extra: "npm:^11.2.0" + gitignore-to-glob: "npm:^0.3.0" + jscpd-sarif-reporter: "npm:4.0.3" + bin: + jscpd: bin/jscpd + checksum: 10c0/f6fc533df6344521ca7869efe423f3d38c8f8206477eaeb0c2ee7e6c5551067823d571b30101449c4f487f8b2b0d585aa405ccb6e8b6cdaa20c937e541005986 + languageName: node + linkType: hard + "jsdoc-type-pratt-parser@npm:~4.0.0": version: 4.0.0 resolution: "jsdoc-type-pratt-parser@npm:4.0.0" @@ -5099,6 +5359,23 @@ __metadata: languageName: node linkType: hard +"jstransformer@npm:1.0.0": + version: 1.0.0 + resolution: "jstransformer@npm:1.0.0" + dependencies: + is-promise: "npm:^2.0.0" + promise: "npm:^7.0.1" + checksum: 10c0/11f9b4f368a55878dd7973154cd83b0adca27f974d21217728652530775b2bec281e92109de66f0c9e37c76af796d5b76b33f3e38363214a83d102d523a7285b + languageName: node + linkType: hard + +"jsx-ast-utils-x@npm:0.1.0": + version: 0.1.0 + resolution: "jsx-ast-utils-x@npm:0.1.0" + checksum: 10c0/bd147ff19bace8309e48110ec5c7a0c9f750148bcab699b5ba5d44dfac6cea8f358127f3da35ebe073f81cfe46494ce1e9647dd45681bb6c84d83c315904b72b + languageName: node + linkType: hard + "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -5202,7 +5479,7 @@ __metadata: languageName: node linkType: hard -"lodash.merge@npm:^4.6.2": +"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 @@ -5347,6 +5624,15 @@ __metadata: languageName: node linkType: hard +"markdown-table@npm:^2.0.0": + version: 2.0.0 + resolution: "markdown-table@npm:2.0.0" + dependencies: + repeat-string: "npm:^1.0.0" + checksum: 10c0/f257e0781ea50eb946919df84bdee4ba61f983971b277a369ca7276f89740fd0e2749b9b187163a42df4c48682b71962d4007215ce3523480028f06c11ddc2e6 + languageName: node + linkType: hard + "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -5444,6 +5730,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:9.0.5, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -5453,15 +5748,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed - languageName: node - linkType: hard - "minimist-options@npm:4.1.0": version: 4.1.0 resolution: "minimist-options@npm:4.1.0" @@ -5681,6 +5967,16 @@ __metadata: languageName: node linkType: hard +"node-sarif-builder@npm:^2.0.3": + version: 2.0.3 + resolution: "node-sarif-builder@npm:2.0.3" + dependencies: + "@types/sarif": "npm:^2.1.4" + fs-extra: "npm:^10.0.0" + checksum: 10c0/328821b645d46a256197c6f8a17f3eb9c53f1af3416184a3d2b354e28d595d2f216380b573ccbd2dd769eaac70e5d020b731f32dc66b8782af0e403723e5ed5f + languageName: node + linkType: hard + "nopt@npm:^8.0.0": version: 8.1.0 resolution: "nopt@npm:8.1.0" @@ -5764,6 +6060,13 @@ __metadata: languageName: node linkType: hard +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + "object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" @@ -6671,6 +6974,15 @@ __metadata: languageName: node linkType: hard +"promise@npm:^7.0.1": + version: 7.3.1 + resolution: "promise@npm:7.3.1" + dependencies: + asap: "npm:~2.0.3" + checksum: 10c0/742e5c0cc646af1f0746963b8776299701ad561ce2c70b49365d62c8db8ea3681b0a1bf0d4e2fe07910bf72f02d39e51e8e73dc8d7503c3501206ac908be107f + languageName: node + linkType: hard + "proxy-from-env@npm:1.0.0": version: 1.0.0 resolution: "proxy-from-env@npm:1.0.0" @@ -6678,6 +6990,133 @@ __metadata: languageName: node linkType: hard +"pug-attrs@npm:^3.0.0": + version: 3.0.0 + resolution: "pug-attrs@npm:3.0.0" + dependencies: + constantinople: "npm:^4.0.1" + js-stringify: "npm:^1.0.2" + pug-runtime: "npm:^3.0.0" + checksum: 10c0/28178e91c05e8eb9130861c78dccc61eae3e1610931346065bd32ad0b08b023a8dcf2470c3b2409ba45a5098d6d7ed15687717e91cf77770c6381a18626e5194 + languageName: node + linkType: hard + +"pug-code-gen@npm:^3.0.3": + version: 3.0.3 + resolution: "pug-code-gen@npm:3.0.3" + dependencies: + constantinople: "npm:^4.0.1" + doctypes: "npm:^1.1.0" + js-stringify: "npm:^1.0.2" + pug-attrs: "npm:^3.0.0" + pug-error: "npm:^2.1.0" + pug-runtime: "npm:^3.0.1" + void-elements: "npm:^3.1.0" + with: "npm:^7.0.0" + checksum: 10c0/517a93930dbc80bc7fa5f60ff324229a07cc5ab70ed9d344ce105e2fe24de68db5121c8457a9ba99cdc8d48dd18779dd34956ebfcab009b3c1c6843a3cade109 + languageName: node + linkType: hard + +"pug-error@npm:^2.0.0, pug-error@npm:^2.1.0": + version: 2.1.0 + resolution: "pug-error@npm:2.1.0" + checksum: 10c0/bbce339b17fab9890de84975c0cd8723a847bf65f35653d3ebcf77018e8ad91529d56e978ab80f4c64c9f4f07ef9e56e7a9fda3be44249c344a93ba11fccff79 + languageName: node + linkType: hard + +"pug-filters@npm:^4.0.0": + version: 4.0.0 + resolution: "pug-filters@npm:4.0.0" + dependencies: + constantinople: "npm:^4.0.1" + jstransformer: "npm:1.0.0" + pug-error: "npm:^2.0.0" + pug-walk: "npm:^2.0.0" + resolve: "npm:^1.15.1" + checksum: 10c0/7ddd62f5eb97f5242858bd56d93ffed387fef3742210a53770c980020cf91a34384b84b7fc8f0de185b43dfa77de2c4d0f63f575a4c5b3887fdef4e64b8d559d + languageName: node + linkType: hard + +"pug-lexer@npm:^5.0.1": + version: 5.0.1 + resolution: "pug-lexer@npm:5.0.1" + dependencies: + character-parser: "npm:^2.2.0" + is-expression: "npm:^4.0.0" + pug-error: "npm:^2.0.0" + checksum: 10c0/24195a5681953ab91c6a3ccd80a643f760dddb65e2f266bf8ccba145018ba0271536efe1572de2c2224163eb00873c2f1df0ad7ea7aa8bcbf79a66b586ca8435 + languageName: node + linkType: hard + +"pug-linker@npm:^4.0.0": + version: 4.0.0 + resolution: "pug-linker@npm:4.0.0" + dependencies: + pug-error: "npm:^2.0.0" + pug-walk: "npm:^2.0.0" + checksum: 10c0/db754ff34cdd4ba9d9e2d9535cce2a74178f2172e848a5fa6381907cb5bfaa0d39d4cc3eb29893d35fc1c417e83ae3cfd434640ba7d3b635c63199104fae976c + languageName: node + linkType: hard + +"pug-load@npm:^3.0.0": + version: 3.0.0 + resolution: "pug-load@npm:3.0.0" + dependencies: + object-assign: "npm:^4.1.1" + pug-walk: "npm:^2.0.0" + checksum: 10c0/2a7659dfaf9872dd25d851f85e4c27fa447d907b1db3540030cd844614159ff181e067d8f2bedf90eb6b5b1ff03747253859ecbbb822e40f4834b15591d4e108 + languageName: node + linkType: hard + +"pug-parser@npm:^6.0.0": + version: 6.0.0 + resolution: "pug-parser@npm:6.0.0" + dependencies: + pug-error: "npm:^2.0.0" + token-stream: "npm:1.0.0" + checksum: 10c0/faa6cec43afdeb2705eb8c68dfdb2e65836238df8043ae55295ffb72450b8c7a990ea1be60adbde19f58988b9e1d18a84ea42453e2c4f104d0031f78fda737b2 + languageName: node + linkType: hard + +"pug-runtime@npm:^3.0.0, pug-runtime@npm:^3.0.1": + version: 3.0.1 + resolution: "pug-runtime@npm:3.0.1" + checksum: 10c0/0db8166d2e17695a6941d1de81dcb21c8a52921299b1e03bf6a0a3d2b0036b51cf98101b3937b731c745e8d3e0268cb0b728c02f61a80a25fcfaa15c594fb1be + languageName: node + linkType: hard + +"pug-strip-comments@npm:^2.0.0": + version: 2.0.0 + resolution: "pug-strip-comments@npm:2.0.0" + dependencies: + pug-error: "npm:^2.0.0" + checksum: 10c0/ca498adedaeba51dd836b20129bbd161e2d5a397a2baaa553b1e74e888caa2258dcd7326396fc6f8fed8c7b7f906cfebc4c386ccbee8888a27b2ca0d4d86d206 + languageName: node + linkType: hard + +"pug-walk@npm:^2.0.0": + version: 2.0.0 + resolution: "pug-walk@npm:2.0.0" + checksum: 10c0/005d63177bcf057f5a618b182f6d4600afb039200b07a381a0d89288a2b3126e763a0a6c40b758eab0731c8e63cad1bbcb46d96803b9ae9cfc879f6ef5a0f8f4 + languageName: node + linkType: hard + +"pug@npm:^3.0.3": + version: 3.0.3 + resolution: "pug@npm:3.0.3" + dependencies: + pug-code-gen: "npm:^3.0.3" + pug-filters: "npm:^4.0.0" + pug-lexer: "npm:^5.0.1" + pug-linker: "npm:^4.0.0" + pug-load: "npm:^3.0.0" + pug-parser: "npm:^6.0.0" + pug-runtime: "npm:^3.0.1" + pug-strip-comments: "npm:^2.0.0" + checksum: 10c0/bda53d3a6deea1d348cd5ab17427c77f3d74165510ad16f4fd182cc63618ad09388ecda317d17122ee890c8a68f9a54b96221fce7f44a332e463fdbb10a9d1e2 + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.3 resolution: "pump@npm:3.0.3" @@ -6776,6 +7215,15 @@ __metadata: languageName: node linkType: hard +"refa@npm:^0.12.0, refa@npm:^0.12.1": + version: 0.12.1 + resolution: "refa@npm:0.12.1" + dependencies: + "@eslint-community/regexpp": "npm:^4.8.0" + checksum: 10c0/5c2f3dc5421f73aba44ec3d67bad58f36ff921dc13b0a921e1784c0510cf26be6d4e14010955a71607e67ff23a815f3ac30b337d06b5a2e8914417b67626c900 + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" @@ -6792,6 +7240,16 @@ __metadata: languageName: node linkType: hard +"regexp-ast-analysis@npm:^0.7.0": + version: 0.7.1 + resolution: "regexp-ast-analysis@npm:0.7.1" + dependencies: + "@eslint-community/regexpp": "npm:^4.8.0" + refa: "npm:^0.12.1" + checksum: 10c0/1b0e6d66e1e619b42a0e7f62b4c9983d0ce69d94fc759802c02272cbab8abd2e0d5b94186472de4e7c4baaf5826ca674d3c7c083615e39c4be55d1ff9d12c823 + languageName: node + linkType: hard + "regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" @@ -6815,6 +7273,20 @@ __metadata: languageName: node linkType: hard +"repeat-string@npm:^1.0.0": + version: 1.6.1 + resolution: "repeat-string@npm:1.6.1" + checksum: 10c0/87fa21bfdb2fbdedc44b9a5b118b7c1239bdd2c2c1e42742ef9119b7d412a5137a1d23f1a83dc6bb686f4f27429ac6f542e3d923090b44181bafa41e8ac0174d + languageName: node + linkType: hard + +"reprism@npm:^0.0.11": + version: 0.0.11 + resolution: "reprism@npm:0.0.11" + checksum: 10c0/d2221217566132f92a25254047ebba14fe82b159ca30fd460b262497d3a6bc8103900835bc9220931c4ce892cc08d073f63c168f908414f4a971c7bc7f27cb99 + languageName: node + linkType: hard + "request-progress@npm:^3.0.0": version: 3.0.0 resolution: "request-progress@npm:3.0.0" @@ -6866,7 +7338,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.2, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.3.2": +"resolve@npm:^1.15.1, resolve@npm:^1.22.2, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.3.2": version: 1.22.11 resolution: "resolve@npm:1.22.11" dependencies: @@ -6879,7 +7351,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.3.2#optional!builtin": +"resolve@patch:resolve@npm%3A^1.15.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.3.2#optional!builtin": version: 1.22.11 resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" dependencies: @@ -7032,6 +7504,26 @@ __metadata: languageName: node linkType: hard +"scslre@npm:0.3.0": + version: 0.3.0 + resolution: "scslre@npm:0.3.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.8.0" + refa: "npm:^0.12.0" + regexp-ast-analysis: "npm:^0.7.0" + checksum: 10c0/47eb72cf913693b453b7622dfee26871b4c408169874b31b8a1f3de8f41698e6dbacd7565fccc8d24cd2fd30f53c21f16995a7f9072e8b25cd938a6c3a750c3c + languageName: node + linkType: hard + +"semver@npm:7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" + bin: + semver: bin/semver.js + checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + languageName: node + linkType: hard + "semver@npm:^5.3.0, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -7287,6 +7779,13 @@ __metadata: languageName: node linkType: hard +"spark-md5@npm:^3.0.2": + version: 3.0.2 + resolution: "spark-md5@npm:3.0.2" + checksum: 10c0/3fd11735eac5e7d60d6006d99ac0a055f148a89e9baf5f0b51ac103022dec30556b44190b37f6737ca50f81e8e50dc13e724f9edf6290c412ff5ab2101ce7780 + languageName: node + linkType: hard + "spawn-wrap@npm:^2.0.0": version: 2.0.0 resolution: "spawn-wrap@npm:2.0.0" @@ -7763,6 +8262,13 @@ __metadata: languageName: node linkType: hard +"token-stream@npm:1.0.0": + version: 1.0.0 + resolution: "token-stream@npm:1.0.0" + checksum: 10c0/c1924a89686fc035d579cbe856da12306571d5fe7408eeeebe80df7c25c5cc644b8ae102d5cbc0f085d0e105f391d1a48dc0e568520434c5b444ea6c7de2b822 + languageName: node + linkType: hard + "tough-cookie@npm:^5.0.0": version: 5.1.2 resolution: "tough-cookie@npm:5.1.2" @@ -7991,6 +8497,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:>=5": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A5.0.3#optional!builtin": version: 5.0.3 resolution: "typescript@patch:typescript@npm%3A5.0.3#optional!builtin::version=5.0.3&hash=b5f058" @@ -8001,6 +8517,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A>=5#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -8175,6 +8701,13 @@ __metadata: languageName: node linkType: hard +"void-elements@npm:^3.1.0": + version: 3.1.0 + resolution: "void-elements@npm:3.1.0" + checksum: 10c0/0b8686f9f9aa44012e9bd5eabf287ae0cde409b9a2854c5a2335cb83920c957668ac5876e3f0d158dd424744ac411a7270e64128556b451ed3bec875ef18534d + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": version: 1.1.1 resolution: "which-boxed-primitive@npm:1.1.1" @@ -8254,7 +8787,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^2.0.1": +"which@npm:^2.0.1, which@npm:^2.0.2": version: 2.0.2 resolution: "which@npm:2.0.2" dependencies: @@ -8276,6 +8809,18 @@ __metadata: languageName: node linkType: hard +"with@npm:^7.0.0": + version: 7.0.2 + resolution: "with@npm:7.0.2" + dependencies: + "@babel/parser": "npm:^7.9.6" + "@babel/types": "npm:^7.9.6" + assert-never: "npm:^1.2.1" + babel-walk: "npm:3.0.0-canary-5" + checksum: 10c0/99289e49afc4b1776afae0ef85e84cfa775e8e07464d2b9853a31b0822347031d1cf77f287d25adc8c3f81e4fa68f4ee31526a9c95d4981ba08a1fe24dee111a + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5"