Merge branch 'next' of github.com:codex-team/editor.js into feature/collaborative-editing

This commit is contained in:
Ilya Maroz 2022-12-01 16:54:26 +00:00
commit a5bea55d35
No known key found for this signature in database
GPG key ID: 3C27F3A104EBCE48
114 changed files with 3745 additions and 4330 deletions

View file

@ -1,27 +1,7 @@
{ {
"extends": [ "extends": [
"codex" "codex/ts"
], ],
"rules": {
/**
* Temporary suppress some errors. We need to fix them partially in next patches
*/
"import/no-duplicates": ["warn"],
"@typescript-eslint/triple-slash-reference": ["off"],
"jsdoc/no-undefined-types": ["warn", {"definedTypes": [
"ConstructorOptions",
"API",
"BlockToolConstructable",
"EditorConfig",
"Tool",
"ToolSettings"
]}]
},
"settings": {
"jsdoc": {
"mode": "typescript"
}
},
"globals": { "globals": {
"Node": true, "Node": true,
"Range": true, "Range": true,

View file

@ -53,7 +53,7 @@ jobs:
# Setup node environment # Setup node environment
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 15 node-version: 14.17.0
registry-url: https://registry.npmjs.org/ registry-url: https://registry.npmjs.org/
# Prepare, build and publish project # Prepare, build and publish project

View file

@ -4,7 +4,7 @@ jobs:
firefox: firefox:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: cypress/browsers:node14.16.0-chrome89-ff86 image: cypress/browsers:node14.17.0-chrome88-ff89
options: --user 1001 options: --user 1001
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View file

@ -22,7 +22,7 @@ jobs:
# Setup node environment # Setup node environment
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 15 node-version: 14.17.0
registry-url: https://registry.npmjs.org/ registry-url: https://registry.npmjs.org/
# Prepare, build and publish project # Prepare, build and publish project

39
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,39 @@
{
"cSpell.words": [
"autofocused",
"Behaviour",
"cacheable",
"childs",
"codexteam",
"colspan",
"contenteditable",
"contentless",
"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",
"viewports"
]
}

View file

@ -4,11 +4,21 @@
### 2.26.0 ### 2.26.0
- `New`*UI* — Block Tunes became vertical just like the Toolbox 🤩 - `New`*UI* — Block Tunes became vertical just like the Toolbox 🤩
- `New`*Block Tunes API* — Now `render()` method of a Block Tune can return config with just icon, label and callback instead of custom HTML. This impovement is a key to the new straightforward way of configuring tune's appearance in Block Tunes menu. - `New`*Block Tunes API* — Now `render()` method of a Block Tune can return config with just icon, label and callback instead of custom HTML. This improvement is a key to the new straightforward way of configuring tune's appearance in Block Tunes menu.
- `New`*Tools API* — As well as `render()` in `Tunes API`, Tool's `renderSettings()` now also supports new configuration format. - `New`*Tools API* — As well as `render()` in `Tunes API`, Tool's `renderSettings()` now also supports new configuration format.
- `New`*UI* — Meet the new icons from [CodeX Icons](https://github.com/codex-team/icons) pack 🛍 💝
- `New`*BlocksAPI* — the `blocks.insert()` method now also have the optional `id` param. If passed, this id will be used instead of the generated one.
- `Deprecated`*Styles API* — CSS classes `.cdx-settings-button` and `.cdx-settings-button--active` are not recommended to use. Consider configuring your block settings with new JSON API instead. - `Deprecated`*Styles API* — CSS classes `.cdx-settings-button` and `.cdx-settings-button--active` are not recommended to use. Consider configuring your block settings with new JSON API instead.
- `Fix` — Wrong element not highlighted anymore when popover opened. - `Fix` — Wrong element not highlighted anymore when popover opened.
- `Fix` — When Tunes Menu open keydown events can not be handled inside plugins. - `Fix` — When Tunes Menu open keydown events can not be handled inside plugins.
- `Fix` — If a Tool specifies some tags to substitute on paste, all attributes of that tags will be removed before passing them to the tool. Possible XSS vulnerability fixed.
- `Fix` — Pasting from Microsoft Word to Chrome (Mac OS) fixed. Now if there are no image-tools connected, regular text content will be pasted.
- `Fix` — Workaround for the HTMLJanitor bug with Tables (https://github.com/guardian/html-janitor/issues/3) added
- `Fix` — Toolbox shortcuts appearance and execution fixed [#2112](https://github.com/codex-team/editor.js/issues/2112)
- `Fix` — Inline Tools click handling on mobile devices improved
- `Improvement`*Tools API*`pasteConfig().tags` now support sanitizing configuration. It allows you to leave some explicitly specified attributes for pasted content.
- `Improvement`*CodeStyle* — [CodeX ESLint Config](https://github.com/codex-team/eslint-config) has bee updated. All ESLint/Spelling issues resolved
- `Improvement`*ToolsAPI* — The `icon` property of the `toolbox` getter became optional.
### 2.25.0 ### 2.25.0
@ -18,7 +28,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.24.4 ### 2.24.4
- `Fix` — Keyboard selection by word [2045](https://github.com/codex-team/editor.js/issues/2045) - `Fix` — Keyboard selection by word [#2045](https://github.com/codex-team/editor.js/issues/2045)
### 2.24.3 ### 2.24.3
@ -112,7 +122,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.20.1 ### 2.20.1
- `Fix` - Create a new block when clicked at the bottom [#1588](https://github.com/codex-team/editor.js/issues/1588). - `Fix` - Create a new block when clicked at the bottom [#1588](https://github.com/codex-team/editor.js/issues/1588).
- `Fix` — Fix sanitisation problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631) - `Fix` — Fix sanitization problem with Inline Tools [#1631](https://github.com/codex-team/editor.js/issues/1631)
- `Fix` — Fix copy in FireFox [1625](https://github.com/codex-team/editor.js/issues/1625) - `Fix` — Fix copy in FireFox [1625](https://github.com/codex-team/editor.js/issues/1625)
- `Refactoring` - The Sanitizer module is util now. - `Refactoring` - The Sanitizer module is util now.
- `Refactoring` - Tooltip module is util now. - `Refactoring` - Tooltip module is util now.
@ -167,7 +177,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
- `New` - Tool's `reset` static method added to the API to clean up any data added by Tool on initialization - `New` - Tool's `reset` static method added to the API to clean up any data added by Tool on initialization
- `Improvements` - The `initialBlock` property of Editor config is deprecated. Use the `defaultBlock` instead. [#993](https://github.com/codex-team/editor.js/issues/993) - `Improvements` - The `initialBlock` property of Editor config is deprecated. Use the `defaultBlock` instead. [#993](https://github.com/codex-team/editor.js/issues/993)
- `Improvements` - BlockAPI `call()` method now returns the result of calling method, thus allowing it to expose arbitrary data as needed [#1205](https://github.com/codex-team/editor.js/pull/1205) - `Improvements` - BlockAPI `call()` method now returns the result of calling method, thus allowing it to expose arbitrary data as needed [#1205](https://github.com/codex-team/editor.js/pull/1205)
- `Improvements` - Unuseful log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269) - `Improvements` - Useless log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269)
- `Improvements` - Allowed to set `false` as `toolbox` config in order to hide Toolbox button [#1221](https://github.com/codex-team/editor.js/issues/1221) - `Improvements` - Allowed to set `false` as `toolbox` config in order to hide Toolbox button [#1221](https://github.com/codex-team/editor.js/issues/1221)
- `Fix` — Fix problem with types usage [#1183](https://github.com/codex-team/editor.js/issues/1183) - `Fix` — Fix problem with types usage [#1183](https://github.com/codex-team/editor.js/issues/1183)
- `Fix` - Fixed issue with Spam clicking the "Click to tune" button duplicates the icons on FireFox. [#1273](https://github.com/codex-team/editor.js/issues/1273) - `Fix` - Fixed issue with Spam clicking the "Click to tune" button duplicates the icons on FireFox. [#1273](https://github.com/codex-team/editor.js/issues/1273)
@ -178,7 +188,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
- `Fix` - Fixed issue with enter key in inputs and textareas [#920](https://github.com/codex-team/editor.js/issues/920) - `Fix` - Fixed issue with enter key in inputs and textareas [#920](https://github.com/codex-team/editor.js/issues/920)
- `Fix` - blocks.getBlockByIndex() API method now returns void for indexes out of range [#1270](https://github.com/codex-team/editor.js/issues/1270) - `Fix` - blocks.getBlockByIndex() API method now returns void for indexes out of range [#1270](https://github.com/codex-team/editor.js/issues/1270)
- `Fix` - Fixed the `Tab` key behavior when the caret is not set inside contenteditable element, but the block is selected [#1302](https://github.com/codex-team/editor.js/issues/1302). - `Fix` - Fixed the `Tab` key behavior when the caret is not set inside contenteditable element, but the block is selected [#1302](https://github.com/codex-team/editor.js/issues/1302).
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contentedtable element changed [#843](https://github.com/codex-team/editor.js/issues/843) - `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contenteditable element changed [#843](https://github.com/codex-team/editor.js/issues/843)
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called after the callback throws an exception [#1339](https://github.com/codex-team/editor.js/issues/1339) - `Fix` - Fixed the `onChange` callback issue. This method didn't be called after the callback throws an exception [#1339](https://github.com/codex-team/editor.js/issues/1339)
- `Fix` - The internal `shortcut` getter of Tools classes will work now. - `Fix` - The internal `shortcut` getter of Tools classes will work now.
- `Deprecated` — The Inline Tool `clear()` method is deprecated because the new instance of Inline Tools will be created on every showing of the Inline Toolbar - `Deprecated` — The Inline Tool `clear()` method is deprecated because the new instance of Inline Tools will be created on every showing of the Inline Toolbar
@ -227,7 +237,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
- `Fix` — Fix Firefox bug with incorrect height and cursor position of empty content editable elements [#947](https://github.com/codex-team/editor.js/issues/947) [#876](https://github.com/codex-team/editor.js/issues/876) [#608](https://github.com/codex-team/editor.js/issues/608) [#876](https://github.com/codex-team/editor.js/issues/876) - `Fix` — Fix Firefox bug with incorrect height and cursor position of empty content editable elements [#947](https://github.com/codex-team/editor.js/issues/947) [#876](https://github.com/codex-team/editor.js/issues/876) [#608](https://github.com/codex-team/editor.js/issues/608) [#876](https://github.com/codex-team/editor.js/issues/876)
- `Fix` — Set initial hidden Inline Toolbar position [#979](https://github.com/codex-team/editor.js/issues/979) - `Fix` — Set initial hidden Inline Toolbar position [#979](https://github.com/codex-team/editor.js/issues/979)
- `Fix` — Fix issue with CodeX.Toolips TypeScript definitions [#978](https://github.com/codex-team/editor.js/issues/978) - `Fix` — Fix issue with CodeX.Tooltips TypeScript definitions [#978](https://github.com/codex-team/editor.js/issues/978)
- `Fix` — Fix some issues with Inline and Tunes toolbars. - `Fix` — Fix some issues with Inline and Tunes toolbars.
- `Fix` - Fix `minHeight` option with zero-value issue [#724](https://github.com/codex-team/editor.js/issues/724) - `Fix` - Fix `minHeight` option with zero-value issue [#724](https://github.com/codex-team/editor.js/issues/724)
- `Improvements` — Disable Conversion Toolbar if there are no Tools to convert [#984](https://github.com/codex-team/editor.js/issues/984) - `Improvements` — Disable Conversion Toolbar if there are no Tools to convert [#984](https://github.com/codex-team/editor.js/issues/984)
@ -340,7 +350,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.11.5 ### 2.11.5
- `Fix` *RectangeSelection* — Redesign of the scrolling zones - `Fix` *RectangleSelection* — Redesign of the scrolling zones
### 2.11.4 ### 2.11.4
@ -356,7 +366,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.11.1 ### 2.11.1
- `Fix` *RectangeSelection* — Selection is available only for the main mouse button - `Fix` *RectangleSelection* — Selection is available only for the main mouse button
### 2.11.0 ### 2.11.0
@ -388,7 +398,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.9.0 ### 2.9.0
- `New` *RectangeSelection* — Ability to select Block or several Blocks with mouse - `New` *RectangleSelection* — Ability to select Block or several Blocks with mouse
### 2.8.1 ### 2.8.1
@ -396,7 +406,7 @@ Due to that API changes: tool's `toolbox` getter now can return either a single
### 2.8.0 ### 2.8.0
- `Imporvements` *API* — Added [API methods](api.md#caretapi) to manage caret position - `Improvements` *API* — Added [API methods](api.md#caretapi) to manage caret position
### 2.7.32 ### 2.7.32

View file

@ -151,7 +151,7 @@ To handle pasted HTML elements object returned from `pasteConfig` getter should
For correct work you MUST provide `onPaste` handler at least for `defaultBlock` Tool. For correct work you MUST provide `onPaste` handler at least for `defaultBlock` Tool.
> Example #### Example
Header Tool can handle `H1`-`H6` tags using paste handling API Header Tool can handle `H1`-`H6` tags using paste handling API
@ -163,7 +163,27 @@ static get pasteConfig() {
} }
``` ```
> Same tag can be handled by one (first specified) Tool only. **Note. Same tag can be handled by one (first specified) Tool only.**
**Note. All attributes of pasted tag will be removed. To leave some attribute, you should explicitly specify them. Se below**
Let's suppose you want to leave the 'src' attribute when handle pasting of the `img` tags. Your config should look like this:
```javascript
static get pasteConfig() {
return {
tags: [
{
img: {
src: true
}
}
],
}
}
```
[Read more](https://editorjs.io/sanitizer) about the sanitizing configuration.
### RegExp patterns handling ### RegExp patterns handling

@ -1 +1 @@
Subproject commit 35742f01ae5875d442b145121d3c9b71b23aea56 Subproject commit 23de06be69bb9e636a2278b0d54f8c2d85d7ae13

@ -1 +1 @@
Subproject commit 585bca271f7696cd17533fa5877d1f72b3a03d2e Subproject commit 056ff5e52677d239dfe73b9ddc6e074474a54a63

@ -1 +1 @@
Subproject commit 0fc365ef256decb8f765fb72b060d5bef9254aa3 Subproject commit 13372270afdee5dfb0f1509b491888db7f06a8e4

@ -1 +1 @@
Subproject commit 9add95389afca0711c05260a92283fae8eb209eb Subproject commit 6da4d45354b8b05b384ea175d7685c733c80a9c8

@ -1 +1 @@
Subproject commit ad0d9012d149e3ca4b41a5ce096b31767cc8c1fd Subproject commit a45d7329f877552bfa6c3c4e10c9bdd13f3e9e31

View file

@ -1,6 +1,6 @@
{ {
"name": "@editorjs/editorjs", "name": "@editorjs/editorjs",
"version": "2.26.0-rc.0", "version": "2.26.0",
"description": "Editor.js — Native JS, based on API and Open Source", "description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editor.js", "main": "dist/editor.js",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
@ -13,15 +13,14 @@
], ],
"scripts": { "scripts": {
"clear": "rimraf dist && mkdirp dist", "clear": "rimraf dist && mkdirp dist",
"build": "yarn clear && yarn svg && yarn build:webpack:prod", "build": "yarn clear && yarn build:webpack:prod",
"build:dev": "yarn clear && yarn svg && yarn build:webpack:dev", "build:dev": "yarn clear && yarn build:webpack:dev",
"build:webpack:dev": "webpack --mode development --progress --display-error-details --display-entrypoints --watch", "build:webpack:dev": "webpack --mode development --progress --display-error-details --display-entrypoints --watch",
"build:webpack:prod": "webpack --mode production", "build:webpack:prod": "webpack --mode production",
"lint": "eslint src/ --ext .ts && yarn lint:tests", "lint": "eslint src/ --ext .ts && yarn lint:tests",
"lint:errors": "eslint src/ --ext .ts --quiet", "lint:errors": "eslint src/ --ext .ts --quiet",
"lint:fix": "eslint src/ --ext .ts --fix", "lint:fix": "eslint src/ --ext .ts --fix",
"lint:tests": "eslint test/ --ext .ts", "lint:tests": "eslint test/ --ext .ts",
"svg": "svg-sprite-generate -d src/assets/ -o dist/sprite.svg",
"ci:pull_paragraph": "git submodule update --init ./src/tools/paragraph", "ci:pull_paragraph": "git submodule update --init ./src/tools/paragraph",
"pull_tools": "git submodule update --init --recursive", "pull_tools": "git submodule update --init --recursive",
"_tools:checkout": "git submodule foreach \"git checkout master || git checkout main\"", "_tools:checkout": "git submodule foreach \"git checkout master || git checkout main\"",
@ -67,13 +66,12 @@
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"cypress": "^6.8.0", "cypress": "^6.8.0",
"cypress-intellij-reporter": "^0.0.6", "cypress-intellij-reporter": "^0.0.6",
"eslint": "^6.8.0", "eslint": "^8.28.0",
"eslint-config-codex": "^1.3.3", "eslint-config-codex": "^1.7.1",
"eslint-loader": "^4.0.2", "eslint-loader": "^4.0.2",
"eslint-plugin-chai-friendly": "^0.6.0", "eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-cypress": "^2.11.2", "eslint-plugin-cypress": "^2.12.1",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"html-janitor": "^2.0.4",
"license-webpack-plugin": "^2.1.4", "license-webpack-plugin": "^2.1.4",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"postcss-apply": "^0.12.0", "postcss-apply": "^0.12.0",
@ -82,10 +80,8 @@
"postcss-nested": "^4.1.2", "postcss-nested": "^4.1.2",
"postcss-nested-ancestors": "^2.0.0", "postcss-nested-ancestors": "^2.0.0",
"postcss-preset-env": "^6.6.0", "postcss-preset-env": "^6.6.0",
"raw-loader": "^4.0.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stylelint": "^13.3.3", "stylelint": "^13.3.3",
"svg-sprite-generator": "^0.0.7",
"terser-webpack-plugin": "^2.3.6", "terser-webpack-plugin": "^2.3.6",
"ts-loader": "^7.0.1", "ts-loader": "^7.0.1",
"tslint": "^6.1.1", "tslint": "^6.1.1",
@ -98,8 +94,10 @@
"url": "https://opencollective.com/editorjs" "url": "https://opencollective.com/editorjs"
}, },
"dependencies": { "dependencies": {
"@codexteam/icons": "^0.0.4",
"codex-notifier": "^1.1.2", "codex-notifier": "^1.1.2",
"codex-tooltip": "^1.0.5", "codex-tooltip": "^1.0.5",
"html-janitor": "^2.0.4",
"nanoid": "^3.1.22" "nanoid": "^3.1.22"
} }
} }

View file

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path transform="matrix(1 0 0 -1 0 14)" d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 353 B

View file

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 321 B

View file

@ -1 +0,0 @@
<svg width="12" height="14" viewBox="0 0 12 14" xmlns="http://www.w3.org/2000/svg"><path d="M5.997 14H1.72c-.618 0-1.058-.138-1.323-.415C.132 13.308 0 12.867 0 12.262V1.738C0 1.121.135.676.406.406.676.136 1.114 0 1.719 0h4.536c.669 0 1.248.041 1.738.124.49.083.93.242 1.318.478a3.458 3.458 0 0 1 1.461 1.752c.134.366.2.753.2 1.16 0 1.401-.7 2.426-2.1 3.075 1.84.586 2.76 1.726 2.76 3.42 0 .782-.2 1.487-.602 2.114a3.61 3.61 0 0 1-1.623 1.39 5.772 5.772 0 0 1-1.471.377c-.554.073-1.2.11-1.939.11zm-.21-6.217h-2.95v4.087h3.046c1.916 0 2.874-.69 2.874-2.072 0-.707-.248-1.22-.745-1.537-.496-.319-1.238-.478-2.225-.478zM2.837 2.13v3.619h2.597c.707 0 1.252-.067 1.638-.2.385-.134.68-.389.883-.765.16-.267.239-.566.239-.897 0-.707-.252-1.176-.755-1.409-.503-.232-1.27-.348-2.301-.348H2.836z"/></svg>

Before

Width:  |  Height:  |  Size: 794 B

View file

@ -1,3 +0,0 @@
<svg width="237" height="237" viewBox="0 0 237 237" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path transform="rotate(45 280.675 51.325)" d="M191 191V73c0-5.523 4.477-10 10-10h25c5.523 0 10 4.477 10 10v118h118c5.523 0 10 4.477 10 10v25c0 5.523-4.477 10-10 10H236v118c0 5.523-4.477 10-10 10h-25c-5.523 0-10-4.477-10-10V236H73c-5.523 0-10-4.477-10-10v-25c0-5.523 4.477-10 10-10h118z"/>
</svg>

Before

Width:  |  Height:  |  Size: 430 B

View file

@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g transform="translate(4 1.5)" fill-rule="evenodd">
<circle cx="1.3" cy="1.3" r="1.3"/>
<circle cx="6.5" cy="1.3" r="1.3"/>
<circle cx="6.5" cy="6.5" r="1.3"/>
<circle cx="1.3" cy="6.5" r="1.3"/>
<circle cx="6.5" cy="11.7" r="1.3"/>
<circle cx="1.3" cy="11.7" r="1.3"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 372 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 11">
<path d="M3.289 4.17L2.164 9.713c-.078.384-.238.674-.48.87-.243.198-.52.296-.831.296-.312 0-.545-.1-.699-.302-.153-.202-.192-.49-.116-.864L1.15 4.225c.077-.38.232-.665.466-.857a1.25 1.25 0 01.818-.288c.312 0 .55.096.713.288.163.192.21.46.141.801zm-.667-2.09c-.295 0-.53-.09-.706-.273-.176-.181-.233-.439-.173-.77.055-.302.207-.55.457-.745C2.45.097 2.716 0 3 0c.273 0 .5.088.68.265.179.176.238.434.177.771-.06.327-.21.583-.45.767-.24.185-.502.277-.785.277z"/>
</svg>

Before

Width:  |  Height:  |  Size: 530 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10">
<path d="M6 0v2H5a3 3 0 000 6h1v2H5A5 5 0 115 0h1zm2 0h1a5 5 0 110 10H8V8h1a3 3 0 000-6H8V0zM5 4h4a1 1 0 110 2H5a1 1 0 110-2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 199 B

View file

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g transform="translate(1 1.5)" fill-rule="evenodd">
<rect x="6" width="2" height="13" rx="1"/>
<rect x=".5" y="5.5" width="13" height="2" rx="1"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 233 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52">
<path fill="#D76B6B" fill-rule="nonzero" d="M26 52C11.64 52 0 40.36 0 26S11.64 0 26 0s26 11.64 26 26-11.64 26-26 26zm0-3.25c12.564 0 22.75-10.186 22.75-22.75S38.564 3.25 26 3.25 3.25 13.436 3.25 26 13.436 48.75 26 48.75zM15.708 33.042a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm23.834 0a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm-15.875 5.452a1.083 1.083 0 1 1-1.834-1.155c1.331-2.114 3.49-3.179 6.334-3.179 2.844 0 5.002 1.065 6.333 3.18a1.083 1.083 0 1 1-1.833 1.154c-.913-1.45-2.366-2.167-4.5-2.167s-3.587.717-4.5 2.167z"/>
</svg>

Before

Width:  |  Height:  |  Size: 643 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M12.711 2.18a7.448 7.448 0 0 1 .79 9.603l2.143 2.144a1.214 1.214 0 1 1-1.717 1.717L11.783 13.5a7.446 7.446 0 1 1 .928-11.32ZM11.39 3.61a5.5 5.5 0 1 0-7.778 7.78 5.5 5.5 0 0 0 7.778-7.78Z" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View file

@ -1,3 +0,0 @@
<svg width="13" height="13" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 9.294a.792.792 0 01-.562-.232L2.233 5.356a.794.794 0 011.123-1.123L6.5 7.377l3.144-3.144a.794.794 0 011.123 1.123L7.062 9.062a.792.792 0 01-.562.232z"/>
</svg>

Before

Width:  |  Height:  |  Size: 240 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 11">
<path d="M13.073 2.099l-1.448 1.448A3 3 0 009 2H8V0h1c1.68 0 3.166.828 4.073 2.099zM6.929 4l-.879.879L7.172 6H5a1 1 0 110-2h1.929zM6 0v2H5a3 3 0 100 6h1v2H5A5 5 0 115 0h1zm6.414 7l2.122 2.121-1.415 1.415L11 8.414l-2.121 2.122L7.464 9.12 9.586 7 7.464 4.879 8.88 3.464 11 5.586l2.121-2.122 1.415 1.415L12.414 7z"/>
</svg>

Before

Width:  |  Height:  |  Size: 384 B

View file

@ -19,7 +19,6 @@ declare const VERSION: string;
* Short Description (_눈;) * Short Description (_눈;)
* *
* @version 2.18.0 * @version 2.18.0
*
* @license Apache-2.0 * @license Apache-2.0
* @author CodeX-Team <https://ifmo.su> * @author CodeX-Team <https://ifmo.su>
*/ */

View file

@ -14,12 +14,11 @@ export type ModuleNodes = object;
* @abstract * @abstract
* @class Module * @class Module
* @classdesc All modules inherits from this class. * @classdesc All modules inherits from this class.
*
* @typedef {Module} Module * @typedef {Module} Module
* @property {object} config - Editor user settings * @property {object} config - Editor user settings
* @property {EditorModules} Editor - List of Editor modules * @property {EditorModules} Editor - List of Editor modules
*/ */
export default class Module<T extends ModuleNodes = {}> { export default class Module<T extends ModuleNodes = Record<string, HTMLElement>> {
/** /**
* Each module can provide some UI elements that will be stored in this property * Each module can provide some UI elements that will be stored in this property
*/ */
@ -92,8 +91,9 @@ export default class Module<T extends ModuleNodes = {}> {
/** /**
* @class * @class
* * @param options - Module options
* @param {ModuleConfig} - Module config * @param options.config - Module config
* @param options.eventsDispatcher - Common event bus
*/ */
constructor({ config, eventsDispatcher }: ModuleConfig) { constructor({ config, eventsDispatcher }: ModuleConfig) {
if (new.target === Module) { if (new.target === Module) {

View file

@ -1,11 +1,10 @@
/** /**
* @class DeleteTune * @class DeleteTune
* @classdesc Editor's default tune that moves up selected block * @classdesc Editor's default tune that moves up selected block
*
* @copyright <CodeX Team> 2018 * @copyright <CodeX Team> 2018
*/ */
import { API, BlockTune, PopoverItem } from '../../../types'; import { API, BlockTune, PopoverItem } from '../../../types';
import $ from '../dom'; import { IconCross } from '@codexteam/icons';
/** /**
* *
@ -37,22 +36,20 @@ export default class DeleteTune implements BlockTune {
*/ */
public render(): PopoverItem { public render(): PopoverItem {
return { return {
icon: $.svg('cross', 14, 14).outerHTML, icon: IconCross,
label: this.api.i18n.t('Delete'), label: this.api.i18n.t('Delete'),
name: 'delete', name: 'delete',
confirmation: { confirmation: {
label: this.api.i18n.t('Click to delete'), label: this.api.i18n.t('Click to delete'),
onActivate: (item, e): void => this.handleClick(e), onActivate: (): void => this.handleClick(),
}, },
}; };
} }
/** /**
* Delete block conditions passed * Delete block conditions passed
*
* @param {MouseEvent} event - click event
*/ */
public handleClick(event: MouseEvent): void { public handleClick(): void {
this.api.blocks.delete(); this.api.blocks.delete();
} }
} }

View file

@ -1,13 +1,13 @@
/** /**
* @class MoveDownTune * @class MoveDownTune
* @classdesc Editor's default tune - Moves down highlighted block * @classdesc Editor's default tune - Moves down highlighted block
*
* @copyright <CodeX Team> 2018 * @copyright <CodeX Team> 2018
*/ */
import $ from '../dom';
import { API, BlockTune, PopoverItem } from '../../../types'; import { API, BlockTune, PopoverItem } from '../../../types';
import Popover from '../utils/popover'; import Popover from '../utils/popover';
import { IconChevronDown } from '@codexteam/icons';
/** /**
* *
@ -46,7 +46,7 @@ export default class MoveDownTune implements BlockTune {
*/ */
public render(): PopoverItem { public render(): PopoverItem {
return { return {
icon: $.svg('arrow-down', 14, 14).outerHTML, icon: IconChevronDown,
label: this.api.i18n.t('Move down'), label: this.api.i18n.t('Move down'),
onActivate: (item, event): void => this.handleClick(event), onActivate: (item, event): void => this.handleClick(event),
name: 'move-down', name: 'move-down',
@ -72,6 +72,7 @@ export default class MoveDownTune implements BlockTune {
window.setTimeout(() => { window.setTimeout(() => {
button.classList.remove(this.CSS.animation); button.classList.remove(this.CSS.animation);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 500); }, 500);
return; return;

View file

@ -1,12 +1,11 @@
/** /**
* @class MoveUpTune * @class MoveUpTune
* @classdesc Editor's default tune that moves up selected block * @classdesc Editor's default tune that moves up selected block
*
* @copyright <CodeX Team> 2018 * @copyright <CodeX Team> 2018
*/ */
import $ from '../dom'; import { API, BlockTune, PopoverItem } from '../../../types';
import { API, BlockTune, BlockAPI, PopoverItem } from '../../../types';
import Popover from '../../components/utils/popover'; import Popover from '../../components/utils/popover';
import { IconChevronUp } from '@codexteam/icons';
/** /**
* *
@ -45,7 +44,7 @@ export default class MoveUpTune implements BlockTune {
*/ */
public render(): PopoverItem { public render(): PopoverItem {
return { return {
icon: $.svg('arrow-up', 14, 14).outerHTML, icon: IconChevronUp,
label: this.api.i18n.t('Move up'), label: this.api.i18n.t('Move up'),
onActivate: (item, e): void => this.handleClick(e), onActivate: (item, e): void => this.handleClick(e),
name: 'move-up', name: 'move-up',
@ -71,6 +70,7 @@ export default class MoveUpTune implements BlockTune {
window.setTimeout(() => { window.setTimeout(() => {
button.classList.remove(this.CSS.animation); button.classList.remove(this.CSS.animation);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 500); }, 500);
return; return;

View file

@ -7,7 +7,6 @@ import { BlockAPI as BlockAPIInterface } from '../../../types/api';
* Constructs new BlockAPI object * Constructs new BlockAPI object
* *
* @class * @class
*
* @param {Block} block - Block to expose * @param {Block} block - Block to expose
*/ */
function BlockAPI( function BlockAPI(
@ -90,7 +89,6 @@ function BlockAPI(
* *
* @param {string} methodName - method to call * @param {string} methodName - method to call
* @param {object} param - object with parameters * @param {object} param - object with parameters
*
* @returns {unknown} * @returns {unknown}
*/ */
call(methodName: string, param?: object): unknown { call(methodName: string, param?: object): unknown {
@ -110,7 +108,6 @@ function BlockAPI(
* Validate Block data * Validate Block data
* *
* @param {BlockToolData} data - data to validate * @param {BlockToolData} data - data to validate
*
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
validate(data: BlockToolData): Promise<boolean> { validate(data: BlockToolData): Promise<boolean> {

View file

@ -60,10 +60,8 @@ interface BlockConstructorOptions {
/** /**
* @class Block * @class Block
* @classdesc This class describes editor`s block, including block`s HTMLElement, data and tool * @classdesc This class describes editor`s block, including block`s HTMLElement, data and tool
*
* @property {BlockTool} tool current block tool (Paragraph, for example) * @property {BlockTool} tool current block tool (Paragraph, for example)
* @property {object} CSS block`s css classes * @property {object} CSS block`s css classes
*
*/ */
/** /**
@ -74,11 +72,13 @@ export enum BlockToolAPI {
* @todo remove method in 3.0.0 * @todo remove method in 3.0.0
* @deprecated use 'rendered' hook instead * @deprecated use 'rendered' hook instead
*/ */
// eslint-disable-next-line @typescript-eslint/naming-convention
APPEND_CALLBACK = 'appendCallback', APPEND_CALLBACK = 'appendCallback',
RENDERED = 'rendered', RENDERED = 'rendered',
MOVED = 'moved', MOVED = 'moved',
UPDATED = 'updated', UPDATED = 'updated',
REMOVED = 'removed', REMOVED = 'removed',
// eslint-disable-next-line @typescript-eslint/naming-convention
ON_PASTE = 'onPaste', ON_PASTE = 'onPaste',
} }
@ -89,7 +89,6 @@ type BlockEvents = 'didMutated';
/** /**
* @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance * @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance
*
* @property {BlockTool} tool - Tool instance * @property {BlockTool} tool - Tool instance
* @property {HTMLElement} holder - Div element that wraps block content with Tool's content. Has `ce-block` CSS class * @property {HTMLElement} holder - Div element that wraps block content with Tool's content. Has `ce-block` CSS class
* @property {HTMLElement} pluginsContent - HTML content that returns by Tool's render function * @property {HTMLElement} pluginsContent - HTML content that returns by Tool's render function
@ -244,7 +243,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* @param {object} options - block constructor options * @param {object} options - block constructor options
* @param {string} [options.id] - block's id. Will be generated if omitted. * @param {string} [options.id] - block's id. Will be generated if omitted.
* @param {BlockToolData} options.data - Tool's initial data * @param {BlockToolData} options.data - Tool's initial data
* @param {BlockToolConstructable} options.tool block's tool * @param {BlockTool} options.tool block's tool
* @param options.api - Editor API module for pass it to the Block Tunes * @param options.api - Editor API module for pass it to the Block Tunes
* @param {boolean} options.readOnly - Read-Only flag * @param {boolean} options.readOnly - Read-Only flag
*/ */
@ -281,7 +280,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
} }
/** /**
* Find and return all editable elements (contenteditables and native inputs) in the Tool HTML * Find and return all editable elements (contenteditable and native inputs) in the Tool HTML
* *
* @returns {HTMLElement[]} * @returns {HTMLElement[]}
*/ */
@ -396,7 +395,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
/** /**
* is block mergeable * is block mergeable
* We plugin have merge function then we call it mergable * We plugin have merge function then we call it mergeable
* *
* @returns {boolean} * @returns {boolean}
*/ */
@ -417,7 +416,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
} }
/** /**
* Check if block has a media content such as images, iframes and other * Check if block has a media content such as images, iframe and other
* *
* @returns {boolean} * @returns {boolean}
*/ */
@ -487,7 +486,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
/** /**
* Set stretched state * Set stretched state
* *
* @param {boolean} state - 'true' to enable, 'false' to disable stretched statte * @param {boolean} state - 'true' to enable, 'false' to disable stretched state
*/ */
public set stretched(state: boolean) { public set stretched(state: boolean) {
this.holder.classList.toggle(Block.CSS.wrapperStretched, state); this.holder.classList.toggle(Block.CSS.wrapperStretched, state);
@ -619,7 +618,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
}; };
}) })
.catch((error) => { .catch((error) => {
_.log(`Saving proccess for ${this.name} tool failed due to the ${error}`, 'log', 'red'); _.log(`Saving process for ${this.name} tool failed due to the ${error}`, 'log', 'red');
}); });
} }
@ -628,7 +627,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* Tool's validation method is optional * Tool's validation method is optional
* *
* @description Method returns true|false whether data passed the validation or not * @description Method returns true|false whether data passed the validation or not
*
* @param {BlockToolData} data - data to validate * @param {BlockToolData} data - data to validate
* @returns {Promise<boolean>} valid * @returns {Promise<boolean>} valid
*/ */
@ -855,10 +853,10 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* Update current input * Update current input
*/ */
this.updateCurrentInput(); this.updateCurrentInput();
} };
/** /**
* Adds focus event listeners to all inputs and contentEditables * Adds focus event listeners to all inputs and contenteditable
*/ */
private addInputEvents(): void { private addInputEvents(): void {
this.inputs.forEach(input => { this.inputs.forEach(input => {
@ -874,7 +872,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
} }
/** /**
* removes focus event listeners from all inputs and contentEditables * removes focus event listeners from all inputs and contenteditable
*/ */
private removeInputEvents(): void { private removeInputEvents(): void {
this.inputs.forEach(input => { this.inputs.forEach(input => {

View file

@ -6,11 +6,8 @@ import { MoveEvent } from '../../types/tools';
/** /**
* @class Blocks * @class Blocks
* @classdesc Class to work with Block instances array * @classdesc Class to work with Block instances array
*
* @private * @private
*
* @property {HTMLElement} workingArea editor`s working node * @property {HTMLElement} workingArea editor`s working node
*
*/ */
export default class Blocks { export default class Blocks {
/** /**
@ -25,7 +22,6 @@ export default class Blocks {
/** /**
* @class * @class
*
* @param {HTMLElement} workingArea editor`s working node * @param {HTMLElement} workingArea editor`s working node
*/ */
constructor(workingArea: HTMLElement) { constructor(workingArea: HTMLElement) {
@ -65,7 +61,6 @@ export default class Blocks {
* *
* @example * @example
* blocks[0] = new Block(...) * blocks[0] = new Block(...)
*
* @param {Blocks} instance Blocks instance * @param {Blocks} instance Blocks instance
* @param {PropertyKey} property block index or any Blocks class property key to set * @param {PropertyKey} property block index or any Blocks class property key to set
* @param {Block} value value to set * @param {Block} value value to set
@ -257,7 +252,6 @@ export default class Blocks {
* Insert Block after passed target * Insert Block after passed target
* *
* @todo decide if this method is necessary * @todo decide if this method is necessary
*
* @param {Block} targetBlock target after which Block should be inserted * @param {Block} targetBlock target after which Block should be inserted
* @param {Block} newBlock Block to insert * @param {Block} newBlock Block to insert
*/ */

View file

@ -30,12 +30,9 @@ contextRequire.keys().forEach((filename) => {
/** /**
* @class Core * @class Core
*
* @classdesc Editor.js core class * @classdesc Editor.js core class
*
* @property {EditorConfig} config - all settings * @property {EditorConfig} config - all settings
* @property {EditorModules} moduleInstances - constructed editor components * @property {EditorModules} moduleInstances - constructed editor components
*
* @type {Core} * @type {Core}
*/ */
export default class Core { export default class Core {
@ -61,7 +58,6 @@ export default class Core {
/** /**
* @param {EditorConfig} config - user configuration * @param {EditorConfig} config - user configuration
*
*/ */
constructor(config?: EditorConfig|string) { constructor(config?: EditorConfig|string) {
/** /**
@ -103,6 +99,7 @@ export default class Core {
* Resolve this.isReady promise * Resolve this.isReady promise
*/ */
onReady(); onReady();
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 500); }, 500);
}) })
.catch((error) => { .catch((error) => {
@ -173,6 +170,7 @@ export default class Core {
* *
* @type {number} * @type {number}
*/ */
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
this.config.minHeight = this.config.minHeight !== undefined ? this.config.minHeight : 300; this.config.minHeight = this.config.minHeight !== undefined ? this.config.minHeight : 300;
/** /**

View file

@ -45,15 +45,14 @@ export default class Dom {
} }
/** /**
* Helper for making Elements with classname and attributes * Helper for making Elements with class name and attributes
* *
* @param {string} tagName - new Element tag name * @param {string} tagName - new Element tag name
* @param {string[]|string} [classNames] - list or name of CSS classname(s) * @param {string[]|string} [classNames] - list or name of CSS class name(s)
* @param {object} [attributes] - any attributes * @param {object} [attributes] - any attributes
*
* @returns {HTMLElement} * @returns {HTMLElement}
*/ */
public static make(tagName: string, classNames: string|string[] = null, attributes: object = {}): HTMLElement { public static make(tagName: string, classNames: string | string[] = null, attributes: object = {}): HTMLElement {
const el = document.createElement(tagName); const el = document.createElement(tagName);
if (Array.isArray(classNames)) { if (Array.isArray(classNames)) {
@ -75,33 +74,12 @@ export default class Dom {
* Creates Text Node with the passed content * Creates Text Node with the passed content
* *
* @param {string} content - text content * @param {string} content - text content
*
* @returns {Text} * @returns {Text}
*/ */
public static text(content: string): Text { public static text(content: string): Text {
return document.createTextNode(content); return document.createTextNode(content);
} }
/**
* Creates SVG icon linked to the sprite
*
* @param {string} name - name (id) of icon from sprite
* @param {number} [width] - icon width
* @param {number} [height] - icon height
*
* @returns {SVGElement}
*/
public static svg(name: string, width = 14, height = 14): SVGElement {
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
icon.classList.add('icon', 'icon--' + name);
icon.setAttribute('width', width + 'px');
icon.setAttribute('height', height + 'px');
icon.innerHTML = `<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#${name}"></use>`;
return icon;
}
/** /**
* Append one or several elements to the parent * Append one or several elements to the parent
* *
@ -109,8 +87,8 @@ export default class Dom {
* @param {Element|Element[]|DocumentFragment|Text|Text[]} elements - element or elements list * @param {Element|Element[]|DocumentFragment|Text|Text[]} elements - element or elements list
*/ */
public static append( public static append(
parent: Element|DocumentFragment, parent: Element | DocumentFragment,
elements: Element|Element[]|DocumentFragment|Text|Text[] elements: Element | Element[] | DocumentFragment | Text | Text[]
): void { ): void {
if (Array.isArray(elements)) { if (Array.isArray(elements)) {
elements.forEach((el) => parent.appendChild(el)); elements.forEach((el) => parent.appendChild(el));
@ -125,7 +103,7 @@ export default class Dom {
* @param {Element} parent - where to append * @param {Element} parent - where to append
* @param {Element|Element[]} elements - element or elements list * @param {Element|Element[]} elements - element or elements list
*/ */
public static prepend(parent: Element, elements: Element|Element[]): void { public static prepend(parent: Element, elements: Element | Element[]): void {
if (Array.isArray(elements)) { if (Array.isArray(elements)) {
elements = elements.reverse(); elements = elements.reverse();
elements.forEach((el) => parent.prepend(el)); elements.forEach((el) => parent.prepend(el));
@ -165,10 +143,9 @@ export default class Dom {
* *
* @param {Element} el - element we searching inside. Default - DOM Document * @param {Element} el - element we searching inside. Default - DOM Document
* @param {string} selector - searching string * @param {string} selector - searching string
*
* @returns {Element} * @returns {Element}
*/ */
public static find(el: Element|Document = document, selector: string): Element { public static find(el: Element | Document = document, selector: string): Element {
return el.querySelector(selector); return el.querySelector(selector);
} }
@ -189,10 +166,9 @@ export default class Dom {
* *
* @param {Element|Document} el - element we searching inside. Default - DOM Document * @param {Element|Document} el - element we searching inside. Default - DOM Document
* @param {string} selector - searching string * @param {string} selector - searching string
*
* @returns {NodeList} * @returns {NodeList}
*/ */
public static findAll(el: Element|Document = document, selector: string): NodeList { public static findAll(el: Element | Document = document, selector: string): NodeList {
return el.querySelectorAll(selector); return el.querySelectorAll(selector);
} }
@ -207,7 +183,7 @@ export default class Dom {
} }
/** /**
* Find all contendeditable, textarea and editable input elements passed holder contains * Find all contenteditable, textarea and editable input elements passed holder contains
* *
* @param holder - element where to find inputs * @param holder - element where to find inputs
*/ */
@ -230,11 +206,9 @@ export default class Dom {
* Leaf is the vertex that doesn't have any child nodes * Leaf is the vertex that doesn't have any child nodes
* *
* @description Method recursively goes throw the all Node until it finds the Leaf * @description Method recursively goes throw the all Node until it finds the Leaf
*
* @param {Node} node - root Node. From this vertex we start Deep-first search * @param {Node} node - root Node. From this vertex we start Deep-first search
* {@link https://en.wikipedia.org/wiki/Depth-first_search} * {@link https://en.wikipedia.org/wiki/Depth-first_search}
* @param {boolean} [atLast] - find last text node * @param {boolean} [atLast] - find last text node
*
* @returns {Node} - it can be text Node or Element Node, so that caret will able to work with it * @returns {Node} - it can be text Node or Element Node, so that caret will able to work with it
*/ */
public static getDeepestNode(node: Node, atLast = false): Node { public static getDeepestNode(node: Node, atLast = false): Node {
@ -287,7 +261,6 @@ export default class Dom {
* Check if object is DOM node * Check if object is DOM node
* *
* @param {*} node - object to check * @param {*} node - object to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -318,7 +291,6 @@ export default class Dom {
* Check if passed element is contenteditable * Check if passed element is contenteditable
* *
* @param {HTMLElement} element - html element to check * @param {HTMLElement} element - html element to check
*
* @returns {boolean} * @returns {boolean}
*/ */
public static isContentEditable(element: HTMLElement): boolean { public static isContentEditable(element: HTMLElement): boolean {
@ -329,7 +301,6 @@ export default class Dom {
* Checks target if it is native input * Checks target if it is native input
* *
* @param {*} target - HTML element or string * @param {*} target - HTML element or string
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -346,7 +317,6 @@ export default class Dom {
* Checks if we can set caret * Checks if we can set caret
* *
* @param {HTMLElement} target - target to check * @param {HTMLElement} target - target to check
*
* @returns {boolean} * @returns {boolean}
*/ */
public static canSetCaret(target: HTMLElement): boolean { public static canSetCaret(target: HTMLElement): boolean {
@ -377,9 +347,7 @@ export default class Dom {
* *
* @description Method checks simple Node without any childs for emptiness * @description Method checks simple Node without any childs for emptiness
* If you have Node with 2 or more children id depth, you better use {@link Dom#isEmpty} method * If you have Node with 2 or more children id depth, you better use {@link Dom#isEmpty} method
*
* @param {Node} node - node to check * @param {Node} node - node to check
*
* @returns {boolean} true if it is empty * @returns {boolean} true if it is empty
*/ */
public static isNodeEmpty(node: Node): boolean { public static isNodeEmpty(node: Node): boolean {
@ -402,7 +370,6 @@ export default class Dom {
* checks node if it is doesn't have any child nodes * checks node if it is doesn't have any child nodes
* *
* @param {Node} node - node to check * @param {Node} node - node to check
*
* @returns {boolean} * @returns {boolean}
*/ */
public static isLeaf(node: Node): boolean { public static isLeaf(node: Node): boolean {
@ -418,7 +385,6 @@ export default class Dom {
* {@link https://en.wikipedia.org/wiki/Breadth-first_search} * {@link https://en.wikipedia.org/wiki/Breadth-first_search}
* *
* @description Pushes to stack all DOM leafs and checks for emptiness * @description Pushes to stack all DOM leafs and checks for emptiness
*
* @param {Node} node - node to check * @param {Node} node - node to check
* @returns {boolean} * @returns {boolean}
*/ */
@ -453,7 +419,6 @@ export default class Dom {
* Check if string contains html elements * Check if string contains html elements
* *
* @param {string} str - string to check * @param {string} str - string to check
*
* @returns {boolean} * @returns {boolean}
*/ */
public static isHTMLString(str: string): boolean { public static isHTMLString(str: string): boolean {
@ -468,7 +433,6 @@ export default class Dom {
* Return length of node`s text content * Return length of node`s text content
* *
* @param {Node} node - node with content * @param {Node} node - node with content
*
* @returns {number} * @returns {number}
*/ */
public static getContentLength(node: Node): number { public static getContentLength(node: Node): number {
@ -523,6 +487,8 @@ export default class Dom {
'ruby', 'ruby',
'section', 'section',
'table', 'table',
'tbody',
'thead',
'tr', 'tr',
'tfoot', 'tfoot',
'ul', 'ul',
@ -534,7 +500,6 @@ export default class Dom {
* Check if passed content includes only inline elements * Check if passed content includes only inline elements
* *
* @param {string|HTMLElement} data - element or html string * @param {string|HTMLElement} data - element or html string
*
* @returns {boolean} * @returns {boolean}
*/ */
public static containsOnlyInlineElements(data: string | HTMLElement): boolean { public static containsOnlyInlineElements(data: string | HTMLElement): boolean {
@ -559,7 +524,6 @@ export default class Dom {
* Find and return all block elements in the passed parent (including subtree) * Find and return all block elements in the passed parent (including subtree)
* *
* @param {HTMLElement} parent - root element * @param {HTMLElement} parent - root element
*
* @returns {HTMLElement[]} * @returns {HTMLElement[]}
*/ */
public static getDeepestBlockElements(parent: HTMLElement): HTMLElement[] { public static getDeepestBlockElements(parent: HTMLElement): HTMLElement[] {
@ -576,7 +540,6 @@ export default class Dom {
* Helper for get holder from {string} or return HTMLElement * Helper for get holder from {string} or return HTMLElement
* *
* @param {string | HTMLElement} element - holder's id or holder's HTML Element * @param {string | HTMLElement} element - holder's id or holder's HTML Element
*
* @returns {HTMLElement} * @returns {HTMLElement}
*/ */
public static getHolder(element: string | HTMLElement): HTMLElement { public static getHolder(element: string | HTMLElement): HTMLElement {
@ -591,7 +554,6 @@ export default class Dom {
* Method checks passed Node if it is some extension Node * Method checks passed Node if it is some extension Node
* *
* @param {Node} node - any node * @param {Node} node - any node
*
* @returns {boolean} * @returns {boolean}
*/ */
public static isExtensionNode(node: Node): boolean { public static isExtensionNode(node: Node): boolean {
@ -606,7 +568,6 @@ export default class Dom {
* Returns true if element is anchor (is A tag) * Returns true if element is anchor (is A tag)
* *
* @param {Element} element - element to check * @param {Element} element - element to check
*
* @returns {boolean} * @returns {boolean}
*/ */
public static isAnchor(element: Element): element is HTMLAnchorElement { public static isAnchor(element: Element): element is HTMLAnchorElement {
@ -619,7 +580,7 @@ export default class Dom {
* @todo handle case when editor initialized in scrollable popup * @todo handle case when editor initialized in scrollable popup
* @param el - element to compute offset * @param el - element to compute offset
*/ */
public static offset(el): {top: number; left: number; right: number; bottom: number} { public static offset(el): { top: number; left: number; right: number; bottom: number } {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

View file

@ -175,6 +175,7 @@ export default class DomIterator {
/** /**
* Focus input with micro-delay to ensure DOM is updated * Focus input with micro-delay to ensure DOM is updated
*/ */
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
_.delay(() => SelectionUtils.setCursor(this.items[focusedButtonIndex]), 50)(); _.delay(() => SelectionUtils.setCursor(this.items[focusedButtonIndex]), 50)();
} }

View file

@ -74,7 +74,7 @@ export default class Flipper {
/** /**
* Contains list of callbacks to be executed on each flip * Contains list of callbacks to be executed on each flip
*/ */
private flipCallbacks: Array<() => void> = [] private flipCallbacks: Array<() => void> = [];
/** /**
* @param {FlipperOptions} options - different constructing settings * @param {FlipperOptions} options - different constructing settings
@ -279,15 +279,17 @@ export default class Flipper {
} }
if (this.iterator.currentItem) { if (this.iterator.currentItem) {
/**
* Stop Enter propagation only if flipper is ready to select focused item
*/
event.stopPropagation();
event.preventDefault();
this.iterator.currentItem.click(); this.iterator.currentItem.click();
} }
if (_.isFunction(this.activateCallback)) { if (_.isFunction(this.activateCallback)) {
this.activateCallback(this.iterator.currentItem); this.activateCallback(this.iterator.currentItem);
} }
event.preventDefault();
event.stopPropagation();
} }
/** /**

View file

@ -21,7 +21,6 @@ export default class I18n {
* Perform translation of the string by namespace and a key * Perform translation of the string by namespace and a key
* *
* @example I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune') * @example I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune')
*
* @param internalNamespace - path to translated string in dictionary * @param internalNamespace - path to translated string in dictionary
* @param dictKey - dictionary key. Better to use default locale original text * @param dictKey - dictionary key. Better to use default locale original text
*/ */

View file

@ -1,5 +1,5 @@
import $ from '../dom';
import { InlineTool, SanitizerConfig } from '../../../types'; import { InlineTool, SanitizerConfig } from '../../../types';
import { IconBold } from '@codexteam/icons';
/** /**
* Bold Tool * Bold Tool
@ -61,28 +61,24 @@ export default class BoldInlineTool implements InlineTool {
this.nodes.button = document.createElement('button') as HTMLButtonElement; this.nodes.button = document.createElement('button') as HTMLButtonElement;
this.nodes.button.type = 'button'; this.nodes.button.type = 'button';
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier); this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
this.nodes.button.appendChild($.svg('bold', 12, 14)); this.nodes.button.innerHTML = IconBold;
return this.nodes.button; return this.nodes.button;
} }
/** /**
* Wrap range with <b> tag * Wrap range with <b> tag
*
* @param {Range} range - range to wrap
*/ */
public surround(range: Range): void { public surround(): void {
document.execCommand(this.commandName); document.execCommand(this.commandName);
} }
/** /**
* Check selection and set activated state to button if there are <b> tag * Check selection and set activated state to button if there are <b> tag
* *
* @param {Selection} selection - selection to check
*
* @returns {boolean} * @returns {boolean}
*/ */
public checkState(selection: Selection): boolean { public checkState(): boolean {
const isActive = document.queryCommandState(this.commandName); const isActive = document.queryCommandState(this.commandName);
this.nodes.button.classList.toggle(this.CSS.buttonActive, isActive); this.nodes.button.classList.toggle(this.CSS.buttonActive, isActive);

View file

@ -1,5 +1,5 @@
import $ from '../dom';
import { InlineTool, SanitizerConfig } from '../../../types'; import { InlineTool, SanitizerConfig } from '../../../types';
import { IconItalic } from '@codexteam/icons';
/** /**
* Italic Tool * Italic Tool
@ -61,26 +61,22 @@ export default class ItalicInlineTool implements InlineTool {
this.nodes.button = document.createElement('button') as HTMLButtonElement; this.nodes.button = document.createElement('button') as HTMLButtonElement;
this.nodes.button.type = 'button'; this.nodes.button.type = 'button';
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier); this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
this.nodes.button.appendChild($.svg('italic', 4, 11)); this.nodes.button.innerHTML = IconItalic;
return this.nodes.button; return this.nodes.button;
} }
/** /**
* Wrap range with <i> tag * Wrap range with <i> tag
*
* @param {Range} range - range to wrap
*/ */
public surround(range: Range): void { public surround(): void {
document.execCommand(this.commandName); document.execCommand(this.commandName);
} }
/** /**
* Check selection and set activated state to button if there are <i> tag * Check selection and set activated state to button if there are <i> tag
*
* @param {Selection} selection - selection to check
*/ */
public checkState(selection: Selection): boolean { public checkState(): boolean {
const isActive = document.queryCommandState(this.commandName); const isActive = document.queryCommandState(this.commandName);
this.nodes.button.classList.toggle(this.CSS.buttonActive, isActive); this.nodes.button.classList.toggle(this.CSS.buttonActive, isActive);

View file

@ -1,9 +1,8 @@
import SelectionUtils from '../selection'; import SelectionUtils from '../selection';
import $ from '../dom';
import * as _ from '../utils'; import * as _ from '../utils';
import { InlineTool, SanitizerConfig } from '../../../types'; import { InlineTool, SanitizerConfig, API } from '../../../types';
import { Notifier, Toolbar, I18n } from '../../../types/api'; import { Notifier, Toolbar, I18n, InlineToolbar } from '../../../types/api';
import { IconLink, IconUnlink } from '@codexteam/icons';
/** /**
* Link Tool * Link Tool
@ -71,9 +70,9 @@ export default class LinkInlineTool implements InlineTool {
button: HTMLButtonElement; button: HTMLButtonElement;
input: HTMLInputElement; input: HTMLInputElement;
} = { } = {
button: null, button: null,
input: null, input: null,
}; };
/** /**
* SelectionUtils instance * SelectionUtils instance
@ -93,7 +92,7 @@ export default class LinkInlineTool implements InlineTool {
/** /**
* Available inline toolbar methods (open/close) * Available inline toolbar methods (open/close)
*/ */
private inlineToolbar: Toolbar; private inlineToolbar: InlineToolbar;
/** /**
* Notifier API methods * Notifier API methods
@ -106,9 +105,9 @@ export default class LinkInlineTool implements InlineTool {
private i18n: I18n; private i18n: I18n;
/** /**
* @param {API} api - Editor.js API * @param api - Editor.js API
*/ */
constructor({ api }) { constructor({ api }: { api: API }) {
this.toolbar = api.toolbar; this.toolbar = api.toolbar;
this.inlineToolbar = api.inlineToolbar; this.inlineToolbar = api.inlineToolbar;
this.notifier = api.notifier; this.notifier = api.notifier;
@ -123,8 +122,8 @@ export default class LinkInlineTool implements InlineTool {
this.nodes.button = document.createElement('button') as HTMLButtonElement; this.nodes.button = document.createElement('button') as HTMLButtonElement;
this.nodes.button.type = 'button'; this.nodes.button.type = 'button';
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier); this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
this.nodes.button.appendChild($.svg('link', 14, 10));
this.nodes.button.appendChild($.svg('unlink', 15, 11)); this.nodes.button.innerHTML = IconLink;
return this.nodes.button; return this.nodes.button;
} }
@ -187,13 +186,12 @@ export default class LinkInlineTool implements InlineTool {
/** /**
* Check selection and set activated state to button if there are <a> tag * Check selection and set activated state to button if there are <a> tag
*
* @param {Selection} selection - selection to check
*/ */
public checkState(selection?: Selection): boolean { public checkState(): boolean {
const anchorTag = this.selection.findParentTag('A'); const anchorTag = this.selection.findParentTag('A');
if (anchorTag) { if (anchorTag) {
this.nodes.button.innerHTML = IconUnlink;
this.nodes.button.classList.add(this.CSS.buttonUnlink); this.nodes.button.classList.add(this.CSS.buttonUnlink);
this.nodes.button.classList.add(this.CSS.buttonActive); this.nodes.button.classList.add(this.CSS.buttonActive);
this.openActions(); this.openActions();
@ -207,6 +205,7 @@ export default class LinkInlineTool implements InlineTool {
this.selection.save(); this.selection.save();
} else { } else {
this.nodes.button.innerHTML = IconLink;
this.nodes.button.classList.remove(this.CSS.buttonUnlink); this.nodes.button.classList.remove(this.CSS.buttonUnlink);
this.nodes.button.classList.remove(this.CSS.buttonActive); this.nodes.button.classList.remove(this.CSS.buttonActive);
} }

View file

@ -58,7 +58,6 @@ export default class BlocksAPI extends Module {
* Returns the index of Block by id; * Returns the index of Block by id;
* *
* @param id - block id * @param id - block id
* @returns {number}
*/ */
public getBlockIndex(id: string): number | undefined { public getBlockIndex(id: string): number | undefined {
const block = this.Editor.BlockManager.getBlockById(id); const block = this.Editor.BlockManager.getBlockById(id);
@ -201,7 +200,6 @@ export default class BlocksAPI extends Module {
* *
* @param {number} index - index of Block to stretch * @param {number} index - index of Block to stretch
* @param {boolean} status - true to enable, false to disable * @param {boolean} status - true to enable, false to disable
*
* @deprecated Use BlockAPI interface to stretch Blocks * @deprecated Use BlockAPI interface to stretch Blocks
*/ */
public stretchBlock(index: number, status = true): void { public stretchBlock(index: number, status = true): void {
@ -229,16 +227,20 @@ export default class BlocksAPI extends Module {
* @param {number?} index index where to insert new Block * @param {number?} index index where to insert new Block
* @param {boolean?} needToFocus - flag to focus inserted Block * @param {boolean?} needToFocus - flag to focus inserted Block
* @param replace - pass true to replace the Block existed under passed index * @param replace - pass true to replace the Block existed under passed index
* @param {string} id An optional id for the new block. If omitted then the new id will be generated
*/ */
public insert = ( public insert = (
type: string = this.config.defaultBlock, type: string = this.config.defaultBlock,
data: BlockToolData = {}, data: BlockToolData = {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
config: ToolConfig = {}, config: ToolConfig = {},
index?: number, index?: number,
needToFocus?: boolean, needToFocus?: boolean,
replace?: boolean replace?: boolean,
id?: string
): BlockAPIInterface => { ): BlockAPIInterface => {
const insertedBlock = this.Editor.BlockManager.insert({ const insertedBlock = this.Editor.BlockManager.insert({
id,
tool: type, tool: type,
data, data,
index, index,
@ -247,7 +249,7 @@ export default class BlocksAPI extends Module {
}); });
return new BlockAPI(insertedBlock); return new BlockAPI(insertedBlock);
} };
/** /**
* Creates data of an empty block with a passed type. * Creates data of an empty block with a passed type.
@ -265,14 +267,13 @@ export default class BlocksAPI extends Module {
}); });
return block.data; return block.data;
} };
/** /**
* Insert new Block * Insert new Block
* After set caret to this Block * After set caret to this Block
* *
* @todo remove in 3.0.0 * @todo remove in 3.0.0
*
* @deprecated with insert() method * @deprecated with insert() method
*/ */
public insertNewBlock(): void { public insertNewBlock(): void {
@ -307,5 +308,5 @@ export default class BlocksAPI extends Module {
replace: true, replace: true,
tunes: block.tunes, tunes: block.tunes,
}); });
} };
} }

View file

@ -27,7 +27,6 @@ export default class CaretAPI extends Module {
* *
* @param {string} position - position where to set caret * @param {string} position - position where to set caret
* @param {number} offset - caret offset * @param {number} offset - caret offset
*
* @returns {boolean} * @returns {boolean}
*/ */
private setToFirstBlock = (position: string = this.Editor.Caret.positions.DEFAULT, offset = 0): boolean => { private setToFirstBlock = (position: string = this.Editor.Caret.positions.DEFAULT, offset = 0): boolean => {
@ -38,14 +37,13 @@ export default class CaretAPI extends Module {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.firstBlock, position, offset); this.Editor.Caret.setToBlock(this.Editor.BlockManager.firstBlock, position, offset);
return true; return true;
} };
/** /**
* Sets caret to the last Block * Sets caret to the last Block
* *
* @param {string} position - position where to set caret * @param {string} position - position where to set caret
* @param {number} offset - caret offset * @param {number} offset - caret offset
*
* @returns {boolean} * @returns {boolean}
*/ */
private setToLastBlock = (position: string = this.Editor.Caret.positions.DEFAULT, offset = 0): boolean => { private setToLastBlock = (position: string = this.Editor.Caret.positions.DEFAULT, offset = 0): boolean => {
@ -56,14 +54,13 @@ export default class CaretAPI extends Module {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.lastBlock, position, offset); this.Editor.Caret.setToBlock(this.Editor.BlockManager.lastBlock, position, offset);
return true; return true;
} };
/** /**
* Sets caret to the previous Block * Sets caret to the previous Block
* *
* @param {string} position - position where to set caret * @param {string} position - position where to set caret
* @param {number} offset - caret offset * @param {number} offset - caret offset
*
* @returns {boolean} * @returns {boolean}
*/ */
private setToPreviousBlock = ( private setToPreviousBlock = (
@ -77,14 +74,13 @@ export default class CaretAPI extends Module {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.previousBlock, position, offset); this.Editor.Caret.setToBlock(this.Editor.BlockManager.previousBlock, position, offset);
return true; return true;
} };
/** /**
* Sets caret to the next Block * Sets caret to the next Block
* *
* @param {string} position - position where to set caret * @param {string} position - position where to set caret
* @param {number} offset - caret offset * @param {number} offset - caret offset
*
* @returns {boolean} * @returns {boolean}
*/ */
private setToNextBlock = (position: string = this.Editor.Caret.positions.DEFAULT, offset = 0): boolean => { private setToNextBlock = (position: string = this.Editor.Caret.positions.DEFAULT, offset = 0): boolean => {
@ -95,7 +91,7 @@ export default class CaretAPI extends Module {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.nextBlock, position, offset); this.Editor.Caret.setToBlock(this.Editor.BlockManager.nextBlock, position, offset);
return true; return true;
} };
/** /**
* Sets caret to the Block by passed index * Sets caret to the Block by passed index
@ -103,7 +99,6 @@ export default class CaretAPI extends Module {
* @param {number} index - index of Block where to set caret * @param {number} index - index of Block where to set caret
* @param {string} position - position where to set caret * @param {string} position - position where to set caret
* @param {number} offset - caret offset * @param {number} offset - caret offset
*
* @returns {boolean} * @returns {boolean}
*/ */
private setToBlock = ( private setToBlock = (
@ -118,13 +113,12 @@ export default class CaretAPI extends Module {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.blocks[index], position, offset); this.Editor.Caret.setToBlock(this.Editor.BlockManager.blocks[index], position, offset);
return true; return true;
} };
/** /**
* Sets caret to the Editor * Sets caret to the Editor
* *
* @param {boolean} atEnd - if true, set Caret to the end of the Editor * @param {boolean} atEnd - if true, set Caret to the end of the Editor
*
* @returns {boolean} * @returns {boolean}
*/ */
private focus = (atEnd = false): boolean => { private focus = (atEnd = false): boolean => {
@ -133,5 +127,5 @@ export default class CaretAPI extends Module {
} }
return this.setToFirstBlock(this.Editor.Caret.positions.START); return this.setToFirstBlock(this.Editor.Caret.positions.START);
} };
} }

View file

@ -1,4 +1,3 @@
import EventsDispatcher from '../../utils/events';
import { Notifier as INotifier } from '../../../../types/api'; import { Notifier as INotifier } from '../../../../types/api';
import Notifier from '../../utils/notifier'; import Notifier from '../../utils/notifier';
import { ConfirmNotifierOptions, NotifierOptions, PromptNotifierOptions } from 'codex-notifier'; import { ConfirmNotifierOptions, NotifierOptions, PromptNotifierOptions } from 'codex-notifier';
@ -15,10 +14,9 @@ export default class NotifierAPI extends Module {
private notifier: Notifier; private notifier: Notifier;
/** /**
* @class * @param moduleConfiguration - Module Configuration
* @param {object} moduleConfiguration - Module Configuration * @param moduleConfiguration.config - Editor's config
* @param {EditorConfig} moduleConfiguration.config - Editor's config * @param moduleConfiguration.eventsDispatcher - Editor's event dispatcher
* @param {EventsDispatcher} moduleConfiguration.eventsDispatcher - Editor's event dispatcher
*/ */
constructor({ config, eventsDispatcher }: ModuleConfig) { constructor({ config, eventsDispatcher }: ModuleConfig) {
super({ super({

View file

@ -25,7 +25,6 @@ export default class ReadOnlyAPI extends Module {
* Set or toggle read-only state * Set or toggle read-only state
* *
* @param {boolean|undefined} state - set or toggle state * @param {boolean|undefined} state - set or toggle state
*
* @returns {boolean} current value * @returns {boolean} current value
*/ */
public toggle(state?: boolean): Promise<boolean> { public toggle(state?: boolean): Promise<boolean> {

View file

@ -24,7 +24,6 @@ export default class SanitizerAPI extends Module {
* *
* @param {string} taintString - what to sanitize * @param {string} taintString - what to sanitize
* @param {SanitizerConfig} config - sanitizer config * @param {SanitizerConfig} config - sanitizer config
*
* @returns {string} * @returns {string}
*/ */
public clean(taintString: string, config: SanitizerConfig): string { public clean(taintString: string, config: SanitizerConfig): string {

View file

@ -24,7 +24,6 @@ export default class SelectionAPI extends Module {
* *
* @param {string} tagName - tag to find * @param {string} tagName - tag to find
* @param {string} className - tag's class name * @param {string} className - tag's class name
*
* @returns {HTMLElement|null} * @returns {HTMLElement|null}
*/ */
public findParentTag(tagName: string, className?: string): HTMLElement | null { public findParentTag(tagName: string, className?: string): HTMLElement | null {

View file

@ -1,5 +1,5 @@
/** /**
* Contains keyboard and mouse events binded on each Block by Block Manager * Contains keyboard and mouse events bound on each Block by Block Manager
*/ */
import Module from '../__module'; import Module from '../__module';
import * as _ from '../utils'; import * as _ from '../utils';
@ -233,7 +233,7 @@ export default class BlockEvents extends Module {
} }
/** /**
* Allow to create linebreaks by Shift+Enter * Allow to create line breaks by Shift+Enter
*/ */
if (event.shiftKey) { if (event.shiftKey) {
return; return;
@ -424,6 +424,7 @@ export default class BlockEvents extends Module {
if (this.Editor.BlockManager.currentBlock) { if (this.Editor.BlockManager.currentBlock) {
this.Editor.BlockManager.currentBlock.updateCurrentInput(); this.Editor.BlockManager.currentBlock.updateCurrentInput();
} }
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 20)(); }, 20)();
} }
@ -482,6 +483,7 @@ export default class BlockEvents extends Module {
if (this.Editor.BlockManager.currentBlock) { if (this.Editor.BlockManager.currentBlock) {
this.Editor.BlockManager.currentBlock.updateCurrentInput(); this.Editor.BlockManager.currentBlock.updateCurrentInput();
} }
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 20)(); }, 20)();
} }

View file

@ -1,9 +1,7 @@
/** /**
* @class BlockManager * @class BlockManager
* @classdesc Manage editor`s blocks storage and appearance * @classdesc Manage editor`s blocks storage and appearance
*
* @module BlockManager * @module BlockManager
*
* @version 2.0.0 * @version 2.0.0
*/ */
import Block, { BlockToolAPI } from '../block'; import Block, { BlockToolAPI } from '../block';
@ -184,9 +182,7 @@ export default class BlockManager extends Module {
* this._blocks[0] = new Block(...); * this._blocks[0] = new Block(...);
* *
* block = this._blocks[0]; * block = this._blocks[0];
*
* @todo proxy the enumerate method * @todo proxy the enumerate method
*
* @type {Proxy} * @type {Proxy}
* @private * @private
*/ */
@ -229,7 +225,6 @@ export default class BlockManager extends Module {
* @param {string} options.tool - tools passed in editor config {@link EditorConfig#tools} * @param {string} options.tool - tools passed in editor config {@link EditorConfig#tools}
* @param {string} [options.id] - unique id for this block * @param {string} [options.id] - unique id for this block
* @param {BlockToolData} [options.data] - constructor params * @param {BlockToolData} [options.data] - constructor params
*
* @returns {Block} * @returns {Block}
*/ */
public composeBlock({ public composeBlock({
@ -266,7 +261,6 @@ export default class BlockManager extends Module {
* @param {number} [options.index] - index where to insert new Block * @param {number} [options.index] - index where to insert new Block
* @param {boolean} [options.needToFocus] - flag shows if needed to update current Block index * @param {boolean} [options.needToFocus] - flag shows if needed to update current Block index
* @param {boolean} [options.replace] - flag shows if block by passed index should be replaced with inserted one * @param {boolean} [options.replace] - flag shows if block by passed index should be replaced with inserted one
*
* @returns {Block} * @returns {Block}
*/ */
public insert({ public insert({
@ -333,7 +327,6 @@ export default class BlockManager extends Module {
* @param {object} options - replace options * @param {object} options - replace options
* @param {string} options.tool plugin name * @param {string} options.tool plugin name
* @param {BlockToolData} options.data plugin data * @param {BlockToolData} options.data plugin data
*
* @returns {Block} * @returns {Block}
*/ */
public replace({ public replace({
@ -381,7 +374,6 @@ export default class BlockManager extends Module {
* @param {boolean} needToFocus - if true, updates current Block index * @param {boolean} needToFocus - if true, updates current Block index
* *
* TODO: Remove method and use insert() with index instead (?) * TODO: Remove method and use insert() with index instead (?)
*
* @returns {Block} inserted Block * @returns {Block} inserted Block
*/ */
public insertDefaultBlockAtIndex(index: number, needToFocus = false): Block { public insertDefaultBlockAtIndex(index: number, needToFocus = false): Block {
@ -427,7 +419,6 @@ export default class BlockManager extends Module {
* *
* @param {Block} targetBlock - previous block will be append to this block * @param {Block} targetBlock - previous block will be append to this block
* @param {Block} blockToMerge - block that will be merged with target block * @param {Block} blockToMerge - block that will be merged with target block
*
* @returns {Promise} - the sequence that can be continued * @returns {Promise} - the sequence that can be continued
*/ */
public async mergeBlocks(targetBlock: Block, blockToMerge: Block): Promise<void> { public async mergeBlocks(targetBlock: Block, blockToMerge: Block): Promise<void> {
@ -559,7 +550,6 @@ export default class BlockManager extends Module {
* Returns Block by passed index * Returns Block by passed index
* *
* @param {number} index - index to get. -1 to get last * @param {number} index - index to get. -1 to get last
*
* @returns {Block} * @returns {Block}
*/ */
public getBlockByIndex(index): Block { public getBlockByIndex(index): Block {
@ -583,7 +573,6 @@ export default class BlockManager extends Module {
* Returns the Block by passed id * Returns the Block by passed id
* *
* @param id - id of block to get * @param id - id of block to get
*
* @returns {Block} * @returns {Block}
*/ */
public getBlockById(id): Block | undefined { public getBlockById(id): Block | undefined {
@ -594,8 +583,6 @@ export default class BlockManager extends Module {
* Get Block instance by html element * Get Block instance by html element
* *
* @param {Node} element - html element to get Block by * @param {Node} element - html element to get Block by
*
* @returns {Block}
*/ */
public getBlock(element: HTMLElement): Block { public getBlock(element: HTMLElement): Block {
if (!$.isElement(element) as boolean) { if (!$.isElement(element) as boolean) {
@ -690,7 +677,6 @@ export default class BlockManager extends Module {
* Return block which contents passed node * Return block which contents passed node
* *
* @param {Node} childNode - node to get Block by * @param {Node} childNode - node to get Block by
*
* @returns {Block} * @returns {Block}
*/ */
public getBlockByChildNode(childNode: Node): Block { public getBlockByChildNode(childNode: Node): Block {
@ -711,7 +697,6 @@ export default class BlockManager extends Module {
* *
* @param {number} fromIndex - index of first block * @param {number} fromIndex - index of first block
* @param {number} toIndex - index of second block * @param {number} toIndex - index of second block
*
* @deprecated use 'move' instead * @deprecated use 'move' instead
*/ */
public swap(fromIndex, toIndex): void { public swap(fromIndex, toIndex): void {
@ -855,7 +840,6 @@ export default class BlockManager extends Module {
* Validates that the given index is not lower than 0 or higher than the amount of blocks * Validates that the given index is not lower than 0 or higher than the amount of blocks
* *
* @param {number} index - index of blocks array to validate * @param {number} index - index of blocks array to validate
*
* @returns {boolean} * @returns {boolean}
*/ */
private validateIndex(index: number): boolean { private validateIndex(index: number): boolean {

View file

@ -1,7 +1,6 @@
/** /**
* @class BlockSelection * @class BlockSelection
* @classdesc Manages Block selection with shortcut CMD+A * @classdesc Manages Block selection with shortcut CMD+A
*
* @module BlockSelection * @module BlockSelection
* @version 1.0.0 * @version 1.0.0
*/ */
@ -190,10 +189,8 @@ export default class BlockSelection extends Module {
* *
* - Remove all ranges * - Remove all ranges
* - Unselect all Blocks * - Unselect all Blocks
*
* @param {boolean} readOnlyEnabled - "read only" state
*/ */
public toggleReadOnly(readOnlyEnabled: boolean): void { public toggleReadOnly(): void {
SelectionUtils.get() SelectionUtils.get()
.removeAllRanges(); .removeAllRanges();
@ -250,12 +247,13 @@ export default class BlockSelection extends Module {
const eventKey = (reason as KeyboardEvent).key; const eventKey = (reason as KeyboardEvent).key;
/** /**
* If event.key length >1 that means key is special (e.g. Enter or Dead or Unidentifier). * If event.key length >1 that means key is special (e.g. Enter or Dead or Unidentified).
* So we use empty string * So we use empty string
* *
* @see https://developer.mozilla.org/ru/docs/Web/API/KeyboardEvent/key * @see https://developer.mozilla.org/ru/docs/Web/API/KeyboardEvent/key
*/ */
Caret.insertContentAtCaretPosition(eventKey.length > 1 ? '' : eventKey); Caret.insertContentAtCaretPosition(eventKey.length > 1 ? '' : eventKey);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 20)(); }, 20)();
} }
@ -283,7 +281,6 @@ export default class BlockSelection extends Module {
* Reduce each Block and copy its content * Reduce each Block and copy its content
* *
* @param {ClipboardEvent} e - copy/cut event * @param {ClipboardEvent} e - copy/cut event
*
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public copySelectedBlocks(e: ClipboardEvent): Promise<void> { public copySelectedBlocks(e: ClipboardEvent): Promise<void> {

View file

@ -3,9 +3,7 @@
* @classdesc Contains methods for working Caret * @classdesc Contains methods for working Caret
* *
* Uses Range methods to manipulate with caret * Uses Range methods to manipulate with caret
*
* @module Caret * @module Caret
*
* @version 2.0.0 * @version 2.0.0
*/ */
@ -110,7 +108,7 @@ export default class Caret extends Module {
* Workaround case when block starts with several <br>'s (created by SHIFT+ENTER) * Workaround case when block starts with several <br>'s (created by SHIFT+ENTER)
* *
* @see https://github.com/codex-team/editor.js/issues/726 * @see https://github.com/codex-team/editor.js/issues/726
* We need to allow to delete such linebreaks, so in this case caret IS NOT AT START * We need to allow to delete such line breaks, so in this case caret IS NOT AT START
*/ */
const regularLineBreak = $.isLineBreakTag(node); const regularLineBreak = $.isLineBreakTag(node);
/** /**
@ -162,7 +160,7 @@ export default class Caret extends Module {
* In this case, anchor node has ELEMENT_NODE node type. * In this case, anchor node has ELEMENT_NODE node type.
* Anchor offset shows amount of children between start of the element and caret position. * Anchor offset shows amount of children between start of the element and caret position.
* *
* So we use child with anchofocusOffset - 1 as new focusNode. * So we use child with focusOffset - 1 as new focusNode.
*/ */
let focusOffset = selection.focusOffset; let focusOffset = selection.focusOffset;
@ -262,6 +260,7 @@ export default class Caret extends Module {
*/ */
_.delay(() => { _.delay(() => {
this.set(nodeToSet as HTMLElement, offset); this.set(nodeToSet as HTMLElement, offset);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 20)(); }, 20)();
BlockManager.setCurrentBlockByChildNode(block.holder); BlockManager.setCurrentBlockByChildNode(block.holder);
@ -509,6 +508,7 @@ export default class Caret extends Module {
newRange.selectNode(shadowCaret); newRange.selectNode(shadowCaret);
newRange.extractContents(); newRange.extractContents();
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 50); }, 50);
} }
@ -549,7 +549,7 @@ export default class Caret extends Module {
} }
/** /**
* Get all first-level (first child of [contenteditabel]) siblings from passed node * Get all first-level (first child of [contenteditable]) siblings from passed node
* Then you can check it for emptiness * Then you can check it for emptiness
* *
* @example * @example
@ -562,10 +562,8 @@ export default class Caret extends Module {
* <p></p> | right first-level siblings * <p></p> | right first-level siblings
* <p></p> | * <p></p> |
* </div> * </div>
*
* @param {HTMLElement} from - element from which siblings should be searched * @param {HTMLElement} from - element from which siblings should be searched
* @param {'left' | 'right'} direction - direction of search * @param {'left' | 'right'} direction - direction of search
*
* @returns {HTMLElement[]} * @returns {HTMLElement[]}
*/ */
private getHigherLevelSiblings(from: HTMLElement, direction?: 'left' | 'right'): HTMLElement[] { private getHigherLevelSiblings(from: HTMLElement, direction?: 'left' | 'right'): HTMLElement[] {

View file

@ -176,7 +176,7 @@ export default class CrossBlockSelection extends Module {
private onMouseUp = (): void => { private onMouseUp = (): void => {
this.listeners.off(document, 'mouseover', this.onMouseOver); this.listeners.off(document, 'mouseover', this.onMouseOver);
this.listeners.off(document, 'mouseup', this.onMouseUp); this.listeners.off(document, 'mouseup', this.onMouseUp);
} };
/** /**
* Mouse over event handler * Mouse over event handler
@ -222,7 +222,7 @@ export default class CrossBlockSelection extends Module {
this.toggleBlocksSelectedState(relatedBlock, targetBlock); this.toggleBlocksSelectedState(relatedBlock, targetBlock);
this.lastSelectedBlock = targetBlock; this.lastSelectedBlock = targetBlock;
} };
/** /**
* Change blocks selection state between passed two blocks. * Change blocks selection state between passed two blocks.

View file

@ -4,7 +4,9 @@ import * as _ from '../utils';
import { import {
BlockAPI, BlockAPI,
PasteEvent, PasteEvent,
PasteEventDetail PasteEventDetail,
SanitizerConfig,
SanitizerRule
} from '../../../types'; } from '../../../types';
import Block from '../block'; import Block from '../block';
import { SavedData } from '../../../types/data-formats'; import { SavedData } from '../../../types/data-formats';
@ -20,6 +22,12 @@ interface TagSubstitute {
* *
*/ */
tool: BlockTool; tool: BlockTool;
/**
* If a Tool specifies just a tag name, all the attributes will be sanitized.
* But Tool can explicitly specify sanitizer configuration for supported tags
*/
sanitizationConfig?: SanitizerRule;
} }
/** /**
@ -97,9 +105,7 @@ interface PasteData {
/** /**
* @class Paste * @class Paste
* @classdesc Contains methods to handle paste on editor * @classdesc Contains methods to handle paste on editor
*
* @module Paste * @module Paste
*
* @version 2.0.0 * @version 2.0.0
*/ */
export default class Paste extends Module { export default class Paste extends Module {
@ -112,12 +118,12 @@ export default class Paste extends Module {
/** /**
* Tags` substitutions parameters * Tags` substitutions parameters
*/ */
private toolsTags: {[tag: string]: TagSubstitute} = {}; private toolsTags: { [tag: string]: TagSubstitute } = {};
/** /**
* Store tags to substitute by tool name * Store tags to substitute by tool name
*/ */
private tagsByTool: {[tools: string]: string[]} = {}; private tagsByTool: { [tools: string]: string[] } = {};
/** Patterns` substitutions parameters */ /** Patterns` substitutions parameters */
private toolsPatterns: PatternSubstitute[] = []; private toolsPatterns: PatternSubstitute[] = [];
@ -168,7 +174,7 @@ export default class Paste extends Module {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const includesFiles = types.includes ? types.includes('Files') : (types as any).contains('Files'); const includesFiles = types.includes ? types.includes('Files') : (types as any).contains('Files');
if (includesFiles) { if (includesFiles && !_.isEmpty(this.toolsFiles)) {
await this.processFiles(dataTransfer.files); await this.processFiles(dataTransfer.files);
return; return;
@ -186,7 +192,7 @@ export default class Paste extends Module {
this.insertEditorJSData(JSON.parse(editorJSData)); this.insertEditorJSData(JSON.parse(editorJSData));
return; return;
} catch (e) {} // Do nothing and continue execution as usual if error appears } catch (e) { } // Do nothing and continue execution as usual if error appears
} }
/** /**
@ -198,7 +204,11 @@ export default class Paste extends Module {
/** Add all tags that can be substituted to sanitizer configuration */ /** Add all tags that can be substituted to sanitizer configuration */
const toolsTags = Object.keys(this.toolsTags).reduce((result, tag) => { const toolsTags = Object.keys(this.toolsTags).reduce((result, tag) => {
result[tag.toLowerCase()] = true; /**
* If Tool explicitly specifies sanitizer configuration for the tag, use it.
* Otherwise, remove all attributes
*/
result[tag.toLowerCase()] = this.toolsTags[tag].sanitizationConfig ?? {};
return result; return result;
}, {}); }, {});
@ -304,6 +314,30 @@ export default class Paste extends Module {
e e
); );
} }
};
/**
* Get tags name list from either tag name or sanitization config.
*
* @param {string | object} tagOrSanitizeConfig - tag name or sanitize config object.
* @returns {string[]} array of tags.
*/
private collectTagNames(tagOrSanitizeConfig: string | SanitizerConfig): string[] {
/**
* If string, then it is a tag name.
*/
if (_.isString(tagOrSanitizeConfig)) {
return [ tagOrSanitizeConfig ];
}
/**
* If object, then its keys are tags.
*/
if (_.isObject(tagOrSanitizeConfig)) {
return Object.keys(tagOrSanitizeConfig);
}
/** Return empty tag list */
return [];
} }
/** /**
@ -312,25 +346,39 @@ export default class Paste extends Module {
* @param tool - BlockTool object * @param tool - BlockTool object
*/ */
private getTagsConfig(tool: BlockTool): void { private getTagsConfig(tool: BlockTool): void {
const tags = tool.pasteConfig.tags || []; const tagsOrSanitizeConfigs = tool.pasteConfig.tags || [];
const toolTags = [];
tags.forEach((tag) => { tagsOrSanitizeConfigs.forEach((tagOrSanitizeConfig) => {
if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) { const tags = this.collectTagNames(tagOrSanitizeConfig);
_.log(
`Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` +
`because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`,
'warn'
);
return; /**
} * Add tags to toolTags array
*/
toolTags.push(...tags);
tags.forEach((tag) => {
if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) {
_.log(
`Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` +
`because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`,
'warn'
);
this.toolsTags[tag.toUpperCase()] = { return;
tool, }
}; /**
* Get sanitize config for tag.
*/
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : null;
this.toolsTags[tag.toUpperCase()] = {
tool,
sanitizationConfig,
};
});
}); });
this.tagsByTool[tool.name] = tags.map((t) => t.toUpperCase()); this.tagsByTool[tool.name] = toolTags.map((t) => t.toUpperCase());
} }
/** /**
@ -405,7 +453,6 @@ export default class Paste extends Module {
* Check if browser behavior suits better * Check if browser behavior suits better
* *
* @param {EventTarget} element - element where content has been pasted * @param {EventTarget} element - element where content has been pasted
*
* @returns {boolean} * @returns {boolean}
*/ */
private isNativeBehaviour(element: EventTarget): boolean { private isNativeBehaviour(element: EventTarget): boolean {
@ -439,7 +486,7 @@ export default class Paste extends Module {
BlockManager.clearFocused(); BlockManager.clearFocused();
Toolbar.close(); Toolbar.close();
} };
/** /**
* Get files from data transfer object and insert related Tools * Get files from data transfer object and insert related Tools
@ -449,7 +496,7 @@ export default class Paste extends Module {
private async processFiles(items: FileList): Promise<void> { private async processFiles(items: FileList): Promise<void> {
const { BlockManager } = this.Editor; const { BlockManager } = this.Editor;
let dataToInsert: {type: string; event: PasteEvent}[]; let dataToInsert: { type: string; event: PasteEvent }[];
dataToInsert = await Promise.all( dataToInsert = await Promise.all(
Array Array
@ -473,11 +520,12 @@ export default class Paste extends Module {
* *
* @param {File} file - file to process * @param {File} file - file to process
*/ */
private async processFile(file: File): Promise<{event: PasteEvent; type: string}> { private async processFile(file: File): Promise<{ event: PasteEvent; type: string }> {
const extension = _.getFileExtension(file); const extension = _.getFileExtension(file);
const foundConfig = Object const foundConfig = Object
.entries(this.toolsFiles) .entries(this.toolsFiles)
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
.find(([toolName, { mimeTypes, extensions } ]) => { .find(([toolName, { mimeTypes, extensions } ]) => {
const [fileType, fileSubtype] = file.type.split('/'); const [fileType, fileSubtype] = file.type.split('/');
@ -510,11 +558,22 @@ export default class Paste extends Module {
* Split HTML string to blocks and return it as array of Block data * Split HTML string to blocks and return it as array of Block data
* *
* @param {string} innerHTML - html string to process * @param {string} innerHTML - html string to process
*
* @returns {PasteData[]} * @returns {PasteData[]}
*/ */
private processHTML(innerHTML: string): PasteData[] { private processHTML(innerHTML: string): PasteData[] {
const { Tools } = this.Editor; const { Tools } = this.Editor;
/**
* @todo Research, do we really need to always wrap innerHTML to a div:
* - <img> tag could be processed separately, but for now it becomes div-wrapped
* and then .getNodes() returns strange: [document-fragment, img]
* (description of the method says that it should should return only block tags or fragments,
* but there are inline-block element along with redundant empty fragment)
* - probably this is a reason of bugs with unexpected new block creation instead of inline pasting:
* - https://github.com/codex-team/editor.js/issues/1427
* - https://github.com/codex-team/editor.js/issues/1244
* - https://github.com/codex-team/editor.js/issues/740
*/
const wrapper = $.make('DIV'); const wrapper = $.make('DIV');
wrapper.innerHTML = innerHTML; wrapper.innerHTML = innerHTML;
@ -543,16 +602,65 @@ export default class Paste extends Module {
break; break;
} }
const { tags } = tool.pasteConfig; const { tags: tagsOrSanitizeConfigs } = tool.pasteConfig;
const toolTags = tags.reduce((result, tag) => { /**
result[tag.toLowerCase()] = {}; * Reduce the tags or sanitize configs to a single array of sanitize config.
* For example:
* If sanitize config is
* [ 'tbody',
* {
* table: {
* width: true,
* height: true,
* },
* },
* {
* td: {
* colspan: true,
* rowspan: true,
* },
* tr: { // <-- the second tag
* height: true,
* },
* },
* ]
* then sanitize config will be
* [
* 'table':{},
* 'tbody':{width: true, height: true}
* 'td':{colspan: true, rowspan: true},
* 'tr':{height: true}
* ]
*/
const toolTags = tagsOrSanitizeConfigs.reduce((result, tagOrSanitizeConfig) => {
const tags = this.collectTagNames(tagOrSanitizeConfig);
tags.forEach((tag) => {
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : null;
result[tag] = sanitizationConfig || {};
});
return result; return result;
}, {}); }, {});
const customConfig = Object.assign({}, toolTags, tool.baseSanitizeConfig); const customConfig = Object.assign({}, toolTags, tool.baseSanitizeConfig);
content.innerHTML = clean(content.innerHTML, customConfig); /**
* A workaround for the HTMLJanitor bug with Tables (incorrect sanitizing of table.innerHTML)
* https://github.com/guardian/html-janitor/issues/3
*/
if (content.tagName.toLowerCase() === 'table') {
const cleanTableHTML = clean(content.outerHTML, customConfig);
const tmpWrapper = $.make('div', undefined, {
innerHTML: cleanTableHTML,
});
content = tmpWrapper.firstChild;
} else {
content.innerHTML = clean(content.innerHTML, customConfig);
}
const event = this.composePasteEvent('tag', { const event = this.composePasteEvent('tag', {
data: content, data: content,
@ -565,18 +673,22 @@ export default class Paste extends Module {
event, event,
}; };
}) })
.filter((data) => !$.isNodeEmpty(data.content) || $.isSingleTag(data.content)); .filter((data) => {
const isEmpty = $.isEmpty(data.content);
const isSingleTag = $.isSingleTag(data.content);
return !isEmpty || isSingleTag;
});
} }
/** /**
* Split plain text by new line symbols and return it as array of Block data * Split plain text by new line symbols and return it as array of Block data
* *
* @param {string} plain - string to process * @param {string} plain - string to process
*
* @returns {PasteData[]} * @returns {PasteData[]}
*/ */
private processPlain(plain: string): PasteData[] { private processPlain(plain: string): PasteData[] {
const { defaultBlock } = this.config as {defaultBlock: string}; const { defaultBlock } = this.config as { defaultBlock: string };
if (!plain) { if (!plain) {
return []; return [];
@ -608,7 +720,7 @@ export default class Paste extends Module {
/** /**
* Process paste of single Block tool content * Process paste of single Block tool content
* *
* @param {PasteData} dataToInsert - data of Block to inseret * @param {PasteData} dataToInsert - data of Block to insert
*/ */
private async processSingleBlock(dataToInsert: PasteData): Promise<void> { private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
const { Caret, BlockManager } = this.Editor; const { Caret, BlockManager } = this.Editor;
@ -678,10 +790,9 @@ export default class Paste extends Module {
* Get patterns` matches * Get patterns` matches
* *
* @param {string} text - text to process * @param {string} text - text to process
*
* @returns {Promise<{event: PasteEvent, tool: string}>} * @returns {Promise<{event: PasteEvent, tool: string}>}
*/ */
private async processPattern(text: string): Promise<{event: PasteEvent; tool: string}> { private async processPattern(text: string): Promise<{ event: PasteEvent; tool: string }> {
const pattern = this.toolsPatterns.find((substitute) => { const pattern = this.toolsPatterns.find((substitute) => {
const execResult = substitute.pattern.exec(text); const execResult = substitute.pattern.exec(text);
@ -712,7 +823,6 @@ export default class Paste extends Module {
* *
* @param {PasteData} data - data to insert * @param {PasteData} data - data to insert
* @param {boolean} canReplaceCurrentBlock - if true and is current Block is empty, will replace current Block * @param {boolean} canReplaceCurrentBlock - if true and is current Block is empty, will replace current Block
*
* @returns {void} * @returns {void}
*/ */
private insertBlock(data: PasteData, canReplaceCurrentBlock = false): void { private insertBlock(data: PasteData, canReplaceCurrentBlock = false): void {
@ -736,7 +846,6 @@ export default class Paste extends Module {
* Insert data passed as application/x-editor-js JSON * Insert data passed as application/x-editor-js JSON
* *
* @param {Array} blocks Blocks' data to insert * @param {Array} blocks Blocks' data to insert
*
* @returns {void} * @returns {void}
*/ */
private insertEditorJSData(blocks: Pick<SavedData, 'id' | 'data' | 'tool'>[]): void { private insertEditorJSData(blocks: Pick<SavedData, 'id' | 'data' | 'tool'>[]): void {
@ -770,8 +879,6 @@ export default class Paste extends Module {
* @param {Node} node - current node * @param {Node} node - current node
* @param {Node[]} nodes - processed nodes * @param {Node[]} nodes - processed nodes
* @param {Node} destNode - destination node * @param {Node} destNode - destination node
*
* @returns {Node[]}
*/ */
private processElementNode(node: Node, nodes: Node[], destNode: Node): Node[] | void { private processElementNode(node: Node, nodes: Node[], destNode: Node): Node[] | void {
const tags = Object.keys(this.toolsTags); const tags = Object.keys(this.toolsTags);
@ -814,7 +921,6 @@ export default class Paste extends Module {
* 2. Document Fragments contained text and markup tags like a, b, i etc. * 2. Document Fragments contained text and markup tags like a, b, i etc.
* *
* @param {Node} wrapper - wrapper of paster HTML content * @param {Node} wrapper - wrapper of paster HTML content
*
* @returns {Node[]} * @returns {Node[]}
*/ */
private getNodes(wrapper: Node): Node[] { private getNodes(wrapper: Node): Node[] {
@ -878,3 +984,4 @@ export default class Paste extends Module {
}) as PasteEvent; }) as PasteEvent;
} }
} }

View file

@ -6,9 +6,7 @@ import { CriticalError } from '../errors/critical';
* *
* Has one important method: * Has one important method:
* - {Function} toggleReadonly - Set read-only mode or toggle current state * - {Function} toggleReadonly - Set read-only mode or toggle current state
*
* @version 1.0.0 * @version 1.0.0
*
* @typedef {ReadOnly} ReadOnly * @typedef {ReadOnly} ReadOnly
* @property {boolean} readOnlyEnabled - read-only state * @property {boolean} readOnlyEnabled - read-only state
*/ */

View file

@ -1,7 +1,6 @@
/** /**
* @class RectangleSelection * @class RectangleSelection
* @classdesc Manages Block selection with mouse * @classdesc Manages Block selection with mouse
*
* @module RectangleSelection * @module RectangleSelection
* @version 1.0.0 * @version 1.0.0
*/ */
@ -188,6 +187,7 @@ export default class RectangleSelection extends Module {
this.listeners.on(document.body, 'mousemove', _.throttle((mouseEvent: MouseEvent) => { this.listeners.on(document.body, 'mousemove', _.throttle((mouseEvent: MouseEvent) => {
this.processMouseMove(mouseEvent); this.processMouseMove(mouseEvent);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 10), { }, 10), {
passive: true, passive: true,
}); });
@ -198,6 +198,7 @@ export default class RectangleSelection extends Module {
this.listeners.on(window, 'scroll', _.throttle((mouseEvent: MouseEvent) => { this.listeners.on(window, 'scroll', _.throttle((mouseEvent: MouseEvent) => {
this.processScroll(mouseEvent); this.processScroll(mouseEvent);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 10), { }, 10), {
passive: true, passive: true,
}); });
@ -290,7 +291,7 @@ export default class RectangleSelection extends Module {
/** /**
* Generates required HTML elements * Generates required HTML elements
* *
* @returns {object<string, Element>} * @returns {Object<string, Element>}
*/ */
private genHTML(): {container: Element; overlay: Element} { private genHTML(): {container: Element; overlay: Element} {
const { UI } = this.Editor; const { UI } = this.Editor;

View file

@ -8,7 +8,6 @@ import BlockTool from '../tools/block';
* *
* @module Renderer * @module Renderer
* @author CodeX Team * @author CodeX Team
*
* @version 2.0.0 * @version 2.0.0
*/ */
export default class Renderer extends Module { export default class Renderer extends Module {
@ -37,7 +36,6 @@ export default class Renderer extends Module {
* } * }
* }, * },
* ] * ]
*
*/ */
/** /**
@ -68,7 +66,6 @@ export default class Renderer extends Module {
* Insert block to working zone * Insert block to working zone
* *
* @param {object} item - Block data to insert * @param {object} item - Block data to insert
*
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async insertBlock(item: OutputBlockData): Promise<void> { public async insertBlock(item: OutputBlockData): Promise<void> {

View file

@ -16,7 +16,6 @@ declare const VERSION: string;
/** /**
* @classdesc This method reduces all Blocks asyncronically and calls Block's save method to extract data * @classdesc This method reduces all Blocks asyncronically and calls Block's save method to extract data
*
* @typedef {Saver} Saver * @typedef {Saver} Saver
* @property {Element} html - Editor HTML content * @property {Element} html - Editor HTML content
* @property {string} json - Editor JSON output * @property {string} json - Editor JSON output

View file

@ -1,6 +1,5 @@
import Module from '../../__module'; import Module from '../../__module';
import $ from '../../dom'; import $ from '../../dom';
import * as _ from '../../utils';
import SelectionUtils from '../../selection'; import SelectionUtils from '../../selection';
import Block from '../../block'; import Block from '../../block';
import Popover, { PopoverEvent } from '../../utils/popover'; import Popover, { PopoverEvent } from '../../utils/popover';
@ -53,7 +52,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
* @todo remove once BlockSettings becomes standalone non-module class * @todo remove once BlockSettings becomes standalone non-module class
*/ */
public get flipper(): Flipper { public get flipper(): Flipper {
return this.popover.flipper; return this.popover?.flipper;
} }
/** /**
@ -64,7 +63,7 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
/** /**
* Popover instance. There is a util for vertical lists. * Popover instance. There is a util for vertical lists.
*/ */
private popover: Popover; private popover: Popover | undefined;
/** /**
* Panel with block settings with 2 sections: * Panel with block settings with 2 sections:
@ -192,5 +191,5 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
*/ */
private onOverlayClicked = (): void => { private onOverlayClicked = (): void => {
this.close(); this.close();
} };
} }

View file

@ -50,7 +50,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
/** /**
* Available tools data * Available tools data
*/ */
private tools: {name: string; toolboxItem: ToolboxConfigEntry; button: HTMLElement}[] = [] private tools: {name: string; toolboxItem: ToolboxConfigEntry; button: HTMLElement}[] = [];
/** /**
* Instance of class that responses for leafing buttons by arrows/tab * Instance of class that responses for leafing buttons by arrows/tab
@ -184,8 +184,6 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
public async replaceWithBlock(replacingToolName: string, blockDataOverrides?: BlockToolData): Promise<void> { public async replaceWithBlock(replacingToolName: string, blockDataOverrides?: BlockToolData): Promise<void> {
/** /**
* At first, we get current Block data * At first, we get current Block data
*
* @type {BlockToolConstructable}
*/ */
const currentBlockTool = this.Editor.BlockManager.currentBlock.tool; const currentBlockTool = this.Editor.BlockManager.currentBlock.tool;
const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData; const savedBlock = await this.Editor.BlockManager.currentBlock.save() as SavedData;
@ -193,8 +191,6 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
/** /**
* Getting a class of replacing Tool * Getting a class of replacing Tool
*
* @type {BlockToolConstructable}
*/ */
const replacingTool = this.Editor.Tools.blockTools.get(replacingToolName); const replacingTool = this.Editor.Tools.blockTools.get(replacingToolName);
@ -265,6 +261,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
_.delay(() => { _.delay(() => {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock); this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 10)(); }, 10)();
} }

View file

@ -8,25 +8,24 @@ import { ModuleConfig } from '../../../types-internal/module-config';
import { BlockAPI } from '../../../../types'; import { BlockAPI } from '../../../../types';
import Block from '../../block'; import Block from '../../block';
import Toolbox, { ToolboxEvent } from '../../ui/toolbox'; import Toolbox, { ToolboxEvent } from '../../ui/toolbox';
import { IconMenu, IconPlus } from '@codexteam/icons';
/** /**
* @todo Tab on non-empty block should open Block Settings of the hoveredBlock (not where caret is set) * @todo Tab on non-empty block should open Block Settings of the hoveredBlock (not where caret is set)
* - make Block Settings a standalone module * - make Block Settings a standalone module
*
* @todo - Keyboard-only mode bug: * @todo - Keyboard-only mode bug:
* press Tab, flip to the Checkbox. press Enter (block will be added), Press Tab * press Tab, flip to the Checkbox. press Enter (block will be added), Press Tab
* (Block Tunes will be opened with Move up focused), press Enter, press Tab both Block Tunes and Toolbox will be opened * (Block Tunes will be opened with Move up focused), press Enter, press Tab both Block Tunes and Toolbox will be opened
* * @todo TEST CASE - show toggler after opening and closing the Inline Toolbar
* @todo TESTCASE - show toggler after opening and closing the Inline Toolbar * @todo TEST CASE - Click outside Editor holder should close Toolbar and Clear Focused blocks
* @todo TESTCASE - Click outside Editor holder should close Toolbar and Clear Focused blocks * @todo TEST CASE - Click inside Editor holder should close Toolbar and Clear Focused blocks
* @todo TESTCASE - Click inside Editor holder should close Toolbar and Clear Focused blocks * @todo TEST CASE - Click inside Redactor zone when Block Settings are opened:
* @todo TESTCASE - Click inside Redactor zone when Block Settings are opened:
* - should close Block Settings * - should close Block Settings
* - should not close Toolbar * - should not close Toolbar
* - should move Toolbar to the clicked Block * - should move Toolbar to the clicked Block
* @todo TESTCASE - Toolbar should be closed on the Cross Block Selection * @todo TEST CASE - Toolbar should be closed on the Cross Block Selection
* @todo TESTCASE - Toolbar should be closed on the Rectangle Selection * @todo TEST CASE - Toolbar should be closed on the Rectangle Selection
* @todo TESTCASE - If Block Settings or Toolbox are opened, the Toolbar should not be moved by Bocks hovering * @todo TEST CASE - If Block Settings or Toolbox are opened, the Toolbar should not be moved by Bocks hovering
*/ */
/** /**
@ -78,7 +77,6 @@ interface ToolbarNodes {
* *
* @class * @class
* @classdesc Toolbar module * @classdesc Toolbar module
*
* @typedef {Toolbar} Toolbar * @typedef {Toolbar} Toolbar
* @property {object} nodes - Toolbar nodes * @property {object} nodes - Toolbar nodes
* @property {Element} nodes.wrapper - Toolbar main element * @property {Element} nodes.wrapper - Toolbar main element
@ -300,12 +298,8 @@ export default class Toolbar extends Module<ToolbarNodes> {
* *
* @param {boolean} withBlockActions - by default, Toolbar opens with Block Actions. * @param {boolean} withBlockActions - by default, Toolbar opens with Block Actions.
* This flag allows to open Toolbar without Actions. * This flag allows to open Toolbar without Actions.
* @param {boolean} needToCloseToolbox - by default, Toolbar will be moved with opening
* (by click on Block, or by enter)
* with closing Toolbox and Block Settings
* This flag allows to open Toolbar with Toolbox
*/ */
private open(withBlockActions = true, needToCloseToolbox = true): void { private open(withBlockActions = true): void {
_.delay(() => { _.delay(() => {
this.nodes.wrapper.classList.add(this.CSS.toolbarOpened); this.nodes.wrapper.classList.add(this.CSS.toolbarOpened);
@ -314,6 +308,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
} else { } else {
this.blockActions.hide(); this.blockActions.hide();
} }
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 50)(); }, 50)();
} }
@ -341,8 +336,9 @@ export default class Toolbar extends Module<ToolbarNodes> {
* - Plus Button * - Plus Button
* - Toolbox * - Toolbox
*/ */
this.nodes.plusButton = $.make('div', this.CSS.plusButton); this.nodes.plusButton = $.make('div', this.CSS.plusButton, {
$.append(this.nodes.plusButton, $.svg('plus', 16, 16)); innerHTML: IconPlus,
});
$.append(this.nodes.actions, this.nodes.plusButton); $.append(this.nodes.actions, this.nodes.plusButton);
this.readOnlyMutableListeners.on(this.nodes.plusButton, 'click', () => { this.readOnlyMutableListeners.on(this.nodes.plusButton, 'click', () => {
@ -370,10 +366,10 @@ export default class Toolbar extends Module<ToolbarNodes> {
* - Remove Block Button * - Remove Block Button
* - Settings Panel * - Settings Panel
*/ */
this.nodes.settingsToggler = $.make('span', this.CSS.settingsToggler); this.nodes.settingsToggler = $.make('span', this.CSS.settingsToggler, {
const settingsIcon = $.svg('dots', 16, 16); innerHTML: IconMenu,
});
$.append(this.nodes.settingsToggler, settingsIcon);
$.append(this.nodes.actions, this.nodes.settingsToggler); $.append(this.nodes.actions, this.nodes.settingsToggler);
this.tooltip.onHover( this.tooltip.onHover(
@ -478,7 +474,7 @@ export default class Toolbar extends Module<ToolbarNodes> {
}, true); }, true);
/** /**
* Subscribe to the 'block-hovered' event if currenct view is not mobile * Subscribe to the 'block-hovered' event if current view is not mobile
* *
* @see https://github.com/codex-team/editor.js/issues/1972 * @see https://github.com/codex-team/editor.js/issues/1972
*/ */

View file

@ -11,6 +11,7 @@ import Tooltip from '../../utils/tooltip';
import { ModuleConfig } from '../../../types-internal/module-config'; import { ModuleConfig } from '../../../types-internal/module-config';
import InlineTool from '../../tools/inline'; import InlineTool from '../../tools/inline';
import { CommonInternalSettings } from '../../tools/base'; import { CommonInternalSettings } from '../../tools/base';
import { IconChevronDown } from '@codexteam/icons';
/** /**
* Inline Toolbar elements * Inline Toolbar elements
@ -51,6 +52,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
inputField: 'cdx-input', inputField: 'cdx-input',
focusedButton: 'ce-inline-tool--focused', focusedButton: 'ce-inline-tool--focused',
conversionToggler: 'ce-inline-toolbar__dropdown', conversionToggler: 'ce-inline-toolbar__dropdown',
conversionTogglerArrow: 'ce-inline-toolbar__dropdown-arrow',
conversionTogglerHidden: 'ce-inline-toolbar__dropdown--hidden', conversionTogglerHidden: 'ce-inline-toolbar__dropdown--hidden',
conversionTogglerContent: 'ce-inline-toolbar__dropdown-content', conversionTogglerContent: 'ce-inline-toolbar__dropdown-content',
togglerAndButtonsWrapper: 'ce-inline-toolbar__toggler-and-button-wrapper', togglerAndButtonsWrapper: 'ce-inline-toolbar__toggler-and-button-wrapper',
@ -66,7 +68,8 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
/** /**
* Margin above/below the Toolbar * Margin above/below the Toolbar
*/ */
private readonly toolbarVerticalMargin: number = 5; // eslint-disable-next-line @typescript-eslint/no-magic-numbers
private readonly toolbarVerticalMargin: number = _.isMobileScreen() ? 20 : 6;
/** /**
* TODO: Get rid of this * TODO: Get rid of this
@ -280,7 +283,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
/** /**
* Check if node is contained by Inline Toolbar * Check if node is contained by Inline Toolbar
* *
* @param {Node} node node to chcek * @param {Node} node node to check
*/ */
public containsNode(node: Node): boolean { public containsNode(node: Node): boolean {
return this.nodes.wrapper.contains(node); return this.nodes.wrapper.contains(node);
@ -322,7 +325,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`); const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
// If click is on actions wrapper, // If click is on actions wrapper,
// do not prevent default behaviour because actions might include interactive elements // do not prevent default behavior because actions might include interactive elements
if (!isClickedOnActionsWrapper) { if (!isClickedOnActionsWrapper) {
event.preventDefault(); event.preventDefault();
} }
@ -428,10 +431,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
this.nodes.conversionToggler = $.make('div', this.CSS.conversionToggler); this.nodes.conversionToggler = $.make('div', this.CSS.conversionToggler);
this.nodes.conversionTogglerContent = $.make('div', this.CSS.conversionTogglerContent); this.nodes.conversionTogglerContent = $.make('div', this.CSS.conversionTogglerContent);
const icon = $.svg('toggler-down', 13, 13); const iconWrapper = $.make('div', this.CSS.conversionTogglerArrow, {
innerHTML: IconChevronDown,
});
this.nodes.conversionToggler.appendChild(this.nodes.conversionTogglerContent); this.nodes.conversionToggler.appendChild(this.nodes.conversionTogglerContent);
this.nodes.conversionToggler.appendChild(icon); this.nodes.conversionToggler.appendChild(iconWrapper);
this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler); this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler);
@ -454,10 +459,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
}); });
}); });
this.tooltip.onHover(this.nodes.conversionToggler, I18n.ui(I18nInternalNS.ui.inlineToolbar.converter, 'Convert to'), { if (_.isMobileScreen() === false ) {
placement: 'top', this.tooltip.onHover(this.nodes.conversionToggler, I18n.ui(I18nInternalNS.ui.inlineToolbar.converter, 'Convert to'), {
hidingDelay: 100, placement: 'top',
}); hidingDelay: 100,
});
}
} }
/** /**
@ -581,10 +588,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
})); }));
} }
this.tooltip.onHover(button, tooltipContent, { if (_.isMobileScreen() === false ) {
placement: 'top', this.tooltip.onHover(button, tooltipContent, {
hidingDelay: 100, placement: 'top',
}); hidingDelay: 100,
});
}
instance.checkState(SelectionUtils.get()); instance.checkState(SelectionUtils.get());
} }
@ -664,6 +673,15 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
tool.surround(range); tool.surround(range);
this.checkToolsState(); this.checkToolsState();
/**
* If tool has "actions", so after click it will probably toggle them on.
* For example, the Inline Link Tool will show the URL-input.
* So we disable the Flipper for that case to allow Tool bind own Enter listener
*/
if (tool.renderActions !== undefined) {
this.flipper.deactivate();
}
} }
/** /**

View file

@ -21,17 +21,8 @@ import ToolsCollection from '../tools/collection';
* Creates Instances from Plugins and binds external config to the instances * Creates Instances from Plugins and binds external config to the instances
*/ */
type ToolClass = BlockTool | InlineTool | BlockTune;
/** /**
* Class properties: * Modules that works with tools classes
*
* @typedef {Tools} Tools
* @property {Tools[]} toolsAvailable - available Tools
* @property {Tools[]} toolsUnavailable - unavailable Tools
* @property {object} toolsClasses - all classes
* @property {object} toolsSettings - Tools settings
* @property {EditorConfig} config - Editor config
*/ */
export default class Tools extends Module { export default class Tools extends Module {
/** /**
@ -44,8 +35,6 @@ export default class Tools extends Module {
/** /**
* Returns available Tools * Returns available Tools
*
* @returns {object<Tool>}
*/ */
public get available(): ToolsCollection { public get available(): ToolsCollection {
return this.toolsAvailable; return this.toolsAvailable;
@ -53,8 +42,6 @@ export default class Tools extends Module {
/** /**
* Returns unavailable Tools * Returns unavailable Tools
*
* @returns {Tool[]}
*/ */
public get unavailable(): ToolsCollection { public get unavailable(): ToolsCollection {
return this.toolsUnavailable; return this.toolsUnavailable;
@ -62,8 +49,6 @@ export default class Tools extends Module {
/** /**
* Return Tools for the Inline Toolbar * Return Tools for the Inline Toolbar
*
* @returns {object} - object of Inline Tool's classes
*/ */
public get inlineTools(): ToolsCollection<InlineTool> { public get inlineTools(): ToolsCollection<InlineTool> {
return this.available.inlineTools; return this.available.inlineTools;

View file

@ -1,9 +1,4 @@
/* eslint-disable jsdoc/no-undefined-types */ /* eslint-disable jsdoc/no-undefined-types */
/**
* Prebuilded sprite of SVG icons
*/
import sprite from '../../../dist/sprite.svg';
/** /**
* Module UI * Module UI
* *
@ -16,6 +11,7 @@ import * as _ from '../utils';
import Selection from '../selection'; import Selection from '../selection';
import Block from '../block'; import Block from '../block';
import Flipper from '../flipper'; import Flipper from '../flipper';
import { mobileScreenBreakpoint } from '../utils';
/** /**
* HTML Elements used for UI * HTML Elements used for UI
@ -29,14 +25,12 @@ interface UINodes {
/** /**
* @class * @class
*
* @classdesc Makes Editor.js UI: * @classdesc Makes Editor.js UI:
* <codex-editor> * <codex-editor>
* <ce-redactor /> * <ce-redactor />
* <ce-toolbar /> * <ce-toolbar />
* <ce-inline-toolbar /> * <ce-inline-toolbar />
* </codex-editor> * </codex-editor>
*
* @typedef {UI} UI * @typedef {UI} UI
* @property {EditorConfig} config - editor configuration {@link EditorJS#configuration} * @property {EditorConfig} config - editor configuration {@link EditorJS#configuration}
* @property {object} Editor - available editor modules {@link EditorJS#moduleInstances} * @property {object} Editor - available editor modules {@link EditorJS#moduleInstances}
@ -125,6 +119,7 @@ export default class UI extends Module<UINodes> {
*/ */
private resizeDebouncer: () => void = _.debounce(() => { private resizeDebouncer: () => void = _.debounce(() => {
this.windowResize(); this.windowResize();
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 200); }, 200);
/** /**
@ -163,11 +158,6 @@ export default class UI extends Module<UINodes> {
*/ */
this.addLoader(); this.addLoader();
/**
* Append SVG sprite
*/
this.appendSVGSprite();
/** /**
* Load and append CSS * Load and append CSS
*/ */
@ -235,12 +225,15 @@ export default class UI extends Module<UINodes> {
return true; return true;
} }
return Object.entries(this.Editor).filter(([moduleName, moduleClass]) => { /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
return Object.entries(this.Editor).filter(([_moduleName, moduleClass]) => {
return moduleClass.flipper instanceof Flipper; return moduleClass.flipper instanceof Flipper;
}) })
.some(([moduleName, moduleClass]) => { .some(([_moduleName, moduleClass]) => {
return moduleClass.flipper.hasFocus(); return moduleClass.flipper.hasFocus();
}); });
/* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
} }
/** /**
@ -266,7 +259,7 @@ export default class UI extends Module<UINodes> {
* Check for mobile mode and cache a result * Check for mobile mode and cache a result
*/ */
private checkIsMobile(): void { private checkIsMobile(): void {
this.isMobile = window.innerWidth < 650; this.isMobile = window.innerWidth < mobileScreenBreakpoint;
} }
/** /**
@ -364,8 +357,8 @@ export default class UI extends Module<UINodes> {
/** /**
* Handle selection change to manipulate Inline Toolbar appearance * Handle selection change to manipulate Inline Toolbar appearance
*/ */
this.readOnlyMutableListeners.on(document, 'selectionchange', (event: Event) => { this.readOnlyMutableListeners.on(document, 'selectionchange', () => {
this.selectionChanged(event); this.selectionChanged();
}, true); }, true);
this.readOnlyMutableListeners.on(window, 'resize', () => { this.readOnlyMutableListeners.on(window, 'resize', () => {
@ -412,6 +405,7 @@ export default class UI extends Module<UINodes> {
this.eventsDispatcher.emit(this.events.blockHovered, { this.eventsDispatcher.emit(this.events.blockHovered, {
block: this.Editor.BlockManager.getBlockByChildNode(hoveredBlock), block: this.Editor.BlockManager.getBlockByChildNode(hoveredBlock),
}); });
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 20), { }, 20), {
passive: true, passive: true,
}); });
@ -726,7 +720,6 @@ export default class UI extends Module<UINodes> {
* All clicks on the redactor zone * All clicks on the redactor zone
* *
* @param {MouseEvent} event - click event * @param {MouseEvent} event - click event
*
* @description * @description
* - By clicks on the Editor's bottom zone: * - By clicks on the Editor's bottom zone:
* - if last Block is empty, set a Caret to this * - if last Block is empty, set a Caret to this
@ -804,10 +797,8 @@ export default class UI extends Module<UINodes> {
/** /**
* Handle selection changes on mobile devices * Handle selection changes on mobile devices
* Uses for showing the Inline Toolbar * Uses for showing the Inline Toolbar
*
* @param {Event} event - selection event
*/ */
private selectionChanged(event: Event): void { private selectionChanged(): void {
const { CrossBlockSelection, BlockSelection } = this.Editor; const { CrossBlockSelection, BlockSelection } = this.Editor;
const focusedElement = Selection.anchorElement; const focusedElement = Selection.anchorElement;
@ -874,17 +865,4 @@ export default class UI extends Module<UINodes> {
*/ */
this.Editor.InlineToolbar.tryToShow(true, isNeedToShowConversionToolbar); this.Editor.InlineToolbar.tryToShow(true, isNeedToShowConversionToolbar);
} }
/**
* Append prebuilt sprite with SVG icons
*/
private appendSVGSprite(): void {
const spriteHolder = $.make('div');
spriteHolder.hidden = true;
spriteHolder.style.display = 'none';
spriteHolder.innerHTML = sprite;
$.append(this.nodes.wrapper, spriteHolder);
}
} }

View file

@ -19,7 +19,6 @@ interface Element {
* otherwise, returns false. * otherwise, returns false.
* *
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill}
*
* @param {string} s - selector * @param {string} s - selector
*/ */
if (!Element.prototype.matches) { if (!Element.prototype.matches) {
@ -46,7 +45,6 @@ if (!Element.prototype.matches) {
* If there isn't such an ancestor, it returns null. * If there isn't such an ancestor, it returns null.
* *
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill}
*
* @param {string} s - selector * @param {string} s - selector
*/ */
if (!Element.prototype.closest) { if (!Element.prototype.closest) {
@ -76,7 +74,6 @@ if (!Element.prototype.closest) {
* DOMString objects are inserted as equivalent Text nodes. * DOMString objects are inserted as equivalent Text nodes.
* *
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/prepend#Polyfill} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/prepend#Polyfill}
*
* @param {Node | Node[] | string | string[]} nodes - nodes to prepend * @param {Node | Node[] | string | string[]} nodes - nodes to prepend
*/ */
if (!Element.prototype.prepend) { if (!Element.prototype.prepend) {

View file

@ -146,7 +146,7 @@ export default class SelectionUtils {
/** /**
* Check if passed selection is at Editor's zone * Check if passed selection is at Editor's zone
* *
* @param selection - Selectoin object to check * @param selection - Selection object to check
*/ */
public static isSelectionAtEditor(selection: Selection): boolean { public static isSelectionAtEditor(selection: Selection): boolean {
if (!selection) { if (!selection) {
@ -326,8 +326,6 @@ export default class SelectionUtils {
* *
* @param element - element where to set focus * @param element - element where to set focus
* @param offset - offset of cursor * @param offset - offset of cursor
*
* @returns {DOMRect} of range
*/ */
public static setCursor(element: HTMLElement, offset = 0): DOMRect { public static setCursor(element: HTMLElement, offset = 0): DOMRect {
const range = document.createRange(); const range = document.createRange();
@ -452,7 +450,6 @@ export default class SelectionUtils {
* @param {string} tagName - tag to found * @param {string} tagName - tag to found
* @param {string} [className] - tag's class name * @param {string} [className] - tag's class name
* @param {number} [searchDepth] - count of tags that can be included. For better performance. * @param {number} [searchDepth] - count of tags that can be included. For better performance.
*
* @returns {HTMLElement|null} * @returns {HTMLElement|null}
*/ */
public findParentTag(tagName: string, className?: string, searchDepth = 10): HTMLElement | null { public findParentTag(tagName: string, className?: string, searchDepth = 10): HTMLElement | null {
@ -526,7 +523,7 @@ export default class SelectionUtils {
/** /**
* Expands selection range to the passed parent node * Expands selection range to the passed parent node
* *
* @param {HTMLElement} element - element which contents should be selcted * @param {HTMLElement} element - element which contents should be selected
*/ */
public expandToTag(element: HTMLElement): void { public expandToTag(element: HTMLElement): void {
const selection = window.getSelection(); const selection = window.getSelection();

View file

@ -67,11 +67,11 @@ export enum CommonInternalSettings {
} }
/** /**
* Enum of Tool optoins provided by Block Tool * Enum of Tool options provided by Block Tool
*/ */
export enum InternalBlockToolSettings { export enum InternalBlockToolSettings {
/** /**
* Is linebreaks enabled for Tool * Is line breaks enabled for Tool
*/ */
IsEnabledLineBreaks = 'enableLineBreaks', IsEnabledLineBreaks = 'enableLineBreaks',
/** /**
@ -116,7 +116,7 @@ export enum InternalTuneSettings {
IsTune = 'isTune', IsTune = 'isTune',
} }
export type ToolOptions = Omit<ToolSettings, 'class'> export type ToolOptions = Omit<ToolSettings, 'class'>;
interface ConstructorOptions { interface ConstructorOptions {
name: string; name: string;
@ -174,8 +174,7 @@ export default abstract class BaseTool<Type extends Tool = Tool> {
/** /**
* @class * @class
* * @param {ConstructorOptions} options - Constructor options
* @param {ConstructorOptions} - Constructor options
*/ */
constructor({ constructor({
name, name,

View file

@ -29,7 +29,6 @@ export default class ToolsFactory {
/** /**
* @class * @class
*
* @param config - tools config * @param config - tools config
* @param editorConfig - EditorJS config * @param editorConfig - EditorJS config
* @param api - EditorJS API module * @param api - EditorJS API module

View file

@ -36,7 +36,7 @@ export enum ToolboxEvent {
/** /**
* Available i18n dict keys that should be passed to the constructor * Available i18n dict keys that should be passed to the constructor
*/ */
type toolboxTextLabelsKeys = 'filter' | 'nothingFound'; type ToolboxTextLabelsKeys = 'filter' | 'nothingFound';
/** /**
* Toolbox * Toolbox
@ -70,7 +70,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
/** /**
* Popover instance. There is a util for vertical lists. * Popover instance. There is a util for vertical lists.
*/ */
private popover: Popover; private popover: Popover | undefined;
/** /**
* List of Tools available. Some of them will be shown in the Toolbox * List of Tools available. Some of them will be shown in the Toolbox
@ -80,21 +80,21 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
/** /**
* Text labels used in the Toolbox. Should be passed from the i18n module * Text labels used in the Toolbox. Should be passed from the i18n module
*/ */
private i18nLabels: Record<toolboxTextLabelsKeys, string>; private i18nLabels: Record<ToolboxTextLabelsKeys, string>;
/** /**
* Current module HTML Elements * Current module HTML Elements
*/ */
private nodes: { private nodes: {
toolbox: HTMLElement; toolbox: HTMLElement | null;
} = { } = {
toolbox: null, toolbox: null,
}; };
/** /**
* CSS styles * CSS styles
* *
* @returns {object.<string, string>} * @returns {Object<string, string>}
*/ */
private static get CSS(): { [name: string]: string } { private static get CSS(): { [name: string]: string } {
return { return {
@ -102,11 +102,6 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
}; };
} }
/**
* Id of listener added used to remove it on destroy()
*/
private clickListenerId: string = null;
/** /**
* Toolbox constructor * Toolbox constructor
* *
@ -114,7 +109,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
* @param options.api - Editor API methods * @param options.api - Editor API methods
* @param options.tools - Tools available to check whether some of them should be displayed at the Toolbox or not * @param options.tools - Tools available to check whether some of them should be displayed at the Toolbox or not
*/ */
constructor({ api, tools, i18nLabels }: {api: API; tools: ToolsCollection<BlockTool>; i18nLabels: Record<toolboxTextLabelsKeys, string>}) { constructor({ api, tools, i18nLabels }: {api: API; tools: ToolsCollection<BlockTool>; i18nLabels: Record<ToolboxTextLabelsKeys, string>}) {
super(); super();
this.api = api; this.api = api;
@ -150,8 +145,8 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
/** /**
* Returns true if the Toolbox has the Flipper activated and the Flipper has selected button * Returns true if the Toolbox has the Flipper activated and the Flipper has selected button
*/ */
public hasFocus(): boolean { public hasFocus(): boolean | undefined {
return this.popover.hasFocus(); return this.popover?.hasFocus();
} }
/** /**
@ -165,10 +160,8 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
this.nodes.toolbox = null; this.nodes.toolbox = null;
} }
this.api.listeners.offById(this.clickListenerId);
this.removeAllShortcuts(); this.removeAllShortcuts();
this.popover.off(PopoverEvent.OverlayClicked, this.onOverlayClicked); this.popover?.off(PopoverEvent.OverlayClicked, this.onOverlayClicked);
} }
/** /**
@ -189,7 +182,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
return; return;
} }
this.popover.show(); this.popover?.show();
this.opened = true; this.opened = true;
this.emit(ToolboxEvent.Opened); this.emit(ToolboxEvent.Opened);
} }
@ -198,7 +191,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
* Close Toolbox * Close Toolbox
*/ */
public close(): void { public close(): void {
this.popover.hide(); this.popover?.hide();
this.opened = false; this.opened = false;
this.emit(ToolboxEvent.Closed); this.emit(ToolboxEvent.Closed);
} }
@ -219,31 +212,24 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
*/ */
private onOverlayClicked = (): void => { private onOverlayClicked = (): void => {
this.close(); this.close();
} };
/** /**
* Returns list of tools that enables the Toolbox (by specifying the 'toolbox' getter) * Returns list of tools that enables the Toolbox (by specifying the 'toolbox' getter)
*/ */
@_.cacheable @_.cacheable
private get toolsToBeDisplayed(): BlockTool[] { private get toolsToBeDisplayed(): BlockTool[] {
return Array const result: BlockTool[] = [];
.from(this.tools.values())
.reduce((result, tool) => {
const toolToolboxSettings = tool.toolbox;
if (toolToolboxSettings) { this.tools.forEach((tool) => {
const validToolboxSettings = toolToolboxSettings.filter(item => { const toolToolboxSettings = tool.toolbox;
return this.areToolboxSettingsValid(item, tool.name);
});
result.push({ if (toolToolboxSettings) {
...tool, result.push(tool);
toolbox: validToolboxSettings, }
}); });
}
return result; return result;
}, []);
} }
/** /**
@ -259,7 +245,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
icon: toolboxItem.icon, icon: toolboxItem.icon,
label: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)), label: I18n.t(I18nInternalNS.toolNames, toolboxItem.title || _.capitalize(tool.name)),
name: tool.name, name: tool.name,
onActivate: (e): void => { onActivate: (): void => {
this.toolButtonActivated(tool.name, toolboxItem.data); this.toolButtonActivated(tool.name, toolboxItem.data);
}, },
secondaryLabel: tool.shortcut ? _.beautifyShortcut(tool.shortcut) : '', secondaryLabel: tool.shortcut ? _.beautifyShortcut(tool.shortcut) : '',
@ -267,12 +253,12 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
}; };
return this.toolsToBeDisplayed return this.toolsToBeDisplayed
.reduce((result, tool) => { .reduce<PopoverItem[]>((result, tool) => {
if (Array.isArray(tool.toolbox)) { if (Array.isArray(tool.toolbox)) {
tool.toolbox.forEach(item => { tool.toolbox.forEach(item => {
result.push(toPopoverItem(item, tool)); result.push(toPopoverItem(item, tool));
}); });
} else { } else if (tool.toolbox !== undefined) {
result.push(toPopoverItem(tool.toolbox, tool)); result.push(toPopoverItem(tool.toolbox, tool));
} }
@ -280,29 +266,6 @@ export default class Toolbox extends EventsDispatcher<ToolboxEvent> {
}, []); }, []);
} }
/**
* Validates tool's toolbox settings
*
* @param toolToolboxSettings - item to validate
* @param toolName - name of the tool used in console warning if item is not valid
*/
private areToolboxSettingsValid(toolToolboxSettings: ToolboxConfigEntry, toolName: string): boolean {
/**
* Skip tools that don't pass 'toolbox' property
*/
if (!toolToolboxSettings) {
return false;
}
if (toolToolboxSettings && !toolToolboxSettings.icon) {
_.log('Toolbar icon is missed. Tool %o skipped', 'warn', toolName);
return false;
}
return true;
}
/** /**
* Iterate all tools and enable theirs shortcuts if specified * Iterate all tools and enable theirs shortcuts if specified
*/ */

View file

@ -24,7 +24,6 @@ declare const VERSION: string;
* @typedef {object} ChainData * @typedef {object} ChainData
* @property {object} data - data that will be passed to the success or fallback * @property {object} data - data that will be passed to the success or fallback
* @property {Function} function - function's that must be called asynchronously * @property {Function} function - function's that must be called asynchronously
*
* @interface ChainData * @interface ChainData
*/ */
export interface ChainData { export interface ChainData {
@ -38,7 +37,7 @@ export interface ChainData {
*/ */
/** /**
* Returns basic keycodes as constants * Returns basic key codes as constants
* *
* @returns {{}} * @returns {{}}
*/ */
@ -178,7 +177,6 @@ export const logLabeled = _log.bind(window, true);
* Return string representation of the object type * Return string representation of the object type
* *
* @param {*} object - object to get type * @param {*} object - object to get type
*
* @returns {string} * @returns {string}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -190,7 +188,6 @@ export function typeOf(object: any): string {
* Check if passed variable is a function * Check if passed variable is a function
* *
* @param {*} fn - function to check * @param {*} fn - function to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -202,7 +199,6 @@ export function isFunction(fn: any): fn is (...args: any[]) => any {
* Checks if passed argument is an object * Checks if passed argument is an object
* *
* @param {*} v - object to check * @param {*} v - object to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -214,7 +210,6 @@ export function isObject(v: any): v is object {
* Checks if passed argument is a string * Checks if passed argument is a string
* *
* @param {*} v - variable to check * @param {*} v - variable to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -226,7 +221,6 @@ export function isString(v: any): v is string {
* Checks if passed argument is boolean * Checks if passed argument is boolean
* *
* @param {*} v - variable to check * @param {*} v - variable to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -238,7 +232,6 @@ export function isBoolean(v: any): v is boolean {
* Checks if passed argument is number * Checks if passed argument is number
* *
* @param {*} v - variable to check * @param {*} v - variable to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -250,7 +243,6 @@ export function isNumber(v: any): v is number {
* Checks if passed argument is undefined * Checks if passed argument is undefined
* *
* @param {*} v - variable to check * @param {*} v - variable to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -262,7 +254,6 @@ export function isUndefined(v: any): v is undefined {
* Check if passed function is a class * Check if passed function is a class
* *
* @param {Function} fn - function to check * @param {Function} fn - function to check
*
* @returns {boolean} * @returns {boolean}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -274,7 +265,6 @@ export function isClass(fn: any): boolean {
* Checks if object is empty * Checks if object is empty
* *
* @param {object} object - object to check * @param {object} object - object to check
*
* @returns {boolean} * @returns {boolean}
*/ */
export function isEmpty(object: object): boolean { export function isEmpty(object: object): boolean {
@ -296,22 +286,23 @@ export function isPromise(object: any): object is Promise<any> {
return Promise.resolve(object) === object; return Promise.resolve(object) === object;
} }
/* eslint-disable @typescript-eslint/no-magic-numbers */
/** /**
* Returns true if passed key code is printable (a-Z, 0-9, etc) character. * Returns true if passed key code is printable (a-Z, 0-9, etc) character.
* *
* @param {number} keyCode - key code * @param {number} keyCode - key code
*
* @returns {boolean} * @returns {boolean}
*/ */
export function isPrintableKey(keyCode: number): boolean { export function isPrintableKey(keyCode: number): boolean {
return (keyCode > 47 && keyCode < 58) || // number keys return (keyCode > 47 && keyCode < 58) || // number keys
keyCode === 32 || keyCode === 13 || // Spacebar & return key(s) keyCode === 32 || keyCode === 13 || // Space bar & return key(s)
keyCode === 229 || // processing key input for certain languages — Chinese, Japanese, etc. keyCode === 229 || // processing key input for certain languages — Chinese, Japanese, etc.
(keyCode > 64 && keyCode < 91) || // letter keys (keyCode > 64 && keyCode < 91) || // letter keys
(keyCode > 95 && keyCode < 112) || // Numpad keys (keyCode > 95 && keyCode < 112) || // Numpad keys
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
(keyCode > 218 && keyCode < 223); // [\]' (in order) (keyCode > 218 && keyCode < 223); // [\]' (in order)
} }
/* eslint-enable @typescript-eslint/no-magic-numbers */
/** /**
* Fires a promise sequence asynchronously * Fires a promise sequence asynchronously
@ -319,7 +310,6 @@ export function isPrintableKey(keyCode: number): boolean {
* @param {ChainData[]} chains - list or ChainData's * @param {ChainData[]} chains - list or ChainData's
* @param {Function} success - success callback * @param {Function} success - success callback
* @param {Function} fallback - callback that fires in case of errors * @param {Function} fallback - callback that fires in case of errors
*
* @returns {Promise} * @returns {Promise}
*/ */
export async function sequence( export async function sequence(
@ -333,10 +323,8 @@ export async function sequence(
* Decorator * Decorator
* *
* @param {ChainData} chainData - Chain data * @param {ChainData} chainData - Chain data
*
* @param {Function} successCallback - success callback * @param {Function} successCallback - success callback
* @param {Function} fallbackCallback - fail callback * @param {Function} fallbackCallback - fail callback
*
* @returns {Promise} * @returns {Promise}
*/ */
async function waitNextBlock( async function waitNextBlock(
@ -370,7 +358,6 @@ export async function sequence(
* Make array from array-like collection * Make array from array-like collection
* *
* @param {ArrayLike} collection - collection to convert to array * @param {ArrayLike} collection - collection to convert to array
*
* @returns {Array} * @returns {Array}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -400,7 +387,6 @@ export function delay(method: (...args: any[]) => any, timeout: number) {
* Get file extension * Get file extension
* *
* @param {File} file - file * @param {File} file - file
*
* @returns {string} * @returns {string}
*/ */
export function getFileExtension(file: File): string { export function getFileExtension(file: File): string {
@ -411,7 +397,6 @@ export function getFileExtension(file: File): string {
* Check if string is MIME type * Check if string is MIME type
* *
* @param {string} type - string to check * @param {string} type - string to check
*
* @returns {boolean} * @returns {boolean}
*/ */
export function isValidMimeType(type: string): boolean { export function isValidMimeType(type: string): boolean {
@ -492,6 +477,7 @@ export function throttle(func, wait, options: {leading?: boolean; trailing?: boo
const remaining = wait - (now - previous); const remaining = wait - (now - previous);
// eslint-disable-next-line @typescript-eslint/no-this-alias
context = this; context = this;
// eslint-disable-next-line prefer-rest-params // eslint-disable-next-line prefer-rest-params
@ -551,7 +537,7 @@ export function getUserOS(): {[key: string]: boolean} {
linux: false, linux: false,
}; };
const userOS = Object.keys(OS).find((os: string) => navigator.appVersion.toLowerCase().indexOf(os) !== -1); const userOS = Object.keys(OS).find((os: string) => window.navigator.appVersion.toLowerCase().indexOf(os) !== -1);
if (userOS) { if (userOS) {
OS[userOS] = true; OS[userOS] = true;
@ -566,7 +552,6 @@ export function getUserOS(): {[key: string]: boolean} {
* Capitalizes first letter of the string * Capitalizes first letter of the string
* *
* @param {string} text - text to capitalize * @param {string} text - text to capitalize
*
* @returns {string} * @returns {string}
*/ */
export function capitalize(text: string): string { export function capitalize(text: string): string {
@ -610,7 +595,6 @@ export function deepMerge<T extends object>(target, ...sources): T {
* To detect touch devices more carefully, use 'touchstart' event listener * To detect touch devices more carefully, use 'touchstart' event listener
* *
* @see http://www.stucox.com/blog/you-cant-detect-a-touchscreen/ * @see http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
*
* @returns {boolean} * @returns {boolean}
*/ */
export const isTouchSupported: boolean = 'ontouchstart' in document.documentElement; export const isTouchSupported: boolean = 'ontouchstart' in document.documentElement;
@ -674,7 +658,9 @@ export function getValidUrl(url: string): string {
* @returns {string} * @returns {string}
*/ */
export function generateBlockId(): string { export function generateBlockId(): string {
return nanoid(10); const idLen = 10;
return nanoid(idLen);
} }
/** /**
@ -690,11 +676,10 @@ export function openTab(url: string): void {
* Returns random generated identifier * Returns random generated identifier
* *
* @param {string} prefix - identifier prefix * @param {string} prefix - identifier prefix
*
* @returns {string} * @returns {string}
*/ */
export function generateId(prefix = ''): string { export function generateId(prefix = ''): string {
// tslint:disable-next-line:no-bitwise // eslint-disable-next-line @typescript-eslint/no-magic-numbers
return `${prefix}${(Math.floor(Math.random() * 1e8)).toString(16)}`; return `${prefix}${(Math.floor(Math.random() * 1e8)).toString(16)}`;
} }
@ -761,7 +746,12 @@ export function cacheable<Target, Value, Arguments extends unknown[] = unknown[]
} }
return descriptor; return descriptor;
}; }
/**
* All screens below this width will be treated as mobile;
*/
export const mobileScreenBreakpoint = 650;
/** /**
* Deep copy function. * Deep copy function.
@ -790,7 +780,7 @@ export function deepCopy<T extends Record<keyof T, unknown>>(target: T): T {
* True if screen has mobile size * True if screen has mobile size
*/ */
export function isMobileScreen(): boolean { export function isMobileScreen(): boolean {
return window.matchMedia('(max-width: 650px)').matches; return window.matchMedia(`(max-width: ${mobileScreenBreakpoint}px)`).matches;
} }
/** /**

View file

@ -7,9 +7,7 @@ import { isEmpty } from '../utils';
* - {Function} on - appends subscriber to the event. If event doesn't exist - creates new one * - {Function} on - appends subscriber to the event. If event doesn't exist - creates new one
* - {Function} emit - fires all subscribers with data * - {Function} emit - fires all subscribers with data
* - {Function off - unsubscribes callback * - {Function off - unsubscribes callback
*
* @version 1.0.0 * @version 1.0.0
*
* @typedef {Events} Events * @typedef {Events} Events
* @property {object} subscribers - all subscribers grouped by event name * @property {object} subscribers - all subscribers grouped by event name
*/ */

View file

@ -62,8 +62,6 @@ export default class Listeners {
* @param {string} eventType - event type * @param {string} eventType - event type
* @param {Function} handler - method that will be fired on event * @param {Function} handler - method that will be fired on event
* @param {boolean|AddEventListenerOptions} options - useCapture or {capture, passive, once} * @param {boolean|AddEventListenerOptions} options - useCapture or {capture, passive, once}
*
* @returns {string}
*/ */
public on( public on(
element: EventTarget, element: EventTarget,
@ -104,6 +102,7 @@ export default class Listeners {
element: EventTarget, element: EventTarget,
eventType: string, eventType: string,
handler?: (event: Event) => void, handler?: (event: Event) => void,
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
options?: boolean | AddEventListenerOptions options?: boolean | AddEventListenerOptions
): void { ): void {
const existingListeners = this.findAll(element, eventType, handler); const existingListeners = this.findAll(element, eventType, handler);
@ -140,7 +139,6 @@ export default class Listeners {
* @param {EventTarget} element - event target * @param {EventTarget} element - event target
* @param {string} [eventType] - event type * @param {string} [eventType] - event type
* @param {Function} [handler] - event handler * @param {Function} [handler] - event handler
*
* @returns {ListenerData|null} * @returns {ListenerData|null}
*/ */
public findOne(element: EventTarget, eventType?: string, handler?: (event: Event) => void): ListenerData { public findOne(element: EventTarget, eventType?: string, handler?: (event: Event) => void): ListenerData {
@ -155,7 +153,6 @@ export default class Listeners {
* @param {EventTarget} element - event target * @param {EventTarget} element - event target
* @param {string} eventType - event type * @param {string} eventType - event type
* @param {Function} handler - event handler * @param {Function} handler - event handler
*
* @returns {ListenerData[]} * @returns {ListenerData[]}
*/ */
public findAll(element: EventTarget, eventType?: string, handler?: (event: Event) => void): ListenerData[] { public findAll(element: EventTarget, eventType?: string, handler?: (event: Event) => void): ListenerData[] {
@ -195,7 +192,6 @@ export default class Listeners {
* Search method: looks for listener by passed element * Search method: looks for listener by passed element
* *
* @param {EventTarget} element - searching element * @param {EventTarget} element - searching element
*
* @returns {Array} listeners that found on element * @returns {Array} listeners that found on element
*/ */
private findByEventTarget(element: EventTarget): ListenerData[] { private findByEventTarget(element: EventTarget): ListenerData[] {
@ -210,7 +206,6 @@ export default class Listeners {
* Search method: looks for listener by passed event type * Search method: looks for listener by passed event type
* *
* @param {string} eventType - event type * @param {string} eventType - event type
*
* @returns {ListenerData[]} listeners that found on element * @returns {ListenerData[]} listeners that found on element
*/ */
private findByType(eventType: string): ListenerData[] { private findByType(eventType: string): ListenerData[] {
@ -225,7 +220,6 @@ export default class Listeners {
* Search method: looks for listener by passed handler * Search method: looks for listener by passed handler
* *
* @param {Function} handler - event handler * @param {Function} handler - event handler
*
* @returns {ListenerData[]} listeners that found on element * @returns {ListenerData[]} listeners that found on element
*/ */
private findByHandler(handler: (event: Event) => void): ListenerData[] { private findByHandler(handler: (event: Event) => void): ListenerData[] {
@ -240,7 +234,6 @@ export default class Listeners {
* Returns listener data found by id * Returns listener data found by id
* *
* @param {string} id - listener identifier * @param {string} id - listener identifier
*
* @returns {ListenerData} * @returns {ListenerData}
*/ */
private findById(id: string): ListenerData { private findById(id: string): ListenerData {

View file

@ -61,12 +61,12 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
nothingFound: HTMLElement; nothingFound: HTMLElement;
overlay: HTMLElement; overlay: HTMLElement;
} = { } = {
wrapper: null, wrapper: null,
popover: null, popover: null,
items: null, items: null,
nothingFound: null, nothingFound: null,
overlay: null, overlay: null,
} };
/** /**
* Additional wrapper's class name * Additional wrapper's class name
@ -150,7 +150,7 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
/** /**
* ScrollLocker instance * ScrollLocker instance
*/ */
private scrollLocker = new ScrollLocker() private scrollLocker = new ScrollLocker();
/** /**
* Editor container element * Editor container element
@ -236,6 +236,7 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
if (this.searchable) { if (this.searchable) {
setTimeout(() => { setTimeout(() => {
this.search.focus(); this.search.focus();
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 100); }, 100);
} }
@ -436,11 +437,9 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
innerHTML: item.label, innerHTML: item.label,
}); });
if (item.icon) { el.appendChild(Dom.make('div', Popover.CSS.itemIcon, {
el.appendChild(Dom.make('div', Popover.CSS.itemIcon, { innerHTML: item.icon || item.name.substring(0, 1).toUpperCase(),
innerHTML: item.icon, }));
}));
}
el.appendChild(label); el.appendChild(label);
@ -601,7 +600,7 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
} }
el.classList.remove(Popover.CSS.itemNoHover); el.classList.remove(Popover.CSS.itemNoHover);
} };
/** /**
* Removes class responsible for special focus behavior on an item * Removes class responsible for special focus behavior on an item
@ -621,7 +620,7 @@ export default class Popover extends EventsDispatcher<PopoverEvent> {
*/ */
private onFlip = (): void => { private onFlip = (): void => {
this.disableSpecialHoverAndFocusBehavior(); this.disableSpecialHoverAndFocusBehavior();
} };
/** /**
* Reactivates flipper instance. * Reactivates flipper instance.

View file

@ -5,7 +5,6 @@
* Clears HTML from taint tags * Clears HTML from taint tags
* *
* @version 2.0.0 * @version 2.0.0
*
* @example * @example
* *
* clean(yourTaintString, yourConfig); * clean(yourTaintString, yourConfig);
@ -18,7 +17,6 @@ import * as _ from '../utils';
/** /**
* @typedef {object} SanitizerConfig * @typedef {object} SanitizerConfig
* @property {object} tags - define tags restrictions * @property {object} tags - define tags restrictions
*
* @example * @example
* *
* tags : { * tags : {
@ -65,7 +63,6 @@ export function sanitizeBlocks(
* *
* @param {string} taintString - taint string * @param {string} taintString - taint string
* @param {SanitizerConfig} customConfig - allowed tags * @param {SanitizerConfig} customConfig - allowed tags
*
* @returns {string} clean HTML * @returns {string} clean HTML
*/ */
export function clean(taintString: string, customConfig: SanitizerConfig = {} as SanitizerConfig): string { export function clean(taintString: string, customConfig: SanitizerConfig = {} as SanitizerConfig): string {
@ -163,7 +160,6 @@ function cleanObject(object: object, rules: SanitizerConfig|{[field: string]: Sa
* *
* @param {string} taintString - string to clean * @param {string} taintString - string to clean
* @param {SanitizerConfig|boolean} rule - sanitizer rule * @param {SanitizerConfig|boolean} rule - sanitizer rule
*
* @returns {string} * @returns {string}
*/ */
function cleanOneItem(taintString: string, rule: SanitizerConfig|boolean): string { function cleanOneItem(taintString: string, rule: SanitizerConfig|boolean): string {

View file

@ -10,12 +10,12 @@ export default class ScrollLocker {
private static CSS = { private static CSS = {
scrollLocked: 'ce-scroll-locked', scrollLocked: 'ce-scroll-locked',
scrollLockedHard: 'ce-scroll-locked--hard', scrollLockedHard: 'ce-scroll-locked--hard',
} };
/** /**
* Stores scroll position, used for hard scroll lock * Stores scroll position, used for hard scroll lock
*/ */
private scrollPosition: null|number private scrollPosition: null|number;
/** /**
* Locks body element scroll * Locks body element scroll

View file

@ -1,5 +1,6 @@
import Dom from '../dom'; import Dom from '../dom';
import Listeners from './listeners'; import Listeners from './listeners';
import { IconSearch } from '@codexteam/icons';
/** /**
* Item that could be searched * Item that could be searched
@ -113,14 +114,14 @@ export default class SearchInput {
private render(placeholder: string): void { private render(placeholder: string): void {
this.wrapper = Dom.make('div', SearchInput.CSS.wrapper); this.wrapper = Dom.make('div', SearchInput.CSS.wrapper);
const iconWrapper = Dom.make('div', SearchInput.CSS.icon); const iconWrapper = Dom.make('div', SearchInput.CSS.icon, {
const icon = Dom.svg('search', 16, 16); innerHTML: IconSearch,
});
this.input = Dom.make('input', SearchInput.CSS.input, { this.input = Dom.make('input', SearchInput.CSS.input, {
placeholder, placeholder,
}) as HTMLInputElement; }) as HTMLInputElement;
iconWrapper.appendChild(icon);
this.wrapper.appendChild(iconWrapper); this.wrapper.appendChild(iconWrapper);
this.wrapper.appendChild(this.input); this.wrapper.appendChild(this.input);

View file

@ -94,7 +94,6 @@ class Shortcuts {
* *
* @param element - Element shorcut is set for * @param element - Element shorcut is set for
* @param shortcut - shortcut name * @param shortcut - shortcut name
*
* @returns {number} index - shortcut index if exist * @returns {number} index - shortcut index if exist
*/ */
private findShortcut(element: Element, shortcut: string): Shortcut | void { private findShortcut(element: Element, shortcut: string): Shortcut | void {

View file

@ -63,20 +63,7 @@
} }
&__icon { &__icon {
display: inline-flex; @apply --tool-icon;
width: 20px;
height: 20px;
border: 1px solid var(--color-gray-border);
border-radius: 3px;
align-items: center;
justify-content: center;
margin-right: 10px;
background: #fff;
svg {
width: 11px;
height: 11px;
}
} }
&--last { &--last {

View file

@ -40,10 +40,23 @@
*/ */
.cdx-settings-button { .cdx-settings-button {
@apply --toolbar-button; @apply --toolbar-button;
min-width: var(--toolbox-buttons-size);
min-height: var(--toolbox-buttons-size);
&--active { &--active {
color: var(--color-active-icon); color: var(--color-active-icon);
} }
svg {
width: auto;
height: auto;
}
@media (--mobile) {
width: var(--toolbox-buttons-size--mobile);
height: var(--toolbox-buttons-size--mobile);
border-radius: 8px;
}
} }
/** /**
@ -91,9 +104,11 @@
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
&:hover { @media (--can-hover) {
background: #FBFCFE; &:hover {
box-shadow: 0 1px 3px 0 rgba(18,30,57,0.08); background: #FBFCFE;
box-shadow: 0 1px 3px 0 rgba(18,30,57,0.08);
}
} }
svg { svg {

View file

@ -1,6 +1,8 @@
.ce-inline-toolbar { .ce-inline-toolbar {
--y-offset: 8px;
@apply --overlay-pane; @apply --overlay-pane;
transform: translateX(-50%) translateY(8px) scale(0.9); transform: translateX(-50%) translateY(8px) scale(0.94);
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: transform 150ms ease, opacity 250ms ease; transition: transform 150ms ease, opacity 250ms ease;
@ -16,7 +18,7 @@
} }
&--left-oriented { &--left-oriented {
transform: translateX(-23px) translateY(8px) scale(0.9); transform: translateX(-23px) translateY(8px) scale(0.94);
} }
&--left-oriented&--showed { &--left-oriented&--showed {
@ -24,7 +26,7 @@
} }
&--right-oriented { &--right-oriented {
transform: translateX(-100%) translateY(8px) scale(0.9); transform: translateX(-100%) translateY(8px) scale(0.94);
margin-left: 23px; margin-left: 23px;
} }
@ -50,35 +52,32 @@
} }
&__dropdown { &__dropdown {
display: inline-flex; display: flex;
height: var(--toolbar-buttons-size); padding: 6px;
padding: 0 9px 0 10px;
margin: 0 6px 0 -6px; margin: 0 6px 0 -6px;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
border-right: 1px solid var(--color-gray-border); border-right: 1px solid var(--color-gray-border);
box-sizing: border-box;
&:hover { @media (--can-hover) {
background: var(--bg-light); &:hover {
background: var(--bg-light);
}
} }
&--hidden { &--hidden {
display: none; display: none;
} }
&-content{ &-content,
&-arrow {
display: flex; display: flex;
font-weight: 500;
font-size: 14px;
svg { svg {
height: 12px; width: var(--icon-size);
height: var(--icon-size);
} }
} }
.icon--toggler-down {
margin-left: 4px;
}
} }
&__shortcut { &__shortcut {
@ -90,19 +89,10 @@
.ce-inline-tool { .ce-inline-tool {
@apply --toolbar-button; @apply --toolbar-button;
border-radius: 0; border-radius: 0;
line-height: normal; line-height: normal;
width: auto;
padding: 0 5px !important;
min-width: 24px;
&:not(:last-of-type) {
margin-right: 2px;
}
.icon {
height: 12px;
}
&--link { &--link {
.icon--unlink { .icon--unlink {
@ -132,6 +122,13 @@
display: none; display: none;
font-weight: 500; font-weight: 500;
border-top: 1px solid rgba(201,201,204,.48); border-top: 1px solid rgba(201,201,204,.48);
-webkit-appearance: none;
font-family: inherit;
@media (--mobile){
font-size: 15px;
font-weight: 500;
}
&::placeholder { &::placeholder {
color: var(--grayText); color: var(--grayText);

View file

@ -17,11 +17,10 @@
justify-content: center; justify-content: center;
margin-right: var(--icon-margin-right); margin-right: var(--icon-margin-right);
.icon { svg {
width: 14px; width: var(--icon-size);
height: 14px; height: var(--icon-size);
color: var(--grayText); color: var(--grayText);
flex-shrink: 0;
} }
} }

View file

@ -34,28 +34,5 @@
&--selected { &--selected {
color: var(--color-active-icon); color: var(--color-active-icon);
} }
&--delete {
transition: background-color 300ms ease;
will-change: background-color;
.icon {
transition: transform 200ms ease-out;
will-change: transform;
}
}
&--confirm {
background-color: var(--color-confirm) !important;
color: #fff;
&:hover {
background-color: color-mod(var(--color-confirm) blackness(+5%)) !important;
}
.icon {
transform: rotate(90deg);
}
}
} }
} }

View file

@ -57,12 +57,12 @@
&__settings-btn { &__settings-btn {
@apply --toolbox-button; @apply --toolbox-button;
margin-left: 5px; margin-left: 3px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
@media (--not-mobile){ @media (--not-mobile){
width: 18px; width: 24px;
} }
&--hidden { &--hidden {
@ -74,6 +74,14 @@
position: static; position: static;
} }
} }
&__plus,
&__settings-btn {
svg {
width: 24px;
height: 24px;
}
}
} }
/** /**

View file

@ -101,10 +101,12 @@
} }
svg { svg {
fill: currentColor;
vertical-align: middle;
max-height: 100%; max-height: 100%;
} }
path {
stroke: currentColor;
}
} }
/** /**
@ -137,4 +139,4 @@
top: calc(-1 * var(--window-scroll-offset)); top: calc(-1 * var(--window-scroll-offset));
position: fixed; position: fixed;
width: 100%; width: 100%;
} }

View file

@ -49,17 +49,19 @@
*/ */
--narrow-mode-right-padding: 50px; --narrow-mode-right-padding: 50px;
/**
* Toolbar buttons height and width
*/
--toolbar-buttons-size: 34px;
/** /**
* Toolbar Plus Button and Toolbox buttons height and width * Toolbar Plus Button and Toolbox buttons height and width
*/ */
--toolbox-buttons-size: 26px; --toolbox-buttons-size: 26px;
--toolbox-buttons-size--mobile: 36px; --toolbox-buttons-size--mobile: 36px;
/**
* Size of svg icons got from the CodeX Icons pack
*/
--icon-size: 20px;
--icon-size--mobile: 28px;
/** /**
* The main `.cdx-block` wrapper has such vertical paddings * The main `.cdx-block` wrapper has such vertical paddings
* And the Block Actions toggler too * And the Block Actions toggler too
@ -82,11 +84,6 @@
border-radius: 6px; border-radius: 6px;
z-index: 2; z-index: 2;
@media (--mobile){
box-shadow: 0 8px 6px -6px rgb(33 48 73 / 19%);
border-bottom-color: #c7c7c7;
}
&--left-oriented { &--left-oriented {
&::before { &::before {
left: 15px; left: 15px;
@ -159,22 +156,31 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: var(--toolbar-buttons-size);
height: var(--toolbar-buttons-size); padding: 6px 1px;
line-height: var(--toolbar-buttons-size);
padding: 0 !important;
text-align: center;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
border: 0; border: 0;
outline: none; outline: none;
background-color: transparent; background-color: transparent;
vertical-align: bottom; vertical-align: bottom;
color: #000; color: inherit;
margin: 0; margin: 0;
&:hover { svg {
background-color: var(--bg-light); width: var(--icon-size);
height: var(--icon-size);
@media (--mobile) {
width: var(--icon-size--mobile);
height: var(--icon-size--mobile);
}
}
@media (--can-hover) {
&:hover {
background-color: var(--bg-light);
}
} }
&--active { &--active {
@ -234,15 +240,20 @@
flex-shrink: 0; flex-shrink: 0;
margin-right: 10px; margin-right: 10px;
svg {
width: var(--icon-size);
height: var(--icon-size);
}
@media (--mobile) { @media (--mobile) {
width: var(--toolbox-buttons-size--mobile); width: var(--toolbox-buttons-size--mobile);
height: var(--toolbox-buttons-size--mobile); height: var(--toolbox-buttons-size--mobile);
border-radius: 8px; border-radius: 8px;
}
svg { svg {
width: 12px; width: var(--icon-size--mobile);
height: 12px; height: var(--icon-size--mobile);
}
} }
} }
} }

@ -1 +1 @@
Subproject commit 21cbdea6e5e61094b046f47e8cb423a817cec3ed Subproject commit 6e45413ccdfd021f1800eb6e5bf7440184d5ab7c

View file

@ -92,7 +92,7 @@ export default class Stub implements BlockTool {
*/ */
private make(): HTMLElement { private make(): HTMLElement {
const wrapper = $.make('div', this.CSS.wrapper); const wrapper = $.make('div', this.CSS.wrapper);
const icon = $.svg('sad-face', 52, 52); const icon = `<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52"><path fill="#D76B6B" fill-rule="nonzero" d="M26 52C11.64 52 0 40.36 0 26S11.64 0 26 0s26 11.64 26 26-11.64 26-26 26zm0-3.25c12.564 0 22.75-10.186 22.75-22.75S38.564 3.25 26 3.25 3.25 13.436 3.25 26 13.436 48.75 26 48.75zM15.708 33.042a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm23.834 0a2.167 2.167 0 1 1 0-4.334 2.167 2.167 0 0 1 0 4.334zm-15.875 5.452a1.083 1.083 0 1 1-1.834-1.155c1.331-2.114 3.49-3.179 6.334-3.179 2.844 0 5.002 1.065 6.333 3.18a1.083 1.083 0 1 1-1.833 1.154c-.913-1.45-2.366-2.167-4.5-2.167s-3.587.717-4.5 2.167z"/></svg>`;
const infoContainer = $.make('div', this.CSS.info); const infoContainer = $.make('div', this.CSS.info);
const title = $.make('div', this.CSS.title, { const title = $.make('div', this.CSS.title, {
textContent: this.title, textContent: this.title,
@ -101,7 +101,7 @@ export default class Stub implements BlockTool {
textContent: this.subtitle, textContent: this.subtitle,
}); });
wrapper.appendChild(icon); wrapper.innerHTML = icon;
infoContainer.appendChild(title); infoContainer.appendChild(title);
infoContainer.appendChild(subtitle); infoContainer.appendChild(subtitle);

View file

@ -1,7 +0,0 @@
/**
* Allow to import .svg from components/modules/ui from TypeScript file
*/
declare module '*.svg' {
const content: string;
export default content;
}

View file

@ -126,3 +126,33 @@ Cypress.Commands.add('render', { prevSubject: true }, async (subject: EditorJS,
return subject; return subject;
}); });
/**
* Select passed text in element
* Note. Previous subject should have 'textNode' as firstChild
*
* Usage
* cy.get('[data-cy=editorjs]')
* .find('.ce-paragraph')
* .selectText('block te')
*
* @param text - text to select
*/
Cypress.Commands.add('selectText', {
prevSubject: true,
}, (subject, text: string) => {
const el = subject[0];
const document = el.ownerDocument;
const range = document.createRange();
const textNode = el.firstChild;
const selectionPositionStart = textNode.textContent.indexOf(text);
const selectionPositionEnd = selectionPositionStart + text.length;
range.setStart(textNode, selectionPositionStart);
range.setEnd(textNode, selectionPositionEnd);
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
return subject;
});

View file

@ -47,6 +47,19 @@ declare global {
* @param data data to render * @param data data to render
*/ */
render(data: OutputData): Chainable<EditorJS>; render(data: OutputData): Chainable<EditorJS>;
/**
* Select passed text in element
* Note. Previous subject should have 'textNode' as firstChild
*
* Usage
* cy.get('[data-cy=editorjs]')
* .find('.ce-paragraph')
* .selectText('block te')
*
* @param text - text to select
*/
selectText(text: string): Chainable<Subject>;
} }
interface ApplicationWindow { interface ApplicationWindow {

View file

@ -1,4 +1,5 @@
import { BlockMutationType } from '../../../../types/events/block/mutation-type'; import { BlockMutationType } from '../../../../types/events/block/mutation-type';
import EditorJS from '../../../../types';
/** /**
* There will be described test cases of BlockAPI * There will be described test cases of BlockAPI
@ -22,18 +23,22 @@ describe('BlockAPI', () => {
*/ */
const EditorJSApiMock = Cypress.sinon.match.any; const EditorJSApiMock = Cypress.sinon.match.any;
beforeEach(() => { beforeEach(function () {
if (this && this.editorInstance) { const config = {
data: editorDataMock,
onChange: (): void => {
console.log('something changed');
},
};
cy.createEditor(config).as('editorInstance');
cy.spy(config, 'onChange').as('onChange');
});
afterEach(function () {
if (this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} else {
const config = {
data: editorDataMock,
onChange: (): void => { console.log('something changed'); },
};
cy.createEditor(config).as('editorInstance');
cy.spy(config, 'onChange').as('onChange');
} }
}); });
@ -45,8 +50,8 @@ describe('BlockAPI', () => {
* Check that blocks.dispatchChange() triggers Editor 'onChange' callback * Check that blocks.dispatchChange() triggers Editor 'onChange' callback
*/ */
it('should trigger onChange with corresponded block', () => { it('should trigger onChange with corresponded block', () => {
cy.get('@editorInstance').then(async (editor: any) => { cy.get('@editorInstance').then(async (editor: unknown) => {
const block = editor.blocks.getById(firstBlock.id); const block = (editor as EditorJS).blocks.getById(firstBlock.id);
block.dispatchChange(); block.dispatchChange();
@ -59,5 +64,4 @@ describe('BlockAPI', () => {
}); });
}); });
}); });
}); });

View file

@ -16,13 +16,15 @@ describe('api.blocks', () => {
], ],
}; };
beforeEach(() => { beforeEach(function () {
if (this && this.editorInstance) { cy.createEditor({
data: editorDataMock,
}).as('editorInstance');
});
afterEach(function () {
if (this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} else {
cy.createEditor({
data: editorDataMock,
}).as('editorInstance');
} }
}); });
@ -117,4 +119,26 @@ describe('api.blocks', () => {
}); });
}); });
}); });
/**
* api.blocks.insert(type, data, config, index, needToFocus, replace, id)
*/
describe('.insert()', function () {
it('should preserve block id if it is passed', function () {
cy.get('@editorInstance').then(async (editor: any) => {
const type = 'paragraph';
const data = { text: 'codex' };
const config = undefined;
const index = undefined;
const needToFocus = undefined;
const replace = undefined;
const id = 'test-id-123';
const block = editor.blocks.insert(type, data, config, index, needToFocus, replace, id);
expect(block).not.to.be.undefined;
expect(block.id).to.be.eq(id);
});
});
});
}); });

View file

@ -1,5 +1,6 @@
import { ToolboxConfig, BlockToolData, ToolboxConfigEntry } from '../../../../types'; import { ToolboxConfig, BlockToolData, ToolboxConfigEntry, PasteConfig } from '../../../../types';
import { TunesMenuConfig } from '../../../../types/tools'; import EditorJS from '../../../../types';
import { HTMLPasteEvent, TunesMenuConfig } from '../../../../types/tools';
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
@ -97,22 +98,22 @@ describe('Editor Tools Api', () => {
.should('contain.text', TestTool.toolbox[1].title); .should('contain.text', TestTool.toolbox[1].title);
}); });
it('should insert block with overriden data on entry click in case toolbox entry provides data overrides', () => { it('should insert block with overridden data on entry click in case toolbox entry provides data overrides', () => {
const text = 'Text'; const text = 'Text';
const dataOverrides = { const dataOverrides = {
testProp: 'new value', testProp: 'new value',
}; };
/** /**
* Tool with default data to be overriden * Tool with default data to be overridden
*/ */
class TestTool { class TestTool {
private _data = { private _data = {
testProp: 'default value', testProp: 'default value',
} };
/** /**
* Tool contructor * Tool constructor
* *
* @param data - previously saved data * @param data - previously saved data
*/ */
@ -121,7 +122,7 @@ describe('Editor Tools Api', () => {
} }
/** /**
* Returns toolbox config as list of entries with overriden data * Returns toolbox config as list of entries with overridden data
*/ */
public static get toolbox(): ToolboxConfig { public static get toolbox(): ToolboxConfig {
return [ return [
@ -182,8 +183,8 @@ describe('Editor Tools Api', () => {
.type(text); .type(text);
cy.get('@editorInstance') cy.get('@editorInstance')
.then(async (editor: any) => { .then(async (editor: unknown) => {
const editorData = await editor.save(); const editorData = await (editor as EditorJS).save();
expect(editorData.blocks[0].data).to.be.deep.eq({ expect(editorData.blocks[0].data).to.be.deep.eq({
...dataOverrides, ...dataOverrides,
@ -191,86 +192,9 @@ describe('Editor Tools Api', () => {
}); });
}); });
}); });
it('should not display tool in toolbox if the tool has single toolbox entry configured and it has icon missing', () => {
/**
* Tool with one of the toolbox entries with icon missing
*/
class TestTool {
/**
* Returns toolbox config as list of entries one of which has missing icon
*/
public static get toolbox(): ToolboxConfig {
return {
title: 'Entry 2',
};
}
}
cy.createEditor({
tools: {
testTool: TestTool,
},
}).as('editorInstance');
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-toolbar__plus')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-popover__item[data-item-name=testTool]')
.should('not.exist');
});
it('should skip toolbox entries that have no icon', () => {
const skippedEntryTitle = 'Entry 2';
/**
* Tool with one of the toolbox entries with icon missing
*/
class TestTool {
/**
* Returns toolbox config as list of entries one of which has missing icon
*/
public static get toolbox(): ToolboxConfig {
return [
{
title: 'Entry 1',
icon: ICON,
},
{
title: skippedEntryTitle,
},
];
}
}
cy.createEditor({
tools: {
testTool: TestTool,
},
}).as('editorInstance');
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-toolbar__plus')
.click();
cy.get('[data-cy=editorjs]')
.get('div.ce-popover__item[data-item-name=testTool]')
.should('have.length', 1)
.should('not.contain', skippedEntryTitle);
});
}); });
context('Tunes', () => { context('Tunes — renderSettings()', () => {
it('should contain a single block tune configured in tool\'s renderSettings() method', () => { it('should contain a single block tune configured in tool\'s renderSettings() method', () => {
/** Tool with single tunes menu entry configured */ /** Tool with single tunes menu entry configured */
class TestTool { class TestTool {
@ -490,4 +414,437 @@ describe('Editor Tools Api', () => {
.should('contain.text', sampleText); .should('contain.text', sampleText);
}); });
}); });
/**
* @todo cover all the pasteConfig properties
*/
context('Paste — pasteConfig()', () => {
context('tags', () => {
/**
* tags: ['H1', 'H2']
*/
it('should use corresponding tool when the array of tag names specified', () => {
/**
* Test tool with pasteConfig.tags specified
*/
class TestImgTool {
/** config specified handled tag */
public static get pasteConfig(): PasteConfig {
return {
tags: [ 'img' ], // only tag name specified. Attributes should be sanitized
};
}
/** onPaste callback will be stubbed below */
public onPaste(): void {}
/** save is required for correct implementation of the BlockTool class */
public save(): void {}
/** render is required for correct implementation of the BlockTool class */
public render(): HTMLElement {
return document.createElement('img');
}
}
const toolsOnPaste = cy.spy(TestImgTool.prototype, 'onPaste');
cy.createEditor({
tools: {
testTool: TestImgTool,
},
}).as('editorInstance');
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<img>',
})
.then(() => {
expect(toolsOnPaste).to.be.called;
});
});
/**
* tags: ['img'] -> <img>
*/
it('should sanitize all attributes from tag, if only tag name specified ', () => {
/**
* Variable used for spying the pasted element we are passing to the Tool
*/
let pastedElement;
/**
* Test tool with pasteConfig.tags specified
*/
class TestImageTool {
/** config specified handled tag */
public static get pasteConfig(): PasteConfig {
return {
tags: [ 'img' ], // only tag name specified. Attributes should be sanitized
};
}
/** onPaste callback will be stubbed below */
public onPaste(): void {}
/** save is required for correct implementation of the BlockTool class */
public save(): void {}
/** render is required for correct implementation of the BlockTool class */
public render(): HTMLElement {
return document.createElement('img');
}
}
/**
* Stub the onPaste method to access the PasteEvent data for assertion
*/
cy.stub(TestImageTool.prototype, 'onPaste').callsFake((event: HTMLPasteEvent) => {
pastedElement = event.detail.data;
});
cy.createEditor({
tools: {
testImageTool: TestImageTool,
},
});
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<img src="foo" onerror="alert(123)"/>', // all attributes should be sanitized
})
.then(() => {
expect(pastedElement).not.to.be.undefined;
expect(pastedElement.tagName.toLowerCase()).eq('img');
expect(pastedElement.attributes.length).eq(0);
});
});
/**
* tags: [{
* img: {
* src: true
* }
* }]
* -> <img src="">
*
*/
it('should leave attributes if entry specified as a sanitizer config ', () => {
/**
* Variable used for spying the pasted element we are passing to the Tool
*/
let pastedElement;
/**
* Test tool with pasteConfig.tags specified
*/
class TestImageTool {
/** config specified handled tag */
public static get pasteConfig(): PasteConfig {
return {
tags: [
{
img: {
src: true,
},
},
],
};
}
/** onPaste callback will be stubbed below */
public onPaste(): void {}
/** save is required for correct implementation of the BlockTool class */
public save(): void {}
/** render is required for correct implementation of the BlockTool class */
public render(): HTMLElement {
return document.createElement('img');
}
}
/**
* Stub the onPaste method to access the PasteEvent data for assertion
*/
cy.stub(TestImageTool.prototype, 'onPaste').callsFake((event: HTMLPasteEvent) => {
pastedElement = event.detail.data;
});
cy.createEditor({
tools: {
testImageTool: TestImageTool,
},
});
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<img src="foo" onerror="alert(123)"/>',
})
.then(() => {
expect(pastedElement).not.to.be.undefined;
/**
* Check that the <img> has only "src" attribute
*/
expect(pastedElement.tagName.toLowerCase()).eq('img');
expect(pastedElement.getAttribute('src')).eq('foo');
expect(pastedElement.attributes.length).eq(1);
});
});
/**
* tags: [
* 'video',
* {
* source: {
* src: true
* }
* }
* ]
*/
it('should support mixed tag names and sanitizer config ', () => {
/**
* Variable used for spying the pasted element we are passing to the Tool
*/
let pastedElement;
/**
* Test tool with pasteConfig.tags specified
*/
class TestTool {
/** config specified handled tag */
public static get pasteConfig(): PasteConfig {
return {
tags: [
'video', // video should not have attributes
{
source: { // source should have only src attribute
src: true,
},
},
],
};
}
/** onPaste callback will be stubbed below */
public onPaste(): void {}
/** save is required for correct implementation of the BlockTool class */
public save(): void {}
/** render is required for correct implementation of the BlockTool class */
public render(): HTMLElement {
return document.createElement('tbody');
}
}
/**
* Stub the onPaste method to access the PasteEvent data for assertion
*/
cy.stub(TestTool.prototype, 'onPaste').callsFake((event: HTMLPasteEvent) => {
pastedElement = event.detail.data;
});
cy.createEditor({
tools: {
testTool: TestTool,
},
});
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<video width="100"><source src="movie.mp4" type="video/mp4"></video>',
})
.then(() => {
expect(pastedElement).not.to.be.undefined;
/**
* Check that <video> has no attributes
*/
expect(pastedElement.tagName.toLowerCase()).eq('video');
expect(pastedElement.attributes.length).eq(0);
/**
* Check that the <source> has only 'src' attribute
*/
expect(pastedElement.firstChild.tagName.toLowerCase()).eq('source');
expect(pastedElement.firstChild.getAttribute('src')).eq('movie.mp4');
expect(pastedElement.firstChild.attributes.length).eq(1);
});
});
/**
* tags: [
* {
* td: { width: true },
* tr: { height: true }
* }
* ]
*/
it('should support config with several keys as the single entry', () => {
/**
* Variable used for spying the pasted element we are passing to the Tool
*/
let pastedElement;
/**
* Test tool with pasteConfig.tags specified
*/
class TestTool {
/** config specified handled tag */
public static get pasteConfig(): PasteConfig {
return {
tags: [
{
video: {
width: true,
},
source: {
src: true,
},
},
],
};
}
/** onPaste callback will be stubbed below */
public onPaste(): void {}
/** save is required for correct implementation of the BlockTool class */
public save(): void {}
/** render is required for correct implementation of the BlockTool class */
public render(): HTMLElement {
return document.createElement('tbody');
}
}
/**
* Stub the onPaste method to access the PasteEvent data for assertion
*/
cy.stub(TestTool.prototype, 'onPaste').callsFake((event: HTMLPasteEvent) => {
pastedElement = event.detail.data;
});
cy.createEditor({
tools: {
testTool: TestTool,
},
});
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<video width="100"><source src="movie.mp4" type="video/mp4"></video>',
})
.then(() => {
expect(pastedElement).not.to.be.undefined;
expect(pastedElement.tagName.toLowerCase()).eq('video');
/**
* Check that the <tr> has the 'height' attribute
*/
expect(pastedElement.firstChild.tagName.toLowerCase()).eq('source');
expect(pastedElement.firstChild.getAttribute('src')).eq('movie.mp4');
});
});
/**
* It covers a workaround HTMLJanitor bug with tables (incorrect sanitizing of table.innerHTML)
* https://github.com/guardian/html-janitor/issues/3
*/
it('should correctly sanitize Table structure (test for HTMLJanitor bug)', () => {
/**
* Variable used for spying the pasted element we are passing to the Tool
*/
let pastedElement;
/**
* Test tool with pasteConfig.tags specified
*/
class TestTool {
/** config specified handled tag */
public static get pasteConfig(): PasteConfig {
return {
tags: [
'table',
'tbody',
{
td: {
width: true,
},
tr: {
height: true,
},
},
],
};
}
/** onPaste callback will be stubbed below */
public onPaste(): void {}
/** save is required for correct implementation of the BlockTool class */
public save(): void {}
/** render is required for correct implementation of the BlockTool class */
public render(): HTMLElement {
return document.createElement('tbody');
}
}
/**
* Stub the onPaste method to access the PasteEvent data for assertion
*/
cy.stub(TestTool.prototype, 'onPaste').callsFake((event: HTMLPasteEvent) => {
pastedElement = event.detail.data;
});
cy.createEditor({
tools: {
testTool: TestTool,
},
});
cy.get('[data-cy=editorjs]')
.get('div.ce-block')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<table><tr height="50"><td width="300">Ho-Ho-Ho</td></tr></table>',
})
.then(() => {
expect(pastedElement).not.to.be.undefined;
expect(pastedElement.tagName.toLowerCase()).eq('table');
/**
* Check that the <tr> has the 'height' attribute
*/
expect(pastedElement.querySelector('tr')).not.to.be.undefined;
expect(pastedElement.querySelector('tr').getAttribute('height')).eq('50');
/**
* Check that the <td> has the 'width' attribute
*/
expect(pastedElement.querySelector('td')).not.to.be.undefined;
expect(pastedElement.querySelector('td').getAttribute('width')).eq('300');
});
});
});
});
}); });

View file

@ -2,16 +2,18 @@
import Header from '@editorjs/header'; import Header from '@editorjs/header';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
describe.only('Block ids', () => { describe('Block ids', () => {
beforeEach(() => { beforeEach(function () {
if (this && this.editorInstance) { cy.createEditor({
tools: {
header: Header,
},
}).as('editorInstance');
});
afterEach(function () {
if (this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} else {
cy.createEditor({
tools: {
header: Header,
},
}).as('editorInstance');
} }
}); });

View file

@ -2,48 +2,53 @@ import Header from '@editorjs/header';
import Image from '@editorjs/simple-image'; import Image from '@editorjs/simple-image';
import * as _ from '../../../src/components/utils'; import * as _ from '../../../src/components/utils';
describe('Copy pasting from Editor', () => { describe('Copy pasting from Editor', function () {
beforeEach(() => { beforeEach(function () {
if (this && this.editorInstance) { cy.createEditor({
tools: {
header: Header,
image: Image,
},
}).as('editorInstance');
});
afterEach(function () {
if (this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} else {
cy.createEditor({
tools: {
header: Header,
image: Image,
},
}).as('editorInstance');
} }
}); });
context('pasting', () => { context('pasting', function () {
it('should paste plain text', () => { it('should paste plain text', function () {
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/plain': 'Some plain text', 'text/plain': 'Some plain text',
}) })
.wait(0) .wait(0)
.should('contain', 'Some plain text'); .should('contain', 'Some plain text');
}); });
it('should paste inline html data', () => { it('should paste inline html data', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<p><b>Some text</b></p>', 'text/html': '<p><b>Some text</b></p>',
}) })
.should('contain.html', '<b>Some text</b>'); .should('contain.html', '<b>Some text</b>');
}); });
it('should paste several blocks if plain text contains new lines', () => { it('should paste several blocks if plain text contains new lines', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/plain': 'First block\n\nSecond block', 'text/plain': 'First block\n\nSecond block',
}); });
@ -55,11 +60,12 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
it('should paste several blocks if html contains several paragraphs', () => { it('should paste several blocks if html contains several paragraphs', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<p>First block</p><p>Second block</p>', 'text/html': '<p>First block</p><p>Second block</p>',
}); });
@ -71,11 +77,12 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
it('should paste using custom data type', () => { it('should paste using custom data type', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'application/x-editor-js': JSON.stringify([ 'application/x-editor-js': JSON.stringify([
{ {
tool: 'paragraph', tool: 'paragraph',
@ -100,11 +107,12 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
it('should parse block tags', () => { it('should parse block tags', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': '<h2>First block</h2><p>Second block</p>', 'text/html': '<h2>First block</h2><p>Second block</p>',
}); });
@ -117,11 +125,12 @@ describe('Copy pasting from Editor', () => {
.should('contain', 'Second block'); .should('contain', 'Second block');
}); });
it('should parse pattern', () => { it('should parse pattern', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
.paste({ .paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/plain': 'https://codex.so/public/app/img/external/codex2x.png', 'text/plain': 'https://codex.so/public/app/img/external/codex2x.png',
}); });
@ -132,8 +141,8 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
context('copying', () => { context('copying', function () {
it('should copy inline fragment', () => { it('should copy inline fragment', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
@ -147,7 +156,7 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
it('should copy several blocks', () => { it('should copy several blocks', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
@ -171,7 +180,7 @@ describe('Copy pasting from Editor', () => {
* Need to wait for custom data as it is set asynchronously * Need to wait for custom data as it is set asynchronously
*/ */
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(0).then(() => { cy.wait(0).then(function () {
expect(clipboardData['application/x-editor-js']).not.to.be.undefined; expect(clipboardData['application/x-editor-js']).not.to.be.undefined;
const data = JSON.parse(clipboardData['application/x-editor-js']); const data = JSON.parse(clipboardData['application/x-editor-js']);
@ -185,8 +194,8 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
context('cutting', () => { context('cutting', function () {
it('should cut inline fragment', () => { it('should cut inline fragment', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
@ -200,7 +209,7 @@ describe('Copy pasting from Editor', () => {
}); });
}); });
it('should cut several blocks', () => { it('should cut several blocks', function () {
cy.get('[data-cy=editorjs]') cy.get('[data-cy=editorjs]')
.get('div.ce-block') .get('div.ce-block')
.click() .click()
@ -224,7 +233,7 @@ describe('Copy pasting from Editor', () => {
* Need to wait for custom data as it is set asynchronously * Need to wait for custom data as it is set asynchronously
*/ */
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(0).then(() => { cy.wait(0).then(function () {
expect(clipboardData['application/x-editor-js']).not.to.be.undefined; expect(clipboardData['application/x-editor-js']).not.to.be.undefined;
const data = JSON.parse(clipboardData['application/x-editor-js']); const data = JSON.parse(clipboardData['application/x-editor-js']);
@ -241,7 +250,7 @@ describe('Copy pasting from Editor', () => {
.should('not.contain', 'Second block'); .should('not.contain', 'Second block');
}); });
it('should cut lots of blocks', () => { it('should cut lots of blocks', function () {
const numberOfBlocks = 50; const numberOfBlocks = 50;
for (let i = 0; i < numberOfBlocks; i++) { for (let i = 0; i < numberOfBlocks; i++) {
@ -264,7 +273,7 @@ describe('Copy pasting from Editor', () => {
* Need to wait for custom data as it is set asynchronously * Need to wait for custom data as it is set asynchronously
*/ */
// eslint-disable-next-line cypress/no-unnecessary-waiting // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(0).then(() => { cy.wait(0).then(function () {
expect(clipboardData['application/x-editor-js']).not.to.be.undefined; expect(clipboardData['application/x-editor-js']).not.to.be.undefined;
const data = JSON.parse(clipboardData['application/x-editor-js']); const data = JSON.parse(clipboardData['application/x-editor-js']);

View file

@ -3,7 +3,7 @@ import { ToolboxConfig } from '../../../types';
describe('Editor i18n', () => { describe('Editor i18n', () => {
context('Toolbox', () => { context('Toolbox', () => {
it('should translate tool title in a toolbox', () => { it('should translate tool title in a toolbox', function () {
if (this && this.editorInstance) { if (this && this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} }
@ -35,7 +35,7 @@ describe('Editor i18n', () => {
.should('contain.text', toolNamesDictionary.Heading); .should('contain.text', toolNamesDictionary.Heading);
}); });
it('should translate titles of toolbox entries', () => { it('should translate titles of toolbox entries', function () {
if (this && this.editorInstance) { if (this && this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} }
@ -95,7 +95,7 @@ describe('Editor i18n', () => {
.should('contain.text', toolNamesDictionary.Title2); .should('contain.text', toolNamesDictionary.Title2);
}); });
it('should use capitalized tool name as translation key if toolbox title is missing', () => { it('should use capitalized tool name as translation key if toolbox title is missing', function () {
if (this && this.editorInstance) { if (this && this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} }
@ -141,4 +141,4 @@ describe('Editor i18n', () => {
.should('contain.text', toolNamesDictionary.TestTool); .should('contain.text', toolNamesDictionary.TestTool);
}); });
}); });
}); });

View file

@ -1,4 +1,4 @@
// eslint-disable-next-line spaced-comment // eslint-disable-next-line spaced-comment, @typescript-eslint/triple-slash-reference
/// <reference path="../support/index.d.ts" /> /// <reference path="../support/index.d.ts" />
describe('Editor basic initialization', () => { describe('Editor basic initialization', () => {
@ -8,11 +8,13 @@ describe('Editor basic initialization', () => {
*/ */
const editorConfig = {}; const editorConfig = {};
beforeEach(() => { beforeEach(function () {
if (this && this.editorInstance) { cy.createEditor(editorConfig).as('editorInstance');
});
afterEach(function () {
if (this.editorInstance) {
this.editorInstance.destroy(); this.editorInstance.destroy();
} else {
cy.createEditor(editorConfig).as('editorInstance');
} }
}); });

View file

@ -0,0 +1,33 @@
describe('Inline Tool Link', () => {
it('should create a link by Enter keydown in input', () => {
cy.createEditor({
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'First block text',
},
},
],
},
});
cy.get('[data-cy=editorjs]')
.find('div.ce-block')
.click()
.type('{selectall}')
.type('{ctrl}K');
cy.get('[data-cy=editorjs]')
.find('.ce-inline-tool-input')
.click()
.type('https://codex.so')
.type('{enter}');
cy.get('[data-cy=editorjs]')
.find('div.ce-block')
.find('a')
.should('have.attr', 'href', 'https://codex.so');
});
});

View file

@ -0,0 +1,43 @@
describe('Keydown', function () {
describe('enter', function () {
it('should split block and remove selected fragment if some text fragment selected', function () {
cy.createEditor({
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'The block with some text',
},
},
],
},
});
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.get('[data-cy=editorjs]')
.find('.ce-paragraph')
.click()
.selectText('with so')
.wait(0)
.type('{enter}');
cy.get('[data-cy=editorjs]')
.find('div.ce-block')
.then((blocks) => {
/**
* Check that there is two blocks after split
*/
expect(blocks.length).to.equal(2);
/**
* Check that selected text fragment has been removed
*/
expect(blocks[0].textContent).to.equal('The block ');
expect(blocks[1].textContent).to.equal('me text');
});
});
});
});

View file

@ -1,5 +1,5 @@
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
/* eslint-disable @typescript-eslint/ban-ts-ignore,@typescript-eslint/no-explicit-any,jsdoc/require-jsdoc */ /* eslint-disable @typescript-eslint/no-explicit-any, jsdoc/require-jsdoc */
import Tools from '../../../../src/components/modules/tools'; import Tools from '../../../../src/components/modules/tools';
import { EditorConfig } from '../../../../types'; import { EditorConfig } from '../../../../types';
import BlockTool from '../../../../src/components/tools/block'; import BlockTool from '../../../../src/components/tools/block';
@ -57,6 +57,7 @@ describe('Tools module', () => {
it('should throw an error if tools config is corrupted', async () => { it('should throw an error if tools config is corrupted', async () => {
const module = constructModule({ const module = constructModule({
tools: { tools: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
corruptedTool: 'value', corruptedTool: 'value',
}, },
@ -77,7 +78,7 @@ describe('Tools module', () => {
it('should call Tools prepare method with user config', async () => { it('should call Tools prepare method with user config', async () => {
class WithSuccessfulPrepare { class WithSuccessfulPrepare {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public static prepare = cy.stub() public static prepare = cy.stub();
} }
const config = { const config = {
@ -134,7 +135,7 @@ describe('Tools module', () => {
}, },
blockToolWithoutSettings: class {} as any, blockToolWithoutSettings: class {} as any,
inlineTool: class { inlineTool: class {
public static isInline = true public static isInline = true;
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public render(): void {} public render(): void {}
@ -146,7 +147,7 @@ describe('Tools module', () => {
public checkState(): void {} public checkState(): void {}
} as any, } as any,
inlineTool2: class { inlineTool2: class {
public static isInline = true public static isInline = true;
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
public render(): void {} public render(): void {}

View file

@ -1,6 +1,11 @@
import EditorJS, { EditorConfig } from '../../../types'; import EditorJS, { EditorConfig } from '../../../types';
describe('ReadOnly API spec', () => { describe('ReadOnly API spec', () => {
/**
* Creates the new editor instance
*
* @param config - Editor Config
*/
function createEditor(config?: EditorConfig): void { function createEditor(config?: EditorConfig): void {
const editorConfig = Object.assign({}, config || {}); const editorConfig = Object.assign({}, config || {});

Some files were not shown because too many files have changed in this diff Show more