test: add tests for inline-link-tool

This commit is contained in:
JackUait 2025-11-10 05:04:38 +03:00
commit fdcfef6f5a
41 changed files with 1183 additions and 337 deletions

98
.cspell.json Normal file
View file

@ -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,}\"/"
]
}

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ dist/
coverage/
.nyc_output/
.vscode/launch.json
jscpd-report/

24
.jscpdrc.json Normal file
View file

@ -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"
}

40
.vscode/settings.json vendored
View file

@ -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"
]
}

Binary file not shown.

View file

@ -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

View file

@ -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

View file

@ -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 |

View file

@ -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 `<b>` tag, the sanitizer config should looks like this:
If your Tool wraps selected text with `<b>` 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 {

View file

@ -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'
],

View file

@ -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: {

View file

@ -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",

View file

@ -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;
}

View file

@ -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:

View file

@ -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);
}
}

View file

@ -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();
}
/**

View file

@ -57,6 +57,11 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
private tools: Map<InlineToolAdapter, IInlineTool> = new Map();
/**
* Shortcuts registered for inline tools
*/
private registeredShortcuts: Map<string, string> = new Map();
/**
* @param moduleConfiguration - Module Configuration
* @param moduleConfiguration.config - Editor's config
@ -70,6 +75,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
window.requestIdleCallback(() => {
this.make();
this.registerInitialShortcuts();
}, { timeout: 2000 });
}
@ -106,16 +112,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
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<InlineToolbarNodes> {
...(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<InlineToolbarNodes> {
this.nodes.wrapper?.append(this.popover.getElement());
this.popover.show();
this.checkToolsState();
}
/**
@ -364,19 +365,15 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
private async getPopoverItems(): Promise<PopoverItemParams[]> {
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<InlineToolbarNodes> {
);
[ 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<PopoverItemHtmlParams>).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<PopoverItemHtmlParams>).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<InlineToolbarNodes> {
* @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<InlineToolbarNodes> {
*/
// 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<InlineToolbarNodes> {
this.checkToolsState();
}
/**
* Activates inline tool triggered by keyboard shortcut
*
* @param toolName - tool to activate
*/
private async activateToolByShortcut(toolName: string): Promise<void> {
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<InlineToolbarNodes> {
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);
});
}
}

View file

@ -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);
}
};

View file

@ -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<string> {
export const getKeyboardKeyForCode = async (code: string, fallback: string): Promise<string> => {
const keyboard = navigator.keyboard;
if (!keyboard) {
@ -58,4 +58,4 @@ export async function getKeyboardKeyForCode(code: string, fallback: string): Pro
return fallback;
}
}
};

View file

@ -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;
}
};

View file

@ -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)) {

View file

@ -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[]};
}

View file

@ -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));

View file

@ -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<ObjectType>(obj: ObjectType, aliases: { [alias: string]: string }): ObjectType {
export const resolveAliases = <ObjectType extends Record<string, unknown>>(
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;
}
};

View file

@ -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);
}
};

View file

@ -1,5 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<!-- Load Editor.js's Core -->
<script src="./../../../dist/editorjs.umd.js"></script>

View file

@ -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 {

View file

@ -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);
};

View file

@ -27,6 +27,7 @@ import chaiSubset from 'chai-subset';
/**
* "containSubset" object properties matcher
*/
// eslint-disable-next-line no-undef
chai.use(chaiSubset);
/**

View file

@ -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<EditorConfig, 'data'>): Chainable<EditorJS> {
export const createEditorWithTextBlocks = (textBlocks: string[], editorConfig?: Omit<EditorConfig, 'data'>): Chainable<EditorJS> => {
return cy.createEditor(Object.assign(editorConfig || {}, {
data: {
blocks: textBlocks.map((text) => ({
@ -19,4 +20,4 @@ export function createEditorWithTextBlocks(textBlocks: string[], editorConfig?:
})),
},
}));
}
};

View file

@ -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 },
};
}
};

View file

@ -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)

View file

@ -664,7 +664,7 @@ describe('Editor Tools Api', () => {
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<ol start="50"><li>Orderd List</li><li>Unorderd List</li></ol>', // all attributes should be sanitized, <li> should be preserved
'text/html': '<ol start="50"><li>Ordered List</li><li>Unordered List</li></ol>', // all attributes should be sanitized, <li>s should be preserved
})
.then(() => {
expect(pastedElement).not.to.be.undefined;

View file

@ -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]')

View file

@ -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');

View file

@ -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: {

View file

@ -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<void> => {
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('<a href="https://codex.so">Persist me</a>');
});
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 {

View file

@ -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
- [ ] `i18n` property

View file

@ -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;

View file

@ -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;

601
yarn.lock
View file

@ -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<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.3.2#optional!builtin<compat/resolve>":
"resolve@patch:resolve@npm%3A^1.15.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.3.2#optional!builtin<compat/resolve>":
version: 1.22.11
resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin<compat/resolve>::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<compat/typescript>":
version: 5.0.3
resolution: "typescript@patch:typescript@npm%3A5.0.3#optional!builtin<compat/typescript>::version=5.0.3&hash=b5f058"
@ -8001,6 +8517,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A>=5#optional!builtin<compat/typescript>":
version: 5.9.3
resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::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"