diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 3cb084c98..000000000 --- a/.babelrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", { - "modules": "umd", - "useBuiltIns": "entry", - "corejs": 3 - }] - ], - "plugins": [ - "babel-plugin-add-module-exports", - "babel-plugin-class-display-name", - "@babel/plugin-transform-runtime" - ], - "env": { - "test": { - "plugins": [ "istanbul" ] - } - } -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 78639a6ca..d7b90d07e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,5 +1,5 @@ # These are supported funding model platforms -github: neSpecc patreon: editorjs open_collective: editorjs +custom: https://codex.so/donate \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f8df3f874..6889bef95 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Create a report to help us improve Editor.js -title: "[Bug]" +title: "" labels: bug assignees: '' diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 40a9197f0..dbb6ff672 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,9 +3,9 @@ contact_links: - name: Team url: mailto:team@codex.so about: Direct team contact. + - name: 💬 Discussions + url: https://github.com/codex-team/editor.js/discussions + about: Use discussions if you have an issue draft, an idea for improvement or for asking questions. - name: Editor.js Telegram chat url: https://t.me/codex_editor - about: Telegram chat for Editor.js users communication. - - name: Editor.js contributors Telegram chat - url: https://t.me/editorjsdev - about: Telegram chat for Editor.js contributors communication. + about: Telegram chat for Editor.js users communication. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 6069bf434..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature request -about: Suggest an idea to improve Editor.js -title: "\U0001F4A1" -labels: feature -assignees: '' - ---- - -1. Describe a problem. - -2. Describe the solution you'd like. Mockups are welcome. - -3. Are there any alternatives? - - diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/general_issue.md similarity index 65% rename from .github/ISSUE_TEMPLATE/discussion.md rename to .github/ISSUE_TEMPLATE/general_issue.md index f6f6a3ed3..593bfd206 100644 --- a/.github/ISSUE_TEMPLATE/discussion.md +++ b/.github/ISSUE_TEMPLATE/general_issue.md @@ -1,8 +1,8 @@ --- -name: Discussion -about: Any question about the Editor.js to discuss +name: General Issue +about: Well-designed, algorithmized feature/idea/improvement issue for Editor.js title: '' -labels: discussion +labels: '' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/issue--discussion.md b/.github/ISSUE_TEMPLATE/issue--discussion.md deleted file mode 100644 index 00261395d..000000000 --- a/.github/ISSUE_TEMPLATE/issue--discussion.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: 'Issue: Discussion' -about: Any question about the project to discuss -title: "❓" -labels: discussion -assignees: '' - ---- - -**Question** - -A clear and consistent question about the project. Ex. How can I do smth? Why smth works this way? etc. - -**Context** - -Why and how the question has come up - -**Related issues** - -If there are related issues which describe a bugs or features, put them here - -**Comments** - -Any thoughts about the question diff --git a/.github/workflows/bump-version-on-merge-next.yml b/.github/workflows/bump-version-on-merge-next.yml index d1e94ea01..67d303e7e 100644 --- a/.github/workflows/bump-version-on-merge-next.yml +++ b/.github/workflows/bump-version-on-merge-next.yml @@ -49,8 +49,7 @@ jobs: # Setup node environment - uses: actions/setup-node@v1 with: - node-version: 15 - registry-url: https://registry.npmjs.org/ + node-version: 16 # Bump version to the next prerelease (patch) with rc suffix - name: Suggest the new version diff --git a/.github/workflows/create-a-release-draft.yml b/.github/workflows/create-a-release-draft.yml index 4016b7b80..4d923c3c7 100644 --- a/.github/workflows/create-a-release-draft.yml +++ b/.github/workflows/create-a-release-draft.yml @@ -12,6 +12,9 @@ jobs: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: + - uses: actions/setup-node@v3 + with: + node-version: 16 # Checkout to target branch - uses: actions/checkout@v2 with: @@ -53,8 +56,7 @@ jobs: # Setup node environment - uses: actions/setup-node@v1 with: - node-version: 14.17.0 - registry-url: https://registry.npmjs.org/ + node-version: 16 # Prepare, build and publish project - name: Install dependencies @@ -87,16 +89,27 @@ jobs: # If version name contains "-rc" suffix than mark a "pre-release" checkbox prerelease: ${{ contains(steps.package.outputs.version, '-rc') }} - # Build and upload target Editor.js build to release as artifact + # Build and upload target Editor.js UMD build to release as artifact - name: Upload Release Asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: dist/editor.js - asset_name: editor.js + asset_path: dist/editorjs.umd.js + asset_name: editorjs.umd.js asset_content_type: application/javascript + + # Build and upload target Editor.js MJS build to release as artifact + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/editorjs.mjs + asset_name: editorjs.mjs + asset_content_type: application/javascript # Send a notification message - name: Send a message diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index ee311b356..2df7d315f 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -1,36 +1,21 @@ -name: Tests +name: Cypress + on: [pull_request] + jobs: - firefox: + run-tests: + strategy: + matrix: + browser: [firefox, chrome, edge] + runs-on: ubuntu-latest - container: - image: cypress/browsers:node14.17.0-chrome88-ff89 - options: --user 1001 steps: - - uses: actions/checkout@v2 - - run: yarn ci:pull_paragraph - - uses: cypress-io/github-action@v2 + - uses: actions/setup-node@v3 with: - config: video=false - browser: firefox - build: yarn build - chrome: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: yarn ci:pull_paragraph - - uses: cypress-io/github-action@v2 - with: - config: video=false - browser: chrome - build: yarn build - edge: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - run: yarn ci:pull_paragraph - - uses: cypress-io/github-action@v2 + node-version: 16 + - uses: actions/checkout@v3 + - uses: cypress-io/github-action@v5 with: config: video=false - browser: edge + browser: ${{ matrix.browser }} build: yarn build diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 052c68355..47d1e3a9d 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -19,6 +19,5 @@ jobs: ${{ runner.OS }}-build- ${{ runner.OS }}- - - run: yarn install - + - run: yarn - run: yarn lint diff --git a/.github/workflows/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index a614f700e..ae926224e 100644 --- a/.github/workflows/publish-package-to-npm.yml +++ b/.github/workflows/publish-package-to-npm.yml @@ -22,7 +22,7 @@ jobs: # Setup node environment - uses: actions/setup-node@v1 with: - node-version: 14.17.0 + node-version: 16 registry-url: https://registry.npmjs.org/ # Prepare, build and publish project diff --git a/.gitmodules b/.gitmodules index ad185a9e5..66320775b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,9 +16,6 @@ [submodule "example/tools/simple-image"] path = example/tools/simple-image url = https://github.com/editor-js/simple-image -[submodule "src/tools/paragraph"] - path = src/tools/paragraph - url = https://github.com/editor-js/paragraph [submodule "example/tools/marker"] path = example/tools/marker url = https://github.com/editor-js/marker diff --git a/.npmignore b/.npmignore index 98ed3edfa..c549183e3 100644 --- a/.npmignore +++ b/.npmignore @@ -1,22 +1,6 @@ -.idea -.github -docs -example -src -test -.babelrc -.editorconfig -.eslintignore -.eslintrc -.git -.gitmodules -.jshintrc -.postcssrc.yml -.stylelintrc -CODEOWNERS -cypress.json -tsconfig.json -tslint.json -webpack.config.js -yarn.lock -devserver.js +* +!/dist/**/* +!/types/**/* +!/LICENSE +!/README.md +!/package.json diff --git a/.postcssrc.yml b/.postcssrc.yml index b2a67d586..c52a3b2a8 100644 --- a/.postcssrc.yml +++ b/.postcssrc.yml @@ -1,8 +1,4 @@ plugins: - # Consumes files by @import rule - # https://github.com/postcss/postcss-import - postcss-import: {} - # Apply custom property sets via @apply rule # https://github.com/pascalduez/postcss-apply postcss-apply: {} @@ -26,16 +22,6 @@ plugins: # https://github.com/csstools/postcss-preset-env#preserve preserve: false - # Enable or disable specific polyfills - # https://github.com/csstools/postcss-preset-env#features - # - # List of available plugins - # https://github.com/csstools/postcss-preset-env/blob/master/src/lib/plugins-by-id.js - features: - # Modify colors using the color-mod() function in CSS - # https://github.com/jonathantneal/postcss-color-mod-function - color-mod-function: {} - # Nested rules unwrapper # https://github.com/postcss/postcss-nested # @@ -43,7 +29,3 @@ plugins: # 'postcss-nesting' feature but it does not work with BEM # Report: https://github.com/csstools/postcss-preset-env/issues/40 postcss-nested: {} - - # Compression tool - # https://github.com/cssnano/cssnano - cssnano: {} diff --git a/.vscode/settings.json b/.vscode/settings.json index 8b3ac96d3..5d01b0ef3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "colspan", "contenteditable", "contentless", + "Convertable", "cssnano", "cssnext", "Debouncer", @@ -34,6 +35,7 @@ "textareas", "twitterwidget", "typeof", + "Unmergeable", "viewports" ] } diff --git a/README.md b/README.md index 649c4a352..5be82b456 100644 --- a/README.md +++ b/README.md @@ -1,270 +1,233 @@ - - -[![](https://flat.badgen.net/npm/v/@editorjs/editorjs?icon=npm)](https://www.npmjs.com/package/@editorjs/editorjs) -[![](https://flat.badgen.net/bundlephobia/min/@editorjs/editorjs?color=cyan)](https://www.npmjs.com/package/@editorjs/editorjs) -[![](https://flat.badgen.net/bundlephobia/minzip/@editorjs/editorjs?color=green)](https://www.npmjs.com/package/@editorjs/editorjs) -[![Backers on Open Collective](https://opencollective.com/editorjs/backers/badge.svg)](#backers) -[![Sponsors on Open Collective](https://opencollective.com/editorjs/sponsors/badge.svg)](#sponsors) -[![](https://img.shields.io/npm/l/@editorjs/editorjs?style=flat-square)](https://www.npmjs.com/package/@editorjs/editorjs) -[![Join the chat at https://gitter.im/codex-team/editor.js](https://badges.gitter.im/codex-team/editor.js.svg)](https://gitter.im/codex-team/editor.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -| | | | | | | -| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| Edge 12+ | Firefox 18+ | Chrome 49+ | Safari 10+ | iOS Safari 10+ | Opera 36+ | - -## Roadmap - - - -- Collaborative editing - - - [ ] Implement Inline Tools JSON format `In progress` [#1801](https://github.com/codex-team/editor.js/pull/1801) - - [ ] Implement Operations creation and transformations - - [ ] Implement Tools API changes - - [ ] Implement Server and communication - - [ ] Update basic tools to fit the new API - -- Unified Toolbox - - [x] Block Tunes moved left [#1815](https://github.com/codex-team/editor.js/pull/1815) - - [x] Toolbox become vertical [#2014](https://github.com/codex-team/editor.js/pull/2014) - - [x] Ability to display several Toolbox buttons by the single Tool [#2050](https://github.com/codex-team/editor.js/pull/2050) - - [ ] Conversion Toolbar uses Unified Toolbox `In progress` - - [ ] Block Tunes become vertical - - [ ] Conversion Toolbar added to the Block Tunes -- Ecosystem improvements - - - - - -## - -## If you like a project 💗💗💗 - -If you like Editor.js you can support project improvements and development of new features with a donation to our collective. - -👉 [https://opencollective.com/editorjs](https://opencollective.com/editorjs) - -### Sponsors - -Support us by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout)] - - - - - -### Backers - -Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/editorjs/contribute/backer-8632/checkout)] - - - -### Contributors - -This project exists thanks to all the people who contribute. - -We really welcome new contributors. If you want to make some code with us, please take a look at the [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22). You can write to us on `team@codex.so` or via special [Telegram chat](https://t.me/editorjsdev), or any other way. - -## Documentation - -Please visit [https://editorjs.io/](https://editorjs.io) to view all documentation articles. - -- [Base concepts](https://editorjs.io/base-concepts) -- [Getting started](https://editorjs.io/getting-started) -- [Configuration](https://editorjs.io/configuration) -- [How to create a Block Tool Plugin](https://editorjs.io/creating-a-block-tool) -- [How to create an Inline Tool Plugin](https://editorjs.io/creating-an-inline-tool) -- [API for Tools](https://editorjs.io/tools-api) - -You can join a [Gitter-channel](https://gitter.im/codex-team/editor.js) or [Telegram-chat](//t.me/codex_editor) and ask a question. - -## Changelog - -See the whole [Changelog](/docs/CHANGELOG.md) - -If you want to follow Editor.js updates, [subscribe to our Newsletter](http://digest.editorjs.io/). - -## How to use Editor.js - -### Basics - -Editor.js is a Block-Styled editor. Blocks are structural units, of which the Entry is composed. -For example, `Paragraph`, `Heading`, `Image`, `Video`, `List` are Blocks. Each Block is represented by Plugin. -We have [many](http://github.com/editor-js/) ready-to-use Plugins and a [simple API](https://editorjs.io/tools-api) for creating new ones. - -How to use the Editor after [Installation](https://editorjs.io/getting-started). - -- Create new Blocks by pressing Enter or clicking the Plus Button -- Press `TAB` or click on the Plus Button to view the Toolbox -- Press `TAB` again to leaf Toolbox and select a Block you need. Then press Enter. - -![](https://github.com/editor-js/list/raw/master/assets/example.gif) - -- Select a text fragment and apply a style or insert a link from the Inline Toolbar - -![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg) - -- Use the «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block - or apply a Tool's settings, if it provided. For example, you can set a Heading level or List style. - -![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg) - -### Shortcuts +
+ + + +
+ ++ editorjs.io | + documentation | + changelog + +
+ + + +## About + +Editor.js is an open-source text editor offering a variety of features to help users create and format content efficiently. It has a modern, block-style interface that allows users to easily add and arrange different types of content, such as text, images, lists, quotes, etc. Each Block is provided via a separate plugin making Editor.js extremely flexible. + +Editor.js outputs a clean JSON data instead of heavy HTML markup. Use it in Web, iOS, Android, AMP, Instant Articles, speech readers, AI chatbots — everywhere. Easy to sanitize, extend and integrate with your logic. + +- 😍 Modern UI out of the box +- 💎 Clean JSON output +- ⚙️ Well-designed API +- 🛍 Various Tools available +- 💌 Free and open source + + + +## Installation + +It's quite simple: + +1. Install Editor.js +2. Install tools you need +3. Initialize Editor's instance + +Install using NPM, Yarn, or [CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs): + +```bash +npm i @editorjs/editorjs +``` -A few shortcuts are preset as available. +Choose and install tools: + +- [Heading](https://github.com/editor-js/header) +- [Quote](https://github.com/editor-js/quote) +- [Image](https://github.com/editor-js/image) +- [Simple Image](https://github.com/editor-js/simple-image) (without backend requirement) +- [Nested List](https://github.com/editor-js/nested-list) +- [Checklist](https://github.com/editor-js/checklist) +- [Link embed](https://github.com/editor-js/link) +- [Embeds](https://github.com/editor-js/embed) (YouTube, Twitch, Vimeo, Gfycat, Instagram, Twitter, etc) +- [Table](https://github.com/editor-js/table) +- [Delimiter](https://github.com/editor-js/delimiter) +- [Warning](https://github.com/editor-js/warning) +- [Code](https://github.com/editor-js/code) +- [Raw HTML](https://github.com/editor-js/raw) +- [Attaches](https://github.com/editor-js/attaches) +- [Marker](https://github.com/editor-js/marker) +- [Inline Code](https://github.com/editor-js/inline-code) + +See the [😎 Awesome Editor.js](https://github.com/editor-js/awesome-editorjs) list for more tools. + +Initialize the Editor: -| Shortcut | Action | Restrictions | -| ----------- | -------------------- | ------------------------------------------------- | -| `TAB` | Show/leaf a Toolbox. | On empty block | -| `SHIFT+TAB` | Leaf back a Toolbox. | While Toolbox is opened | -| `ENTER` | Create a Block | While Toolbox is opened and some Tool is selected | -| `CMD+B` | Bold style | On selection | -| `CMD+I` | Italic style | On selection | -| `CMD+K` | Insert a link | On selection | +```html + +``` -Each Tool can also have its own shortcuts. These are specified in the configuration of the Tool, for example: +```javascript +import EditorJS from "@editorjs/editorjs"; -```js -var editor = new EditorJS({ - //... +const editor = new EditorJS({ tools: { - header: { - class: Header, - shortcut: "CMD+SHIFT+H", - }, - list: { - class: List, - shortcut: "CMD+SHIFT+L", - }, + // ... your tools }, - //... }); ``` -## Installation Guide - -There are few steps to run Editor.js on your site. - -1. [Load Editor's core](#load-editors-core) -2. [Load Tools](#load-tools) -3. [Initialize Editor's instance](#create-editor-instance) - -### Step 1. Load Editor's core - -Get Editor.js itself. It is a [minified script](dist/editor.js) with Editor's core and some default must-have tools. +See details about [Installation](https://editorjs.io/getting-started/) and [Configuration](https://editorjs.io/configuration/) at the documentation. -Choose the most usable method of getting Editor for you. - -- Node package -- Source from CDN - -##### Option A. NPM install - -Install the package via NPM or Yarn - -```shell -npm i @editorjs/editorjs -``` +### Saving Data -Include module in your application +Call `editor.save()` and handle returned Promise with saved data. ```javascript -import EditorJS from "@editorjs/editorjs"; +const data = await editor.save(); ``` -##### Option B. Use a CDN - -You can load EditorJS directly from from [jsDelivr CDN](https://www.jsdelivr.com/package/npm/@editorjs/editorjs). +### Example -`https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest` +Take a look at the [example.html](example/example.html) to view more detailed examples. -For example, place this in your HTML: +## Roadmap -```html - -``` + -Or download the bundle file and use it from your server. +- Unified Toolbox + - [x] Block Tunes moved left + - [x] Toolbox becomes vertical + - [x] Ability to display several Toolbox buttons by the single Tool + - [x] Block Tunes become vertical + - [ ] Block Tunes support nested menus + - [ ] Conversion Toolbar uses Unified Toolbox + - [ ] Conversion Toolbar added to the Block Tunes +- Collaborative editing + - [ ] Implement Inline Tools JSON format + - [ ] Operations Observer, Executor, Manager, Transformer + - [ ] Implement Undo/Redo Manager + - [ ] Implement Tools API changes + - [ ] Implement Server and communication + - [ ] Update basic tools to fit the new API +- Other features + - [ ] Blocks drag'n'drop + - [ ] New cross-block selection + - [ ] New cross-block caret moving +- Ecosystem improvements + - [x] CodeX Icons — the way to unify all tools and core icons + - [x] New Homepage and Docs + - [x] @editorjs/create-tool for Tools bootstrapping + - [ ] Editor.js DevTools — stand for core and tools development + - [ ] Editor.js Design System + - [ ] Editor.js Preset Env + - [ ] Editor.js ToolKit + - [ ] New core bundle system + - [ ] New documentation and guides -```html - -``` + + + -### Step 2. Load the Tools that you want to make available +"+(l.trim()?l:s)+"
"),f=Object.keys(this.toolsTags).reduce((function(e,t){var n;return e[t.toLowerCase()]=null!==(n=h.toolsTags[t].sanitizationConfig)&&void 0!==n?n:{},e}),{}),d=Object.assign({},f,o.getAllInlineToolsSanitizeConfig(),{br:{}}),(p=(0,g.clean)(l,d)).trim()&&p.trim()!==s&&v.default.isHTMLString(p)){e.next=33;break}return e.next=31,this.processText(s);case 31:e.next=35;break;case 33:return e.next=35,this.processText(p,!0);case 35:case"end":return e.stop()}}),e,this,[[17,22]])}))),function(e){return h.apply(this,arguments)})},{key:"processText",value:(p=(0,l.default)(r.default.mark((function e(t){var n,o,i,a,s,c,u,f=this,d=arguments;return r.default.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(n=d.length>1&&void 0!==d[1]&&d[1],o=this.Editor,i=o.Caret,a=o.BlockManager,(s=n?this.processHTML(t):this.processPlain(t)).length){e.next=5;break}return e.abrupt("return");case 5:if(1!==s.length){e.next=8;break}return s[0].isBlock?this.processSingleBlock(s.pop()):this.processInlinePaste(s.pop()),e.abrupt("return");case 8:c=a.currentBlock&&a.currentBlock.tool.isDefault,u=c&&a.currentBlock.isEmpty,s.map(function(){var e=(0,l.default)(r.default.mark((function e(t,n){return r.default.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",f.insertBlock(t,0===n&&u));case 1:case"end":return e.stop()}}),e)})));return function(t,n){return e.apply(this,arguments)}}()),a.currentBlock&&i.setToBlock(a.currentBlock,i.positions.END);case 12:case"end":return e.stop()}}),e,this)}))),function(e){return p.apply(this,arguments)})},{key:"setCallback",value:function(){this.listeners.on(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}},{key:"unsetCallback",value:function(){this.listeners.off(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}},{key:"processTools",value:function(){var e=this.Editor.Tools.blockTools;Array.from(e.values()).forEach(this.processTool)}},{key:"collectTagNames",value:function(e){return y.isString(e)?[e]:y.isObject(e)?Object.keys(e):[]}},{key:"getTagsConfig",value:function(e){var t=this,n=e.pasteConfig.tags||[],o=[];n.forEach((function(n){var r=t.collectTagNames(n);o.push.apply(o,(0,s.default)(r)),r.forEach((function(o){if(Object.prototype.hasOwnProperty.call(t.toolsTags,o))y.log("Paste handler for «".concat(e.name,"» Tool on «").concat(o,"» tag is skipped ")+"because it is already used by «".concat(t.toolsTags[o].tool.name,"» Tool."),"warn");else{var r=y.isObject(n)?n[o]:null;t.toolsTags[o.toUpperCase()]={tool:e,sanitizationConfig:r}}}))})),this.tagsByTool[e.name]=o.map((function(e){return e.toUpperCase()}))}},{key:"getFilesConfig",value:function(e){var t=e.pasteConfig.files,n=void 0===t?{}:t,o=n.extensions,r=n.mimeTypes;(o||r)&&(o&&!Array.isArray(o)&&(y.log("«extensions» property of the onDrop config for «".concat(e.name,"» Tool should be an array")),o=[]),r&&!Array.isArray(r)&&(y.log("«mimeTypes» property of the onDrop config for «".concat(e.name,"» Tool should be an array")),r=[]),r&&(r=r.filter((function(t){return!!y.isValidMimeType(t)||(y.log("MIME type value «".concat(t,"» for the «").concat(e.name,"» Tool is not a valid MIME type"),"warn"),!1)}))),this.toolsFiles[e.name]={extensions:o||[],mimeTypes:r||[]})}},{key:"getPatternsConfig",value:function(e){var t=this;e.pasteConfig.patterns&&!y.isEmpty(e.pasteConfig.patterns)&&Object.entries(e.pasteConfig.patterns).forEach((function(n){var o=(0,i.default)(n,2),r=o[0],a=o[1];a instanceof RegExp||y.log("Pattern ".concat(a," for «").concat(e.name,"» Tool is skipped because it should be a Regexp instance."),"warn"),t.toolsPatterns.push({key:r,pattern:a,tool:e})}))}},{key:"isNativeBehaviour",value:function(e){return v.default.isNativeInput(e)}},{key:"processFiles",value:(d=(0,l.default)(r.default.mark((function e(t){var n,o,i,a,s=this;return r.default.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=this.Editor.BlockManager,e.next=3,Promise.all(Array.from(t).map((function(e){return s.processFile(e)})));case 3:o=(o=e.sent).filter((function(e){return!!e})),i=n.currentBlock.tool.isDefault,a=i&&n.currentBlock.isEmpty,o.forEach((function(e,t){n.paste(e.type,e.event,0===t&&a)}));case 8:case"end":return e.stop()}}),e,this)}))),function(e){return d.apply(this,arguments)})},{key:"processFile",value:(a=(0,l.default)(r.default.mark((function e(t){var n,o,a,s,l;return r.default.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(n=y.getFileExtension(t),o=Object.entries(this.toolsFiles).find((function(e){var o=(0,i.default)(e,2),r=(o[0],o[1]),a=r.mimeTypes,s=r.extensions,l=t.type.split("/"),c=(0,i.default)(l,2),u=c[0],f=c[1],d=s.find((function(e){return e.toLowerCase()===n.toLowerCase()})),p=a.find((function(e){var t=e.split("/"),n=(0,i.default)(t,2),o=n[0],r=n[1];return o===u&&(r===f||"*"===r)}));return!!d||!!p}))){e.next=4;break}return e.abrupt("return");case 4:return a=(0,i.default)(o,1),s=a[0],l=this.composePasteEvent("file",{file:t}),e.abrupt("return",{event:l,type:s});case 7:case"end":return e.stop()}}),e,this)}))),function(e){return a.apply(this,arguments)})},{key:"processHTML",value:function(e){var t=this,n=this.Editor.Tools,o=v.default.make("DIV");return o.innerHTML=e,this.getNodes(o).map((function(e){var o,r=n.defaultTool,i=!1;switch(e.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:(o=v.default.make("div")).appendChild(e);break;case Node.ELEMENT_NODE:o=e,i=!0,t.toolsTags[o.tagName]&&(r=t.toolsTags[o.tagName].tool)}var a=r.pasteConfig.tags.reduce((function(e,n){return t.collectTagNames(n).forEach((function(t){var o=y.isObject(n)?n[t]:null;e[t]=o||{}})),e}),{}),s=Object.assign({},a,r.baseSanitizeConfig);if("table"===o.tagName.toLowerCase()){var l=(0,g.clean)(o.outerHTML,s);o=v.default.make("div",void 0,{innerHTML:l}).firstChild}else o.innerHTML=(0,g.clean)(o.innerHTML,s);var c=t.composePasteEvent("tag",{data:o});return{content:o,isBlock:i,tool:r.name,event:c}})).filter((function(e){var t=v.default.isEmpty(e.content),n=v.default.isSingleTag(e.content);return!t||n}))}},{key:"processPlain",value:function(e){var t=this,n=this.config.defaultBlock;if(!e)return[];var o=n;return e.split(/\r?\n/).filter((function(e){return e.trim()})).map((function(e){var n=v.default.make("div");n.textContent=e;var r=t.composePasteEvent("tag",{data:n});return{content:n,tool:o,isBlock:!1,event:r}}))}},{key:"processSingleBlock",value:(o=(0,l.default)(r.default.mark((function e(t){var n,o,i,a;return r.default.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(n=this.Editor,o=n.Caret,i=n.BlockManager,(a=i.currentBlock)&&t.tool===a.name&&v.default.containsOnlyInlineElements(t.content.innerHTML)){e.next=5;break}return this.insertBlock(t,(null==a?void 0:a.tool.isDefault)&&a.isEmpty),e.abrupt("return");case 5:o.insertContentAtCaretPosition(t.content.innerHTML);case 6:case"end":return e.stop()}}),e,this)}))),function(e){return o.apply(this,arguments)})},{key:"processInlinePaste",value:(n=(0,l.default)(r.default.mark((function e(t){var n,o,i,a,s,l,c,u;return r.default.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(n=this.Editor,o=n.BlockManager,i=n.Caret,a=t.content,!(o.currentBlock&&o.currentBlock.tool.isDefault&&a.textContent.lengthadaddad<-- passed node for example + * | + * | right first-level siblings + * | + *
" + (a.trim() ? a : r) + "
"); + const u = Object.keys(this.toolsTags).reduce((m, p) => (m[p.toLowerCase()] = this.toolsTags[p].sanitizationConfig ?? {}, m), {}), h = Object.assign({}, u, t.getAllInlineToolsSanitizeConfig(), { br: {} }), f = q(a, h); + !f.trim() || f.trim() === r || !d.isHTMLString(f) ? await this.processText(r) : await this.processText(f, !0); + } + /** + * Process pasted text and divide them into Blocks + * + * @param {string} data - text to process. Can be HTML or plain. + * @param {boolean} isHTML - if passed string is HTML, this parameter should be true + */ + async processText(s, e = !1) { + const { Caret: t, BlockManager: o } = this.Editor, n = e ? this.processHTML(s) : this.processPlain(s); + if (!n.length) + return; + if (n.length === 1) { + n[0].isBlock ? this.processSingleBlock(n.pop()) : this.processInlinePaste(n.pop()); + return; + } + const r = o.currentBlock && o.currentBlock.tool.isDefault && o.currentBlock.isEmpty; + n.map( + async (a, l) => this.insertBlock(a, l === 0 && r) + ), o.currentBlock && t.setToBlock(o.currentBlock, t.positions.END); + } + /** + * Set onPaste callback handler + */ + setCallback() { + this.listeners.on(this.Editor.UI.nodes.holder, "paste", this.handlePasteEvent); + } + /** + * Unset onPaste callback handler + */ + unsetCallback() { + this.listeners.off(this.Editor.UI.nodes.holder, "paste", this.handlePasteEvent); + } + /** + * Get and process tool`s paste configs + */ + processTools() { + const s = this.Editor.Tools.blockTools; + Array.from(s.values()).forEach(this.processTool); + } + /** + * 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. + */ + collectTagNames(s) { + return J(s) ? [s] : j(s) ? Object.keys(s) : []; + } + /** + * Get tags to substitute by Tool + * + * @param tool - BlockTool object + */ + getTagsConfig(s) { + if (s.pasteConfig === !1) + return; + const e = s.pasteConfig.tags || [], t = []; + e.forEach((o) => { + const n = this.collectTagNames(o); + t.push(...n), n.forEach((i) => { + if (Object.prototype.hasOwnProperty.call(this.toolsTags, i)) { + L( + `Paste handler for «${s.name}» Tool on «${i}» tag is skipped because it is already used by «${this.toolsTags[i].tool.name}» Tool.`, + "warn" + ); + return; + } + const r = j(o) ? o[i] : null; + this.toolsTags[i.toUpperCase()] = { + tool: s, + sanitizationConfig: r + }; + }); + }), this.tagsByTool[s.name] = t.map((o) => o.toUpperCase()); + } + /** + * Get files` types and extensions to substitute by Tool + * + * @param tool - BlockTool object + */ + getFilesConfig(s) { + if (s.pasteConfig === !1) + return; + const { files: e = {} } = s.pasteConfig; + let { extensions: t, mimeTypes: o } = e; + !t && !o || (t && !Array.isArray(t) && (L(`«extensions» property of the onDrop config for «${s.name}» Tool should be an array`), t = []), o && !Array.isArray(o) && (L(`«mimeTypes» property of the onDrop config for «${s.name}» Tool should be an array`), o = []), o && (o = o.filter((n) => Dt(n) ? !0 : (L(`MIME type value «${n}» for the «${s.name}» Tool is not a valid MIME type`, "warn"), !1))), this.toolsFiles[s.name] = { + extensions: t || [], + mimeTypes: o || [] + }); + } + /** + * Get RegExp patterns to substitute by Tool + * + * @param tool - BlockTool object + */ + getPatternsConfig(s) { + s.pasteConfig === !1 || !s.pasteConfig.patterns || K(s.pasteConfig.patterns) || Object.entries(s.pasteConfig.patterns).forEach(([e, t]) => { + t instanceof RegExp || L( + `Pattern ${t} for «${s.name}» Tool is skipped because it should be a Regexp instance.`, + "warn" + ), this.toolsPatterns.push({ + key: e, + pattern: t, + tool: s + }); + }); + } + /** + * Check if browser behavior suits better + * + * @param {EventTarget} element - element where content has been pasted + * @returns {boolean} + */ + isNativeBehaviour(s) { + return d.isNativeInput(s); + } + /** + * Get files from data transfer object and insert related Tools + * + * @param {FileList} items - pasted or dropped items + */ + async processFiles(s) { + const { BlockManager: e } = this.Editor; + let t; + t = await Promise.all( + Array.from(s).map((i) => this.processFile(i)) + ), t = t.filter((i) => !!i); + const n = e.currentBlock.tool.isDefault && e.currentBlock.isEmpty; + t.forEach( + (i, r) => { + e.paste(i.type, i.event, r === 0 && n); + } + ); + } + /** + * Get information about file and find Tool to handle it + * + * @param {File} file - file to process + */ + async processFile(s) { + const e = Rt(s), t = Object.entries(this.toolsFiles).find(([i, { mimeTypes: r, extensions: a }]) => { + const [l, c] = s.type.split("/"), u = a.find((f) => f.toLowerCase() === e.toLowerCase()), h = r.find((f) => { + const [m, p] = f.split("/"); + return m === l && (p === c || p === "*"); + }); + return !!u || !!h; + }); + if (!t) + return; + const [o] = t; + return { + event: this.composePasteEvent("file", { + file: s + }), + type: o + }; + } + /** + * Split HTML string to blocks and return it as array of Block data + * + * @param {string} innerHTML - html string to process + * @returns {PasteData[]} + */ + processHTML(s) { + const { Tools: e } = this.Editor, t = d.make("DIV"); + return t.innerHTML = s, this.getNodes(t).map((n) => { + let i, r = e.defaultTool, a = !1; + switch (n.nodeType) { + case Node.DOCUMENT_FRAGMENT_NODE: + i = d.make("div"), i.appendChild(n); + break; + case Node.ELEMENT_NODE: + i = n, a = !0, this.toolsTags[i.tagName] && (r = this.toolsTags[i.tagName].tool); + break; + } + const { tags: l } = r.pasteConfig || { tags: [] }, c = l.reduce((f, m) => (this.collectTagNames(m).forEach((v) => { + const O = j(m) ? m[v] : null; + f[v.toLowerCase()] = O || {}; + }), f), {}), u = Object.assign({}, c, r.baseSanitizeConfig); + if (i.tagName.toLowerCase() === "table") { + const f = q(i.outerHTML, u); + i = d.make("div", void 0, { + innerHTML: f + }).firstChild; + } else + i.innerHTML = q(i.innerHTML, u); + const h = this.composePasteEvent("tag", { + data: i + }); + return { + content: i, + isBlock: a, + tool: r.name, + event: h + }; + }).filter((n) => { + const i = d.isEmpty(n.content), r = d.isSingleTag(n.content); + return !i || r; + }); + } + /** + * Split plain text by new line symbols and return it as array of Block data + * + * @param {string} plain - string to process + * @returns {PasteData[]} + */ + processPlain(s) { + const { defaultBlock: e } = this.config; + if (!s) + return []; + const t = e; + return s.split(/\r?\n/).filter((o) => o.trim()).map((o) => { + const n = d.make("div"); + n.textContent = o; + const i = this.composePasteEvent("tag", { + data: n + }); + return { + content: n, + tool: t, + isBlock: !1, + event: i + }; + }); + } + /** + * Process paste of single Block tool content + * + * @param {PasteData} dataToInsert - data of Block to insert + */ + async processSingleBlock(s) { + const { Caret: e, BlockManager: t } = this.Editor, { currentBlock: o } = t; + if (!o || s.tool !== o.name || !d.containsOnlyInlineElements(s.content.innerHTML)) { + this.insertBlock(s, (o == null ? void 0 : o.tool.isDefault) && o.isEmpty); + return; + } + e.insertContentAtCaretPosition(s.content.innerHTML); + } + /** + * Process paste to single Block: + * 1. Find patterns` matches + * 2. Insert new block if it is not the same type as current one + * 3. Just insert text if there is no substitutions + * + * @param {PasteData} dataToInsert - data of Block to insert + */ + async processInlinePaste(s) { + const { BlockManager: e, Caret: t } = this.Editor, { content: o } = s; + if (e.currentBlock && e.currentBlock.tool.isDefault && o.textContent.length < vt.PATTERN_PROCESSING_MAX_LENGTH) { + const i = await this.processPattern(o.textContent); + if (i) { + const r = e.currentBlock && e.currentBlock.tool.isDefault && e.currentBlock.isEmpty, a = e.paste(i.tool, i.event, r); + t.setToBlock(a, t.positions.END); + return; + } + } + if (e.currentBlock && e.currentBlock.currentInput) { + const i = e.currentBlock.tool.baseSanitizeConfig; + document.execCommand( + "insertHTML", + !1, + q(o.innerHTML, i) + ); + } else + this.insertBlock(s); + } + /** + * Get patterns` matches + * + * @param {string} text - text to process + * @returns {Promise<{event: PasteEvent, tool: string}>} + */ + async processPattern(s) { + const e = this.toolsPatterns.find((o) => { + const n = o.pattern.exec(s); + return n ? s === n.shift() : !1; + }); + return e ? { + event: this.composePasteEvent("pattern", { + key: e.key, + data: s + }), + tool: e.tool.name + } : void 0; + } + /** + * Insert pasted Block content to Editor + * + * @param {PasteData} data - data to insert + * @param {boolean} canReplaceCurrentBlock - if true and is current Block is empty, will replace current Block + * @returns {void} + */ + insertBlock(s, e = !1) { + const { BlockManager: t, Caret: o } = this.Editor, { currentBlock: n } = t; + let i; + if (e && n && n.isEmpty) { + i = t.paste(s.tool, s.event, !0), o.setToBlock(i, o.positions.END); + return; + } + i = t.paste(s.tool, s.event), o.setToBlock(i, o.positions.END); + } + /** + * Insert data passed as application/x-editor-js JSON + * + * @param {Array} blocks — Blocks' data to insert + * @returns {void} + */ + insertEditorJSData(s) { + const { BlockManager: e, Caret: t, Tools: o } = this.Editor; + ht( + s, + (i) => o.blockTools.get(i).sanitizeConfig + ).forEach(({ tool: i, data: r }, a) => { + let l = !1; + a === 0 && (l = e.currentBlock && e.currentBlock.tool.isDefault && e.currentBlock.isEmpty); + const c = e.insert({ + tool: i, + data: r, + replace: l + }); + t.setToBlock(c, t.positions.END); + }); + } + /** + * Fetch nodes from Element node + * + * @param {Node} node - current node + * @param {Node[]} nodes - processed nodes + * @param {Node} destNode - destination node + */ + processElementNode(s, e, t) { + const o = Object.keys(this.toolsTags), n = s, { tool: i } = this.toolsTags[n.tagName] || {}, r = this.tagsByTool[i == null ? void 0 : i.name] || [], a = o.includes(n.tagName), l = d.blockElements.includes(n.tagName.toLowerCase()), c = Array.from(n.children).some( + ({ tagName: h }) => o.includes(h) && !r.includes(h) + ), u = Array.from(n.children).some( + ({ tagName: h }) => d.blockElements.includes(h.toLowerCase()) + ); + if (!l && !a && !c) + return t.appendChild(n), [...e, t]; + if (a && !c || l && !u && !c) + return [...e, t, n]; + } + /** + * Recursively divide HTML string to two types of nodes: + * 1. Block element + * 2. Document Fragments contained text and markup tags like a, b, i etc. + * + * @param {Node} wrapper - wrapper of paster HTML content + * @returns {Node[]} + */ + getNodes(s) { + const e = Array.from(s.childNodes); + let t; + const o = (n, i) => { + if (d.isEmpty(i) && !d.isSingleTag(i)) + return n; + const r = n[n.length - 1]; + let a = new DocumentFragment(); + switch (r && d.isFragment(r) && (a = n.pop()), i.nodeType) { + case Node.ELEMENT_NODE: + if (t = this.processElementNode(i, n, a), t) + return t; + break; + case Node.TEXT_NODE: + return a.appendChild(i), [...n, a]; + default: + return [...n, a]; + } + return [...n, ...Array.from(i.childNodes).reduce(o, [])]; + }; + return e.reduce(o, []); + } + /** + * Compose paste event with passed type and detail + * + * @param {string} type - event type + * @param {PasteEventDetail} detail - event detail + */ + composePasteEvent(s, e) { + return new CustomEvent(s, { + detail: e + }); + } +}; +let xt = vt; +xt.PATTERN_PROCESSING_MAX_LENGTH = 450; +class Xo extends T { + constructor() { + super(...arguments), this.toolsDontSupportReadOnly = [], this.readOnlyEnabled = !1; + } + /** + * Returns state of read only mode + */ + get isEnabled() { + return this.readOnlyEnabled; + } + /** + * Set initial state + */ + async prepare() { + const { Tools: e } = this.Editor, { blockTools: t } = e, o = []; + Array.from(t.entries()).forEach(([n, i]) => { + i.isReadOnlySupported || o.push(n); + }), this.toolsDontSupportReadOnly = o, this.config.readOnly && o.length > 0 && this.throwCriticalError(), this.toggle(this.config.readOnly); + } + /** + * Set read-only mode or toggle current state + * Call all Modules `toggleReadOnly` method and re-render Editor + * + * @param {boolean} state - (optional) read-only state or toggle + */ + async toggle(e = !this.readOnlyEnabled) { + e && this.toolsDontSupportReadOnly.length > 0 && this.throwCriticalError(); + const t = this.readOnlyEnabled; + this.readOnlyEnabled = e; + for (const n in this.Editor) + this.Editor[n].toggleReadOnly && this.Editor[n].toggleReadOnly(e); + if (t === e) + return this.readOnlyEnabled; + const o = await this.Editor.Saver.save(); + return await this.Editor.BlockManager.clear(), await this.Editor.Renderer.render(o.blocks), this.readOnlyEnabled; + } + /** + * Throws an error about tools which don't support read-only mode + */ + throwCriticalError() { + throw new rt( + `To enable read-only mode all connected tools should support it. Tools ${this.toolsDontSupportReadOnly.join(", ")} don't support read-only mode.` + ); + } +} +class pe extends T { + constructor() { + super(...arguments), this.isRectSelectionActivated = !1, this.SCROLL_SPEED = 3, this.HEIGHT_OF_SCROLL_ZONE = 40, this.BOTTOM_SCROLL_ZONE = 1, this.TOP_SCROLL_ZONE = 2, this.MAIN_MOUSE_BUTTON = 0, this.mousedown = !1, this.isScrolling = !1, this.inScrollZone = null, this.startX = 0, this.startY = 0, this.mouseX = 0, this.mouseY = 0, this.stackOfSelected = [], this.listenerIds = []; + } + /** + * CSS classes for the Block + * + * @returns {{wrapper: string, content: string}} + */ + static get CSS() { + return { + overlay: "codex-editor-overlay", + overlayContainer: "codex-editor-overlay__container", + rect: "codex-editor-overlay__rectangle", + topScrollZone: "codex-editor-overlay__scroll-zone--top", + bottomScrollZone: "codex-editor-overlay__scroll-zone--bottom" + }; + } + /** + * Module Preparation + * Creating rect and hang handlers + */ + prepare() { + this.enableModuleBindings(); + } + /** + * Init rect params + * + * @param {number} pageX - X coord of mouse + * @param {number} pageY - Y coord of mouse + */ + startSelection(e, t) { + const o = document.elementFromPoint(e - window.pageXOffset, t - window.pageYOffset); + o.closest(`.${this.Editor.Toolbar.CSS.toolbar}`) || (this.Editor.BlockSelection.allBlocksSelected = !1, this.clearSelection(), this.stackOfSelected = []); + const i = [ + `.${H.CSS.content}`, + `.${this.Editor.Toolbar.CSS.toolbar}`, + `.${this.Editor.InlineToolbar.CSS.inlineToolbar}` + ], r = o.closest("." + this.Editor.UI.CSS.editorWrapper), a = i.some((l) => !!o.closest(l)); + !r || a || (this.mousedown = !0, this.startX = e, this.startY = t); + } + /** + * Clear all params to end selection + */ + endSelection() { + this.mousedown = !1, this.startX = 0, this.startY = 0, this.overlayRectangle.style.display = "none"; + } + /** + * is RectSelection Activated + */ + isRectActivated() { + return this.isRectSelectionActivated; + } + /** + * Mark that selection is end + */ + clearSelection() { + this.isRectSelectionActivated = !1; + } + /** + * Sets Module necessary event handlers + */ + enableModuleBindings() { + const { container: e } = this.genHTML(); + this.listeners.on(e, "mousedown", (t) => { + this.processMouseDown(t); + }, !1), this.listeners.on(document.body, "mousemove", Ee((t) => { + this.processMouseMove(t); + }, 10), { + passive: !0 + }), this.listeners.on(document.body, "mouseleave", () => { + this.processMouseLeave(); + }), this.listeners.on(window, "scroll", Ee((t) => { + this.processScroll(t); + }, 10), { + passive: !0 + }), this.listeners.on(document.body, "mouseup", () => { + this.processMouseUp(); + }, !1); + } + /** + * Handle mouse down events + * + * @param {MouseEvent} mouseEvent - mouse event payload + */ + processMouseDown(e) { + if (e.button !== this.MAIN_MOUSE_BUTTON) + return; + e.target.closest(d.allInputsSelector) !== null || this.startSelection(e.pageX, e.pageY); + } + /** + * Handle mouse move events + * + * @param {MouseEvent} mouseEvent - mouse event payload + */ + processMouseMove(e) { + this.changingRectangle(e), this.scrollByZones(e.clientY); + } + /** + * Handle mouse leave + */ + processMouseLeave() { + this.clearSelection(), this.endSelection(); + } + /** + * @param {MouseEvent} mouseEvent - mouse event payload + */ + processScroll(e) { + this.changingRectangle(e); + } + /** + * Handle mouse up + */ + processMouseUp() { + this.clearSelection(), this.endSelection(); + } + /** + * Scroll If mouse in scroll zone + * + * @param {number} clientY - Y coord of mouse + */ + scrollByZones(e) { + if (this.inScrollZone = null, e <= this.HEIGHT_OF_SCROLL_ZONE && (this.inScrollZone = this.TOP_SCROLL_ZONE), document.documentElement.clientHeight - e <= this.HEIGHT_OF_SCROLL_ZONE && (this.inScrollZone = this.BOTTOM_SCROLL_ZONE), !this.inScrollZone) { + this.isScrolling = !1; + return; + } + this.isScrolling || (this.scrollVertical(this.inScrollZone === this.TOP_SCROLL_ZONE ? -this.SCROLL_SPEED : this.SCROLL_SPEED), this.isScrolling = !0); + } + /** + * Generates required HTML elements + * + * @returns {Object"+(a.trim()?a:r)+"
");const u=Object.keys(this.toolsTags).reduce((m,p)=>(m[p.toLowerCase()]=this.toolsTags[p].sanitizationConfig??{},m),{}),h=Object.assign({},u,t.getAllInlineToolsSanitizeConfig(),{br:{}}),f=q(a,h);!f.trim()||f.trim()===r||!d.isHTMLString(f)?await this.processText(r):await this.processText(f,!0)}async processText(s,e=!1){const{Caret:t,BlockManager:o}=this.Editor,n=e?this.processHTML(s):this.processPlain(s);if(!n.length)return;if(n.length===1){n[0].isBlock?this.processSingleBlock(n.pop()):this.processInlinePaste(n.pop());return}const r=o.currentBlock&&o.currentBlock.tool.isDefault&&o.currentBlock.isEmpty;n.map(async(a,l)=>this.insertBlock(a,l===0&&r)),o.currentBlock&&t.setToBlock(o.currentBlock,t.positions.END)}setCallback(){this.listeners.on(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}unsetCallback(){this.listeners.off(this.Editor.UI.nodes.holder,"paste",this.handlePasteEvent)}processTools(){const s=this.Editor.Tools.blockTools;Array.from(s.values()).forEach(this.processTool)}collectTagNames(s){return Q(s)?[s]:F(s)?Object.keys(s):[]}getTagsConfig(s){if(s.pasteConfig===!1)return;const e=s.pasteConfig.tags||[],t=[];e.forEach(o=>{const n=this.collectTagNames(o);t.push(...n),n.forEach(i=>{if(Object.prototype.hasOwnProperty.call(this.toolsTags,i)){L(`Paste handler for «${s.name}» Tool on «${i}» tag is skipped because it is already used by «${this.toolsTags[i].tool.name}» Tool.`,"warn");return}const r=F(o)?o[i]:null;this.toolsTags[i.toUpperCase()]={tool:s,sanitizationConfig:r}})}),this.tagsByTool[s.name]=t.map(o=>o.toUpperCase())}getFilesConfig(s){if(s.pasteConfig===!1)return;const{files:e={}}=s.pasteConfig;let{extensions:t,mimeTypes:o}=e;!t&&!o||(t&&!Array.isArray(t)&&(L(`«extensions» property of the onDrop config for «${s.name}» Tool should be an array`),t=[]),o&&!Array.isArray(o)&&(L(`«mimeTypes» property of the onDrop config for «${s.name}» Tool should be an array`),o=[]),o&&(o=o.filter(n=>Nt(n)?!0:(L(`MIME type value «${n}» for the «${s.name}» Tool is not a valid MIME type`,"warn"),!1))),this.toolsFiles[s.name]={extensions:t||[],mimeTypes:o||[]})}getPatternsConfig(s){s.pasteConfig===!1||!s.pasteConfig.patterns||Z(s.pasteConfig.patterns)||Object.entries(s.pasteConfig.patterns).forEach(([e,t])=>{t instanceof RegExp||L(`Pattern ${t} for «${s.name}» Tool is skipped because it should be a Regexp instance.`,"warn"),this.toolsPatterns.push({key:e,pattern:t,tool:s})})}isNativeBehaviour(s){return d.isNativeInput(s)}async processFiles(s){const{BlockManager:e}=this.Editor;let t;t=await Promise.all(Array.from(s).map(i=>this.processFile(i))),t=t.filter(i=>!!i);const n=e.currentBlock.tool.isDefault&&e.currentBlock.isEmpty;t.forEach((i,r)=>{e.paste(i.type,i.event,r===0&&n)})}async processFile(s){const e=At(s),t=Object.entries(this.toolsFiles).find(([i,{mimeTypes:r,extensions:a}])=>{const[l,c]=s.type.split("/"),u=a.find(f=>f.toLowerCase()===e.toLowerCase()),h=r.find(f=>{const[m,p]=f.split("/");return m===l&&(p===c||p==="*")});return!!u||!!h});if(!t)return;const[o]=t;return{event:this.composePasteEvent("file",{file:s}),type:o}}processHTML(s){const{Tools:e}=this.Editor,t=d.make("DIV");return t.innerHTML=s,this.getNodes(t).map(n=>{let i,r=e.defaultTool,a=!1;switch(n.nodeType){case Node.DOCUMENT_FRAGMENT_NODE:i=d.make("div"),i.appendChild(n);break;case Node.ELEMENT_NODE:i=n,a=!0,this.toolsTags[i.tagName]&&(r=this.toolsTags[i.tagName].tool);break}const{tags:l}=r.pasteConfig||{tags:[]},c=l.reduce((f,m)=>(this.collectTagNames(m).forEach(v=>{const O=F(m)?m[v]:null;f[v.toLowerCase()]=O||{}}),f),{}),u=Object.assign({},c,r.baseSanitizeConfig);if(i.tagName.toLowerCase()==="table"){const f=q(i.outerHTML,u);i=d.make("div",void 0,{innerHTML:f}).firstChild}else i.innerHTML=q(i.innerHTML,u);const h=this.composePasteEvent("tag",{data:i});return{content:i,isBlock:a,tool:r.name,event:h}}).filter(n=>{const i=d.isEmpty(n.content),r=d.isSingleTag(n.content);return!i||r})}processPlain(s){const{defaultBlock:e}=this.config;if(!s)return[];const t=e;return s.split(/\r?\n/).filter(o=>o.trim()).map(o=>{const n=d.make("div");n.textContent=o;const i=this.composePasteEvent("tag",{data:n});return{content:n,tool:t,isBlock:!1,event:i}})}async processSingleBlock(s){const{Caret:e,BlockManager:t}=this.Editor,{currentBlock:o}=t;if(!o||s.tool!==o.name||!d.containsOnlyInlineElements(s.content.innerHTML)){this.insertBlock(s,(o==null?void 0:o.tool.isDefault)&&o.isEmpty);return}e.insertContentAtCaretPosition(s.content.innerHTML)}async processInlinePaste(s){const{BlockManager:e,Caret:t}=this.Editor,{content:o}=s;if(e.currentBlock&&e.currentBlock.tool.isDefault&&o.textContent.length