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/.eslintrc b/.eslintrc index ef5665480..4504754c2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -31,6 +31,28 @@ "ClientRect": true, "ArrayLike": true, "InputEvent": true, - "unknown": true - } + "unknown": true, + "requestAnimationFrame": true, + "navigator": true + }, + "rules": { + "jsdoc/require-returns-type": "off", + "@typescript-eslint/strict-boolean-expressions": "warn", + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/consistent-type-exports": "error" + }, + "overrides": [ + { + "files": [ + "tsconfig.json", + "package.json", + "tsconfig.*.json", + "tslint.json" + ], + "rules": { + "quotes": [1, "double"], + "semi": [1, "never"], + } + } + ] } 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/workflows/bump-version-on-merge-next.yml b/.github/workflows/bump-version-on-merge-next.yml index d1e94ea01..023bf8097 100644 --- a/.github/workflows/bump-version-on-merge-next.yml +++ b/.github/workflows/bump-version-on-merge-next.yml @@ -1,7 +1,14 @@ name: Bump version on merge +# Caution: +# the use of "pull_request_target" trigger allows to successfully +# run workflow even when triggered from a fork. The trigger grants +# access to repo's secrets and gives write permission to the runner. +# This can be used to run malicious code on untrusted PR, so, please +# DO NOT checkout any PR's ongoing commits (aka github.event.pull_request.head.sha) +# while using this trigger. on: - pull_request: + pull_request_target: branches: - next types: [closed] @@ -11,6 +18,8 @@ jobs: check-for-no-version-changing: if: github.event.pull_request.merged == true runs-on: ubuntu-latest + permissions: + actions: write steps: # Checkout to target branch - uses: actions/checkout@v2 @@ -32,16 +41,22 @@ jobs: uses: codex-team/action-nodejs-package-info@v1 # Stop workflow and do not bump version if it was changed already - - name: Stop workflow and do not bump version if it was changed already - uses: actions/github-script@v3 + - name: Stop workflow if version was changed already if: steps.packageOld.outputs.version != steps.packageNew.outputs.version - with: - script: | - core.setFailed('Version was changed! ${{ steps.packageOld.outputs.version }} -> ${{ steps.packageNew.outputs.version }}') + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/cancel bump-version: needs: check-for-no-version-changing runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: # Checkout to target branch - uses: actions/checkout@v2 @@ -49,8 +64,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..02ffb1b33 100644 --- a/.github/workflows/create-a-release-draft.yml +++ b/.github/workflows/create-a-release-draft.yml @@ -1,7 +1,14 @@ name: Create a release draft +# Caution: +# the use of "pull_request_target" trigger allows to successfully +# run workflow even when triggered from a fork. The trigger grants +# access to repo's secrets and gives write permission to the runner. +# This can be used to run malicious code on untrusted PR, so, please +# DO NOT checkout any PR's ongoing commits (aka github.event.pull_request.head.sha) +# while using this trigger. on: - pull_request: + pull_request_target: branches: - next types: [closed] @@ -11,7 +18,12 @@ jobs: check-version-changing: if: github.event.pull_request.merged == true runs-on: ubuntu-latest + permissions: + actions: write steps: + - uses: actions/setup-node@v3 + with: + node-version: 16 # Checkout to target branch - uses: actions/checkout@v2 with: @@ -33,16 +45,21 @@ jobs: # Stop workflow if version was not changed - name: Stop workflow if version was not changed - uses: actions/github-script@v3 if: steps.packageOld.outputs.version == steps.packageNew.outputs.version - with: - script: | - core.setFailed('No version changes. ${{ steps.packageOld.outputs.version }}') + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/cancel # Create a new draft release release-draft: needs: check-version-changing runs-on: ubuntu-latest + permissions: + contents: write steps: # Checkout to target branch - uses: actions/checkout@v2 @@ -53,8 +70,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,15 +103,26 @@ 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/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/editor.js - asset_name: editor.js + asset_path: dist/editorjs.mjs + asset_name: editorjs.mjs asset_content_type: application/javascript # Send a notification message diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index b7607a99c..e9ed6ae24 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -1,45 +1,28 @@ -name: Tests +name: Cypress + on: [pull_request] + jobs: - firefox: - runs-on: ubuntu-latest - container: - image: cypress/browsers:node14.17.0-chrome88-ff89 - options: --user 1001 + run-tests: + strategy: + matrix: + browser: [firefox, chrome, edge] + + runs-on: ubuntu-22.04 steps: + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: 16 - - uses: actions/checkout@v2 - - run: yarn ci:pull_paragraph - - uses: cypress-io/github-action@v2 + node-version: 18 + + - name: Setup Firefox + if: matrix.browser == 'firefox' + uses: browser-actions/setup-firefox@v1 with: - config: video=false - browser: firefox - build: yarn build - chrome: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-node@v3 - with: - node-version: 16 - - 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/setup-node@v3 - with: - node-version: 16 - - uses: actions/checkout@v2 - - run: yarn ci:pull_paragraph - - uses: cypress-io/github-action@v2 + firefox-version: '115.0esr' + + - uses: cypress-io/github-action@v6 with: config: video=false - browser: edge - build: yarn build + browser: ${{ matrix.browser }} + build: yarn build:test diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 052c68355..c85e5ca9b 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -9,16 +9,17 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Cache node modules - uses: actions/cache@v1 + - uses: actions/setup-node@v3 with: - path: node_modules + node-version: 18 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.OS }}-build-${{ env.cache-name }}- - ${{ runner.OS }}-build- - ${{ runner.OS }}- - - - run: yarn install + ${{ runner.os }}-node- + - 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..e867a7ede 100644 --- a/.github/workflows/publish-package-to-npm.yml +++ b/.github/workflows/publish-package-to-npm.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: # Checkout to target branch - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: # Pull submodules submodules: 'recursive' @@ -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 @@ -46,9 +46,11 @@ jobs: notify: needs: publish runs-on: ubuntu-latest + env: + GITHUB_LINK: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }} steps: # Checkout to target branch - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Get package info id: package @@ -58,6 +60,6 @@ jobs: uses: codex-team/action-codexbot-notify@v1 with: webhook: ${{ secrets.CODEX_BOT_NOTIFY_EDITORJS_PUBLIC_CHAT }} - message: '📦 [${{ steps.package.outputs.name }}](${{ steps.package.outputs.npmjs-link }}) ${{ steps.package.outputs.version }} was published' + message: '📦 [${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }}](${{ env.GITHUB_LINK }}) was published' parse_mode: 'markdown' disable_web_page_preview: true diff --git a/.gitignore b/.gitignore index db93fc475..150d50708 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ dist/ coverage/ .nyc_output/ +.vscode/launch.json diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ad185a9e5..000000000 --- a/.gitmodules +++ /dev/null @@ -1,57 +0,0 @@ -[submodule "example/tools/inline-code"] - path = example/tools/inline-code - url = https://github.com/editor-js/inline-code -[submodule "example/tools/header"] - path = example/tools/header - url = https://github.com/editor-js/header -[submodule "example/tools/delimiter"] - path = example/tools/delimiter - url = https://github.com/editor-js/delimiter -[submodule "example/tools/list"] - path = example/tools/list - url = https://github.com/editor-js/list -[submodule "example/tools/quote"] - path = example/tools/quote - url = https://github.com/editor-js/quote -[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 -[submodule "example/tools/code"] - path = example/tools/code - url = https://github.com/editor-js/code -[submodule "example/tools/image"] - path = example/tools/image - url = https://github.com/editor-js/image -[submodule "example/tools/embed"] - path = example/tools/embed - url = https://github.com/editor-js/embed -[submodule "example/tools/table"] - path = example/tools/table - url = https://github.com/editor-js/table -[submodule "example/tools/checklist"] - path = example/tools/checklist - url = https://github.com/editor-js/checklist -[submodule "example/tools/link"] - path = example/tools/link - url = https://github.com/editor-js/link -[submodule "example/tools/raw"] - path = example/tools/raw - url = https://github.com/editor-js/raw -[submodule "example/tools/warning"] - path = example/tools/warning - url = https://github.com/editor-js/warning -[submodule "example/tools/underline"] - path = example/tools/underline - url = https://github.com/editor-js/underline -[submodule "example/tools/nested-list"] - path = example/tools/nested-list - url = https://github.com/editor-js/nested-list -[submodule "example/tools/text-variant-tune"] - path = example/tools/text-variant-tune - url = https://github.com/editor-js/text-variant-tune 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/.nvmrc b/.nvmrc new file mode 100644 index 000000000..ef33d6510 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18.20.1 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 2ea846ead..eff9df7e1 100644 --- a/README.md +++ b/README.md @@ -116,14 +116,17 @@ Take a look at the [example.html](example/example.html) to view more detailed ex -- Unified Toolbox +- Unified Toolbars - [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 + - [x] Block Tunes support nested menus + - [x] Block Tunes support separators + - [x] Conversion Menu added to the Block Tunes + - [x] Unified Toolbar supports hints + - [x] Conversion Toolbar uses Unified Toolbar + - [x] Inline Toolbar uses Unified Toolbar - Collaborative editing - [ ] Implement Inline Tools JSON format - [ ] Operations Observer, Executor, Manager, Transformer diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 000000000..58f69cfe7 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from 'cypress'; +import path from 'node:path'; +import vitePreprocessor from 'cypress-vite'; + +export default defineConfig({ + env: { + NODE_ENV: 'test', + }, + fixturesFolder: 'test/cypress/fixtures', + screenshotsFolder: 'test/cypress/screenshots', + video: false, + videosFolder: 'test/cypress/videos', + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + on('file:preprocessor', vitePreprocessor({ + configFile: path.resolve(__dirname, './vite.config.test.js'), + })); + + /** + * Plugin for cypress that adds better terminal output for easier debugging. + * Prints cy commands, browser console logs, cy.request and cy.intercept data. Great for your pipelines. + * https://github.com/archfz/cypress-terminal-report + */ + require('cypress-terminal-report/src/installLogsPrinter')(on); + + require('@cypress/code-coverage/task')(on, config); + }, + specPattern: 'test/cypress/tests/**/*.cy.{js,jsx,ts,tsx}', + supportFile: 'test/cypress/support/index.ts', + }, + 'retries': { + // Configure retry attempts for `cypress run` + 'runMode': 2, + // Configure retry attempts for `cypress open` + 'openMode': 0, + }, +}); diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 2bed0b88a..000000000 --- a/cypress.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "env": { - "NODE_ENV": "test" - }, - "fixturesFolder": "test/cypress/fixtures", - "integrationFolder": "test/cypress/tests", - "screenshotsFolder": "test/cypress/screenshots", - "videosFolder": "test/cypress/videos", - "supportFile": "test/cypress/support/index.ts", - "pluginsFile": "test/cypress/plugins/index.ts" -} diff --git a/devserver.js b/devserver.js deleted file mode 100644 index 5087a7b47..000000000 --- a/devserver.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Server for testing example page on mobile devices. - * - * Usage: - * 1. run `yarn devserver:start` - * 2. Open `http://{ip_address}:3000/example/example-dev.html` - * where {ip_address} is IP of your machine. - * - * Also, can serve static files from `/example` or `/dist` on any device in local network. - */ -const path = require('path'); -const fs = require('fs'); -const http = require('http'); -const { networkInterfaces } = require('os'); - -const port = 3000; -const localhost = '127.0.0.1'; -const nonRoutableAddress = '0.0.0.0'; -const host = getHost(); -const server = http.createServer(serveStatic([ - '/example', - '/dist', -])); - -server.listen(port, nonRoutableAddress, () => { - console.log(` - -${wrapInColor('Editor.js 💖', consoleColors.hiColor)} devserver is running ᕕ(⌐■_■)ᕗ ✨ ---------------------------------------------- -${wrapInColor('http://' + host + ':' + port + '/example/example-dev.html', consoleColors.fgGreen)} ---------------------------------------------- -Page can be opened from any device connected to the same local network. -`); - - if (host === localhost) { - console.log(wrapInColor('Looks like you are not connected to any Network so you couldn\'t debug the Editor on your mobile device at the moment.', consoleColors.fgRed)); - } -}); - -/** - * Serves files from specified directories - * - * @param {string[]} paths - directories files from which should be served - * @returns {Function} - */ -function serveStatic(paths) { - return (request, response) => { - const resource = request.url; - const isPathAllowed = paths.find(p => resource.startsWith(p)); - - if (!isPathAllowed) { - response.writeHead(404); - response.end(); - - return; - } - const filePath = path.join(__dirname, resource); - - try { - const stat = fs.statSync(filePath); - - response.writeHead(200, { - 'Content-Length': stat.size, - }); - const readStream = fs.createReadStream(filePath); - - readStream.on('error', e => { - throw e; - }); - readStream.pipe(response); - } catch (e) { - response.writeHead(500); - response.end(e.toString()); - } - }; -} - -/** - * Returns IP address of a machine - * - * @returns {string} - */ -function getHost() { - const nets = networkInterfaces(); - const results = {}; - - for (const name of Object.keys(nets)) { - for (const net of nets[name]) { - // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses - if (net.family === 'IPv4' && !net.internal) { - if (!results[name]) { - results[name] = []; - } - results[name].push(net.address); - } - } - } - - /** - * Offline case - */ - if (Object.keys(results).length === 0) { - return localhost; - } - - return results['en0'][0]; -} - -/** - * Terminal output colors - */ -const consoleColors = { - fgMagenta: 35, - fgRed: 31, - fgGreen: 32, - hiColor: 1, -}; - -/** - * Set a terminal color to the message - * - * @param {string} msg - text to wrap - * @param {string} color - color - * @returns {string} - */ -function wrapInColor(msg, color) { - return '\x1b[' + color + 'm' + msg + '\x1b[0m'; -} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 996914d37..751942620 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,11 +1,199 @@ # Changelog +### 2.31.3 + +- `Fix` - Prevent text formatting removal when applying link + +### 2.31.2 + +- `Fix` - Prevent link removal when applying bold to linked text + +### 2.31.1 + +- `Fix` - Prevent the warning from appearing when `readOnly` mode is initially set to `true` + +### 2.31.0 + +- `New` - Inline tools (those with `isReadOnlySupported` specified) can now be used in read-only mode +- `New` - Inline tools (those with `isReadOnlySupported` specified) shortcuts now work in read-only mode +- `Improvement` - Block manager passes target tool config to the `conversionConfig.import` method on conversion +- `Fix` - Fix selection of first block in read-only initialization with "autofocus=true" +- `Fix` - Incorrect caret position after blocks merging in Safari +- `Fix` - Several toolbox items exported by the one tool have the same shortcut displayed in toolbox +- `Improvement` - The current block reference will be updated in read-only mode when blocks are clicked +- `Fix` - codex-notifier and codex-tooltip moved from devDependencies to dependencies in package.json to solve type errors +- `Fix` - Handle whitespace input in empty placeholder elements to prevent caret from moving unexpectedly to the end of the placeholder +- `Fix` - Fix the memory leak issue in `Shortcuts` class +- `Fix` - Fix when / overides selected text outside of the editor +- `DX` - Tools submodules removed from the repository +- `Improvement` - Shift + Down/Up will allow to select next/previous line instead of Inline Toolbar flipping +- `Improvement` - The API `caret.setToBlock()` offset now works across the entire block content, not just the first or last node. +- `Improvement` - The API `blocks.renderFromHTML()` became async and now can be awaited. +- `Fix` - `blocks.renderFromHTML()` — Error "Can't find a Block to remove." fixed +- `Fix` - The API `.clear()` index invalidation fixed + + + +### 2.30.7 + +- `Fix` - Link insertion in Safari fixed + +### 2.30.6 + +- `Fix` – Fix the display of ‘Convert To’ near blocks that do not have the ‘conversionConfig.export’ rule specified +- `Fix` – The Plus button does not appear when the editor is loaded in an iframe in Chrome +- `Fix` - Prevent inline toolbar from closing in nested instance of editor + +### 2.30.5 + +– `Fix` – Fix exported types + +### 2.30.4 + +- `Fix` - Tool's exporting types added + +### 2.30.3 + +- `Fix` – I18n in nested popover + +### 2.30.2 + +- `Fix` – The onChange callback won't be fired when editor is initialized in the Read-Only mode +- `Fix` – Convert To supports i18n again +- `Fix` – Prevent form submit on inline tool click + +### 2.30.1 + +- `Fix` – Remove fake selection after multiple "convert to" inline tool toggles + +### 2.30.0 + +- `New` – Block Tunes now supports nesting items +- `New` – Block Tunes now supports separator items +- `New` – *Menu Config* – New item type – HTML +- `New` – *Menu Config* – Default and HTML items now support hints +- `New` – Inline Toolbar has new look 💅 +- `New` – Inline Tool's `render()` now supports [Menu Config](https://editorjs.io/menu-config/) format +- `New` – *ToolsAPI* – All installed block tools now accessible via ToolsAPI `getBlockTools()` method +- `New` – *SelectionAPI* – Exposed methods `save()` and `restore()` that allow to save selection to be able to temporally move focus away, methods `setFakeBackground()` and `removeFakeBackground()` that allow to immitate selection while focus moved away +- `New` – *BlocksAPI* – Exposed `getBlockByElement()` method that helps find block by any child html element +- `New` – "Convert to" control is now also available in Block Tunes +- `New` — Editor.js now supports contenteditable placeholders out of the box. Just add `data-placeholder` or `data-placeholder-active` attribute to make it work. The first one will work like native placeholder while the second one will show placeholder only when block is current. +- `Improvement` — Now Paragraph placeholder will be shown for the current paragraph, not only the first one. +- `Improvement` - The API `blocks.update` now accepts `tunes` data as optional third argument and makes `data` - block data as optional. +- `Improvement` — The ability to merge blocks of different types (if both tools provide the conversionConfig) +- `Improvement` - The API `blocks.convert()` now returns the new block API +- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id +- `Improvement` – *MenuConfig* – `TunesMenuConfig` type is deprecated, use the `MenuConfig` instead +– `Improvement` — *Types* — `BlockToolConstructorOptions` type improved, `block` and `config` are not optional anymore +- `Improvement` - The Plus button and Block Tunes toggler are now better aligned with large line-height blocks, such as Headings +- `Improvement` — Creating links on Android devices: now the mobile keyboard will have an "Enter" key for accepting the inserted link. +- `Improvement` — Placeholders will stay visible on inputs focus. +– `Refactoring` – Switched to Vite as Cypress bundler +- `Fix` — `onChange` will be called when removing the entire text within a descendant element of a block. +- `Fix` - Unexpected new line on Enter press with selected block without caret +- `Fix` - Search input autofocus loosing after Block Tunes opening +- `Fix` - Block removing while Enter press on Block Tunes +- `Fix` – Unwanted scroll on first typing on iOS devices +- `Fix` - Unwanted soft line break on Enter press after period and space (". |") on iOS devices +- `Fix` - Caret lost after block conversion on mobile devices. +- `Fix` - Caret lost after Backspace at the start of block when previoius block is not convertable +– `Fix` — Deleting whitespaces at the start/end of the block +- `Fix` — The problem caused by missed "import type" in block mutation event types resolved + +### 2.29.1 + +- `Fix` — Toolbox wont be shown when Slash pressed with along with Shift or Alt +- `Fix` — Toolbox will be opened when Slash pressed in non-US keyboard layout where there is no physical '/' key. + +### 2.29.0 + +- `New` — Editor Config now has the `style.nonce` attribute that could be used to allowlist editor style tag for Content Security Policy "style-src" +- `New` — Toolbox now will be opened by '/' in empty Block instead of Tab +- `New` — Block Tunes now will be opened by 'CMD+/' instead of Tab in non-empty block +- `New` — Tab now will navigate through Blocks. In last block Tab will navigate to the next input on page. +- `Fix` — Passing an empty array via initial data or `blocks.render()` won't break the editor +- `Fix` — Layout did not shrink when a large document cleared in Chrome +- `Fix` — Multiple Tooltip elements creation fixed +- `Fix` — When the focusing Block is out of the viewport, the page will be scrolled. +- `Fix` - Compiler error "This import is never used as a value and must use 'import type'..." fixed +- `Fix` — `blocks.render()` won't lead the `onChange` call in Safari +- `Fix` — Editor wrapper element growing on the Inline Toolbar close +- `Fix` — Fix errors thrown by clicks on a document when the editor is being initialized +- `Fix` — Caret losing on Mobile Devices when adding a block via Toolbox or via Backspace at the beginning of a Block +- `Improvement` — Now you can set focus via arrows/Tab to "contentless" (decorative) blocks like Delimiter which have no inputs. +- `Improvement` — Inline Toolbar sometimes opened in an incorrect position. Now it will be aligned by the left side of the selected text. And won't overflow the right side of the text column. +- `Improvement` - Now the `data-mutation-free` supports deep nesting, so you can mark some element with it to prevent the onChange call caused by child element mutating +- `Improvement` - Now the `data-mutation-free` also allows to skip "characterData" mutations (eg. text content change) +- `Refactoring` — `ce-block--focused` class toggling removed as unused. + +### 2.28.2 + +- `Fix` — Get rid of redundant logs from the build + +### 2.28.1 + +- `Fix` — Some Block were be skipped on saving after pasting them as HTML + +### 2.28.0 + +- `New` - Block ids now displayed in DOM via a data-id attribute. Could be useful for plugins that want to access a Block's element by id. +- `New` - The `blocks.convert(blockId, newType)` API method was added. It allows to convert existing Block to a Block of another type. +- `New` - The `blocks.insertMany()` API method added. It allows to insert several Blocks to the specified index. +- `Improvement` - The Delete keydown at the end of the Block will now work opposite a Backspace at the start. Next Block will be removed (if empty) or merged with the current one. +- `Improvement` - The Delete keydown will work like a Backspace when several Blocks are selected. +- `Improvement` - If we have two empty Blocks, and press Backspace at the start of the second one, the previous will be removed instead of the current. +- `Improvement` - Tools shortcuts could be used to convert one Block to another. +- `Improvement` - Tools shortcuts displayed in the Conversion Toolbar +- `Improvement` - Initialization Loader has been removed. +- `Improvement` - Selection style won't override your custom style for `::selection` outside the editor. +- `Improvement` - Performance optimizations: initialization speed increased, `blocks.render()` API method optimized. Big documents will be displayed faster. +- `Improvement` - "Editor saving" log removed +- `Improvement` - "I'm ready" log removed +- `Improvement` - The stub-block style is simplified. +- `Improvement` - If some Block's tool throws an error during construction, we will show Stub block instead of skipping it during render +- `Improvement` - Call of `blocks.clear()` now will trigger onChange with "block-removed" event for all removed blocks. +- `Improvement` - The `blocks.clear()` now can be awaited. +- `Improvement` - `BlockMutationType` and `BlockMutationEvent` types exported +- `Improvement` - `blocks.update(id, data)` now can accept partial data object — it will update only passed properties, others will remain the same. +- `Improvement` - `blocks.update(id, data)` now will trigger onChange with only `block-change` event. +- `Improvement` - `blocks.update(id, data)` will return a promise with BlockAPI object of the changed block. + + +### 2.27.2 + +- `Fix` - `onChange` won't be called when element with data-mutation-free changes some attribute + +### 2.27.1 + +- `Fix` - `onChange` will be called on removing the whole text in a block + ### 2.27.0 -- `Refactoring` — Popover class refactored. +- `New` — *Toolbar API* — Added a new method for toggling the toolbox. +- `New` — Added types for block mutation events +- `New` — Batching added to the `onChange` callback. Now the second argument can contain an array of CustomEvents as well as a single one. Multiple changes made in a short period of time will be batched under a single `onChange` call. - `Improvement` — *Toolbox* — Number of `close()` method calls optimized. -- `Improvement` — The `onChange` callback won't be triggered only if all mutations contain nodes with the `data-mutation-free` attributes. -- `Fix` — Resolve compiler error from importing the BlockToolData type +- `Improvement` — The `onChange` callback can be muted if all mutations contain nodes with the `data-mutation-free` attribute. +- `Improvement` — Pressing "Enter" at the end of a Block won't lead to redundant `block-changed` event triggering. Only `block-added` event will be dispatched. +- `Improvement` — The block mutation handler is now called on every block change (including background changes), instead of only when a block is focused +- `Improvement` — Number of caret saving method calls optimized for Block Tunes opening/closing. +- `Improvement` — Package size reduced by removing redundant files. +- `Refactoring` — Switched from Webpack to Vite as the build system. +- `Refactoring` — *Dependencies* — Upgraded Cypress to v12 and related libraries to the latest versions. +- `Refactoring` — *Dependencies* — Upgraded TypeScript to v5. +- `Refactoring` — `EventDispatcher` types improved. Now we can pass `EventsMap` via generic to specify a map of event names and their payloads that can be used in a particular EventDispatcher instance. +- `Refactoring` — All events in common editor Event Bus now have own type declarations. +- `Refactoring` — Removed the block mutation observer from blocks and attached a single observer to the editor's blocks wrapper element. +- `Refactoring` — Removed the debounce from the block mutation handler and used batching instead. +- `Refactoring` — Refactored the popover class for better performance and maintenance. +- `Fix` — The `onChange` callback won't trigger when block tunes are opened or closed. +- `Fix` — Resolved a compiler error caused by importing the `BlockToolData` type. +- `Fix` — Resolved a problem where the document would scroll to the beginning after moving a block above the viewport. +- `Fix`- Fixed several bugs caused by browser extensions — Removed the search for a block's container in the DOM on saving and kept it in memory instead, updating it when the tool changes a container element. +- `Fix` — *ToolsAPI* — `pasteConfig` getter with `false` value could be used to disable paste handling by Editor.js core. Could be useful if your tool has its own paste handler. +- `CI` — Ubuntu container is now used for Edge tests runner. +- `CI` — Node 16 is used for GitHib Actions. ### 2.26.5 @@ -46,6 +234,7 @@ - `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 - `New` — *Tools API* — Introducing new feature — toolbox now can have multiple entries for one tool!
diff --git a/docs/api.md b/docs/api.md index 3f009185d..4c7f708de 100644 --- a/docs/api.md +++ b/docs/api.md @@ -79,7 +79,7 @@ use 'move' instead) `insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean)` - insert new Block with passed parameters -`update(id: string, data: BlockToolData)` - updates data for the block with passed id +`update(id: string, data?: BlockToolData, tunes?: {[name: string]: BlockTuneData})` - updates block data and block tunes for the block with passed id #### SanitizerAPI @@ -180,7 +180,7 @@ this.api.notifier.show({ }); ``` -![](https://capella.pics/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg) +![](assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg) Check out [`codex-notifier` package page](https://github.com/codex-team/js-notifier) on GitHub to find docs, params and examples. @@ -203,8 +203,6 @@ After executing the `destroy` method, editor inctance becomes an empty object. T Methods for showing Tooltip helper near your elements. Parameters are the same as in [CodeX Tooltips](http://github.com/codex-team/codex.tooltips) lib. -![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg) - #### Show Method shows tooltip with custom content on passed element diff --git a/docs/assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg b/docs/assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg new file mode 100644 index 000000000..cd81469f7 Binary files /dev/null and b/docs/assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg differ diff --git a/docs/assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg b/docs/assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg new file mode 100644 index 000000000..803ac0281 Binary files /dev/null and b/docs/assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg differ diff --git a/docs/assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg b/docs/assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg new file mode 100644 index 000000000..beb554a8d Binary files /dev/null and b/docs/assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg differ diff --git a/docs/assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg b/docs/assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg new file mode 100644 index 000000000..a0018baa7 Binary files /dev/null and b/docs/assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg differ diff --git a/docs/assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg b/docs/assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg new file mode 100644 index 000000000..b05ac15a9 Binary files /dev/null and b/docs/assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg differ diff --git a/docs/assets/79ce946a-d636-41cd-aa96-d3bc5ecfde03.jpg b/docs/assets/79ce946a-d636-41cd-aa96-d3bc5ecfde03.jpg new file mode 100644 index 000000000..8430c5f94 Binary files /dev/null and b/docs/assets/79ce946a-d636-41cd-aa96-d3bc5ecfde03.jpg differ diff --git a/docs/assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg b/docs/assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg new file mode 100644 index 000000000..f5960e89a Binary files /dev/null and b/docs/assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg differ diff --git a/docs/installation.md b/docs/installation.md index 6d90c6355..6ee6b19a8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -69,7 +69,7 @@ Check [Editor.js's community](https://github.com/editor-js/) to see Tools exampl ## Create Editor instance Create an instance of Editor.js and pass [Configuration Object](../src/types-internal/editor-config.ts). -At least the `holderId` option is required. +At least the `holder` option is required. ```html
@@ -92,7 +92,7 @@ var editor = new EditorJS({ /** * Create a holder for the Editor and pass its ID */ - holderId : 'editorjs', + holder : 'editorjs', /** * Available Tools list. diff --git a/docs/releases.md b/docs/releases.md index 675aaac60..a0fc44d7d 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -33,7 +33,7 @@ There is a [workflow](.github/workflows/publish-package-to-npm.yml) that fired o Use target version changelog as a description. -![](https://capella.pics/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg) +![](assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg) Then you can publish the release and wait for package publishing via action. @@ -44,7 +44,7 @@ This package version will be published to NPM with default `latest` tag. If you want to publish release candidate version, use suffix `-rc.*` for package version in package.json file and in tag on releases page. Workflow will detect it and mark a release as "pre-release". -![](https://capella.pics/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg) +![](assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg) This package version will be published to NPM with `next` tag. diff --git a/docs/tools-inline.md b/docs/tools-inline.md index d76e35307..604a5e4c8 100644 --- a/docs/tools-inline.md +++ b/docs/tools-inline.md @@ -129,8 +129,6 @@ Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitiz You can pass your Tool's title via `title` static getter. It can be used, for example, in the Tooltip with icon description that appears by hover. -![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg) - ```ts export default class BoldInlineTool implements InlineTool { /** diff --git a/docs/tools.md b/docs/tools.md index 7df5ceb64..7cc4fd210 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -70,7 +70,7 @@ to the `tools` property of Editor Config. ```javascript var editor = new EditorJS({ - holderId : 'editorjs', + holder : 'editorjs', tools: { text: { class: Text, @@ -410,7 +410,7 @@ static get sanitize() { Editor.js has a Conversion Toolbar that allows user to convert one Block to another. -![](https://capella.pics/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg) +![](assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg) 1. You can add ability to your Tool to be converted. Specify «export» property of `conversionConfig`. 2. You can add ability to convert other Tools to your Tool. Specify «import» property of `conversionConfig`. diff --git a/docs/usage.md b/docs/usage.md index 5bb43c08f..f1fb4dd98 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -17,12 +17,12 @@ So how to use the Editor after [Installation](installation.md). - Select text fragment and apply a style or insert a link from the Inline Toolbar -![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg) +![](assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg) - Use «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block or apply Tool's settings, if it provided. For example, set a Heading level or List style. -![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg) +![](assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg) ## Shortcuts diff --git a/example/example-i18n.html b/example/example-i18n.html index 930dbaef8..1c40d370e 100644 --- a/example/example-i18n.html +++ b/example/example-i18n.html @@ -10,8 +10,8 @@ Editor.js 🤩🧦🤨 example - - + + @@ -50,7 +50,6 @@ - @@ -61,7 +60,7 @@ - + + diff --git a/example/example-popup.html b/example/example-popup.html index 2579d27b3..e59fd6cd7 100644 --- a/example/example-popup.html +++ b/example/example-popup.html @@ -9,7 +9,7 @@ Editor.js 🤩🧦🤨 example: Popup - + @@ -111,7 +111,7 @@

- + diff --git a/example/example-rtl.html b/example/example-rtl.html index 298381dc2..548c4f4e7 100644 --- a/example/example-rtl.html +++ b/example/example-rtl.html @@ -9,8 +9,8 @@ Editor.js RTL example - - + + @@ -53,24 +53,24 @@ Read more in Tool's README file. For example: https://github.com/editor-js/header#installation --> - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + + + diff --git a/example/tools/checklist b/example/tools/checklist deleted file mode 160000 index b1367277e..000000000 --- a/example/tools/checklist +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b1367277e070bbbf80b7b14b1963845ba9a71d8c diff --git a/example/tools/code b/example/tools/code deleted file mode 160000 index 193f5f6f0..000000000 --- a/example/tools/code +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 193f5f6f00288679a97bfe620a4d811e5acd9b16 diff --git a/example/tools/delimiter b/example/tools/delimiter deleted file mode 160000 index 86e8c5501..000000000 --- a/example/tools/delimiter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 86e8c5501dcbb8eaaeec756e1145db49b8339160 diff --git a/example/tools/embed b/example/tools/embed deleted file mode 160000 index 23de06be6..000000000 --- a/example/tools/embed +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 23de06be69bb9e636a2278b0d54f8c2d85d7ae13 diff --git a/example/tools/header b/example/tools/header deleted file mode 160000 index 80278ee75..000000000 --- a/example/tools/header +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 80278ee75146ff461e9dcaeff1a337167ef97162 diff --git a/example/tools/image b/example/tools/image deleted file mode 160000 index 927ec04ed..000000000 --- a/example/tools/image +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 927ec04edae75fb2e9a83add24be38d439dc3a19 diff --git a/example/tools/inline-code b/example/tools/inline-code deleted file mode 160000 index 7cc94718e..000000000 --- a/example/tools/inline-code +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7cc94718e4c20d6f9db2c236a60b119c39d389e0 diff --git a/example/tools/link b/example/tools/link deleted file mode 160000 index 861de29b1..000000000 --- a/example/tools/link +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 861de29b1d553bb9377dcbaf451af605b28b57bd diff --git a/example/tools/list b/example/tools/list deleted file mode 160000 index f0e9f0110..000000000 --- a/example/tools/list +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f0e9f0110983cd973a1345f2885b18db4fd54636 diff --git a/example/tools/marker b/example/tools/marker deleted file mode 160000 index 13e0b1cf7..000000000 --- a/example/tools/marker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 13e0b1cf72cfa706dc236e617683a5e349a021f5 diff --git a/example/tools/nested-list b/example/tools/nested-list deleted file mode 160000 index c5c473955..000000000 --- a/example/tools/nested-list +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c5c47395516cae0e456881a67a84fd69fec06c47 diff --git a/example/tools/quote b/example/tools/quote deleted file mode 160000 index 02e0db32a..000000000 --- a/example/tools/quote +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 02e0db32a101ec5cfa61210de45be7de647c40c6 diff --git a/example/tools/raw b/example/tools/raw deleted file mode 160000 index b4164eac4..000000000 --- a/example/tools/raw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b4164eac4d81259a15368d7681884e3554554662 diff --git a/example/tools/simple-image b/example/tools/simple-image deleted file mode 160000 index 2d411a650..000000000 --- a/example/tools/simple-image +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2d411a650afa04f0468f7648ee0b5a765362161c diff --git a/example/tools/table b/example/tools/table deleted file mode 160000 index 4a94a1592..000000000 --- a/example/tools/table +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4a94a1592a500ebb6cc570fa1d6216a149b541a0 diff --git a/example/tools/text-variant-tune b/example/tools/text-variant-tune deleted file mode 160000 index 02538b1da..000000000 --- a/example/tools/text-variant-tune +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 02538b1daea722c854cc61b6fbca01b746c21717 diff --git a/example/tools/warning b/example/tools/warning deleted file mode 160000 index 7e706b1cb..000000000 --- a/example/tools/warning +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7e706b1cb67655db75d3a154038e4f11e2d00128 diff --git a/example/example-dev.html b/index.html similarity index 78% rename from example/example-dev.html rename to index.html index f8687184e..6f8ad4bb7 100644 --- a/example/example-dev.html +++ b/index.html @@ -1,17 +1,11 @@ - Editor.js 🤩🧦🤨 example - - + + @@ -33,12 +27,6 @@
-
- No core bundle file found. Run yarn build -
-
- No submodules found. Run yarn pull_tools && yarn tools:update -
editor.save()
@@ -80,40 +68,41 @@
+ - - - - - - - - - - - - - - - - - - - + You can upload Tools to your project's directory and connect them by relative links. + + Also you can load each Tool from CDN or use NPM/Yarn packages. + + Read more at Tools Connection doc: + https://editorjs.io/getting-started#tools-connection + --> + + + + + + + + + + + + + + + - diff --git a/package.json b/package.json index ceebb2803..e17e6f2ac 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,28 @@ { "name": "@editorjs/editorjs", - "version": "2.27.0-rc.1", - "description": "Editor.js — Native JS, based on API and Open Source", - "main": "dist/editor.js", + "version": "2.31.3", + "description": "Editor.js — open source block-style WYSIWYG editor with JSON output", + "main": "dist/editorjs.umd.js", + "module": "dist/editorjs.mjs", "types": "./types/index.d.ts", "keywords": [ "codex editor", "text editor", "editor", "editor.js", - "editorjs" + "editorjs", + "wysiwyg" ], "scripts": { - "clear": "rimraf dist && mkdirp dist", - "build": "yarn clear && yarn build:webpack:prod", - "build:dev": "yarn clear && yarn build:webpack:dev", - "build:webpack:dev": "webpack --mode development --progress --display-error-details --display-entrypoints --watch", - "build:webpack:prod": "webpack --mode production", + "dev": "vite", + "build": "vite build --mode production", + "build:test": "vite build --mode test", "lint": "eslint src/ --ext .ts && yarn lint:tests", "lint:errors": "eslint src/ --ext .ts --quiet", "lint:fix": "eslint src/ --ext .ts --fix", "lint:tests": "eslint test/ --ext .ts", - "ci:pull_paragraph": "git submodule update --init ./src/tools/paragraph", - "pull_tools": "git submodule update --init --recursive", - "_tools:checkout": "git submodule foreach \"git checkout master || git checkout main\"", - "_tools:pull": "git submodule foreach git pull", - "_tools:yarn": "git submodule foreach yarn", - "_tools:build": "git submodule foreach yarn build", - "_tools:make": "yarn _tools:yarn && yarn _tools:build", - "tools:update": "yarn _tools:checkout && yarn _tools:pull && yarn _tools:make", - "test:e2e": "yarn build && cypress run", - "test:e2e:open": "yarn build && cypress open", - "devserver:start": "yarn build && node ./devserver.js" + "test:e2e": "yarn build:test && cypress run", + "test:e2e:open": "yarn build:test && cypress open" }, "author": "CodeX", "license": "Apache-2.0", @@ -40,64 +31,46 @@ "url": "git+https://github.com/codex-team/editor.js.git" }, "devDependencies": { - "@babel/core": "^7.9.0", - "@babel/plugin-transform-runtime": "^7.9.0", - "@babel/polyfill": "^7.8.7", - "@babel/preset-env": "^7.9.5", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.9.0", - "@babel/runtime": "^7.9.2", + "@babel/register": "^7.21.0", + "@codexteam/icons": "0.3.2", "@codexteam/shortcuts": "^1.1.1", - "@cypress/code-coverage": "^3.9.2", - "@cypress/webpack-preprocessor": "^5.6.0", + "@cypress/code-coverage": "^3.10.3", "@editorjs/code": "^2.7.0", "@editorjs/delimiter": "^1.2.0", - "@editorjs/header": "^2.7.0", + "@editorjs/header": "^2.8.8", + "@editorjs/paragraph": "^2.11.6", "@editorjs/simple-image": "^1.4.1", - "@types/node": "^14.14.35", - "@types/webpack": "^4.41.12", - "@types/webpack-env": "^1.15.2", - "babel-loader": "^8.1.0", - "babel-plugin-add-module-exports": "^1.0.0", - "babel-plugin-class-display-name": "^2.1.0", - "babel-plugin-istanbul": "^6.0.0", - "core-js": "3.6.5", - "css-loader": "^3.5.3", - "cssnano": "^4.1.10", - "cypress": "^6.8.0", - "cypress-intellij-reporter": "^0.0.6", - "eslint": "^8.28.0", + "@types/node": "^18.15.11", + "chai-subset": "^1.6.0", + "core-js": "3.30.0", + "cypress": "^13.13.3", + "cypress-intellij-reporter": "^0.0.7", + "cypress-plugin-tab": "^1.0.5", + "cypress-terminal-report": "^5.3.2", + "cypress-vite": "^1.5.0", + "eslint": "^8.37.0", "eslint-config-codex": "^1.7.1", - "eslint-loader": "^4.0.2", "eslint-plugin-chai-friendly": "^0.7.2", - "eslint-plugin-cypress": "^2.12.1", - "extract-text-webpack-plugin": "^3.0.2", - "license-webpack-plugin": "^2.1.4", - "mkdirp": "^1.0.4", + "eslint-plugin-cypress": "2.12.1", + "html-janitor": "^2.0.4", + "nanoid": "^4.0.2", "postcss-apply": "^0.12.0", - "postcss-import": "^12.0.1", - "postcss-loader": "^3.0.0", - "postcss-nested": "^4.1.2", - "postcss-nested-ancestors": "^2.0.0", - "postcss-preset-env": "^6.6.0", - "rimraf": "^3.0.2", - "stylelint": "^13.3.3", - "terser-webpack-plugin": "^2.3.6", - "ts-loader": "^7.0.1", + "postcss-nested": "4.1.2", + "postcss-preset-env": "^8.3.0", + "rollup-plugin-license": "^3.0.1", + "stylelint": "^15.4.0", "tslint": "^6.1.1", - "typescript": "3.8.3", - "webpack": "^4.43.0", - "webpack-cli": "^3.3.11" + "typescript": "5.0.3", + "vite": "^4.2.1", + "vite-plugin-css-injected-by-js": "^3.1.0" }, "collective": { "type": "opencollective", "url": "https://opencollective.com/editorjs" }, "dependencies": { - "@codexteam/icons": "0.1.0", + "@editorjs/caret": "^1.0.1", "codex-notifier": "^1.1.2", - "codex-tooltip": "^1.0.5", - "html-janitor": "^2.0.4", - "nanoid": "^3.1.22" + "codex-tooltip": "^1.0.5" } } diff --git a/example/assets/codex2x.png b/public/assets/codex2x.png similarity index 100% rename from example/assets/codex2x.png rename to public/assets/codex2x.png diff --git a/example/assets/demo.css b/public/assets/demo.css similarity index 100% rename from example/assets/demo.css rename to public/assets/demo.css diff --git a/example/assets/json-preview.js b/public/assets/json-preview.js similarity index 100% rename from example/assets/json-preview.js rename to public/assets/json-preview.js diff --git a/src/codex.ts b/src/codex.ts index 730e5b090..27d50acdb 100644 --- a/src/codex.ts +++ b/src/codex.ts @@ -1,26 +1,25 @@ 'use strict'; -import { EditorConfig } from '../types'; +import type { EditorConfig } from '../types'; /** * Apply polyfills */ import '@babel/register'; -import 'components/polyfills'; +import './components/polyfills'; import Core from './components/core'; import * as _ from './components/utils'; +import { destroy as destroyTooltip } from './components/utils/tooltip'; declare const VERSION: string; /** * Editor.js * - * Short Description (눈_눈;) - * - * @version 2.18.0 * @license Apache-2.0 - * @author CodeX-Team + * @see Editor.js + * @author CodeX Team */ export default class EditorJS { /** @@ -69,6 +68,9 @@ export default class EditorJS { */ this.isReady = editor.isReady.then(() => { this.exportAPI(editor); + /** + * @todo pass API as an argument. It will allow to use Editor's API when editor is ready + */ onReady(); }); } @@ -89,6 +91,8 @@ export default class EditorJS { moduleInstance.listeners.removeAll(); }); + destroyTooltip(); + editor = null; for (const field in this) { diff --git a/src/components/__module.ts b/src/components/__module.ts index c5391422e..a591c25c2 100644 --- a/src/components/__module.ts +++ b/src/components/__module.ts @@ -1,8 +1,9 @@ -import { EditorModules } from '../types-internal/editor-modules'; -import { EditorConfig } from '../../types'; -import { ModuleConfig } from '../types-internal/module-config'; +import type { EditorModules } from '../types-internal/editor-modules'; +import type { EditorConfig } from '../../types'; +import type { ModuleConfig } from '../types-internal/module-config'; import Listeners from './utils/listeners'; -import EventsDispatcher from './utils/events'; +import type EventsDispatcher from './utils/events'; +import type { EditorEventMap } from './events'; /** * The type of the Module generic. @@ -42,7 +43,7 @@ export default class Module> /** * Editor event dispatcher class */ - protected eventsDispatcher: EventsDispatcher; + protected eventsDispatcher: EventsDispatcher; /** * Util for bind/unbind DOM event listeners diff --git a/src/components/block-tunes/block-tune-delete.ts b/src/components/block-tunes/block-tune-delete.ts index 904eefae5..390ea76e1 100644 --- a/src/components/block-tunes/block-tune-delete.ts +++ b/src/components/block-tunes/block-tune-delete.ts @@ -3,9 +3,9 @@ * @classdesc Editor's default tune that moves up selected block * @copyright 2018 */ -import { API, BlockTune } from '../../../types'; +import type { API, BlockTune } from '../../../types'; import { IconCross } from '@codexteam/icons'; -import { TunesMenuConfig } from '../../../types/tools'; +import type { MenuConfig } from '../../../types/tools/menu-config'; /** * @@ -35,7 +35,7 @@ export default class DeleteTune implements BlockTune { /** * Tune's appearance in block settings menu */ - public render(): TunesMenuConfig { + public render(): MenuConfig { return { icon: IconCross, title: this.api.i18n.t('Delete'), diff --git a/src/components/block-tunes/block-tune-move-down.ts b/src/components/block-tunes/block-tune-move-down.ts index 5809c0a07..0e7986a0d 100644 --- a/src/components/block-tunes/block-tune-move-down.ts +++ b/src/components/block-tunes/block-tune-move-down.ts @@ -4,9 +4,9 @@ * @copyright 2018 */ -import { API, BlockTune } from '../../../types'; +import type { API, BlockTune } from '../../../types'; import { IconChevronDown } from '@codexteam/icons'; -import { TunesMenuConfig } from '../../../types/tools'; +import type { TunesMenuConfig } from '../../../types/tools'; /** diff --git a/src/components/block-tunes/block-tune-move-up.ts b/src/components/block-tunes/block-tune-move-up.ts index 242cd83f8..e87d31269 100644 --- a/src/components/block-tunes/block-tune-move-up.ts +++ b/src/components/block-tunes/block-tune-move-up.ts @@ -3,9 +3,9 @@ * @classdesc Editor's default tune that moves up selected block * @copyright 2018 */ -import { API, BlockTune } from '../../../types'; +import type { API, BlockTune } from '../../../types'; import { IconChevronUp } from '@codexteam/icons'; -import { TunesMenuConfig } from '../../../types/tools'; +import type { TunesMenuConfig } from '../../../types/tools'; /** * @@ -82,7 +82,7 @@ export default class MoveUpTune implements BlockTune { if (previousBlockCoords.top > 0) { scrollUpOffset = Math.abs(currentBlockCoords.top) - Math.abs(previousBlockCoords.top); } else { - scrollUpOffset = window.innerHeight - Math.abs(currentBlockCoords.top) + Math.abs(previousBlockCoords.top); + scrollUpOffset = Math.abs(currentBlockCoords.top) + previousBlockCoords.height; } window.scrollBy(0, -1 * scrollUpOffset); diff --git a/src/components/block/api.ts b/src/components/block/api.ts index d760ab63e..4bd26adc5 100644 --- a/src/components/block/api.ts +++ b/src/components/block/api.ts @@ -1,7 +1,7 @@ -import Block from './index'; -import { BlockToolData, ToolConfig } from '../../../types/tools'; -import { SavedData } from '../../../types/data-formats'; -import { BlockAPI as BlockAPIInterface } from '../../../types/api'; +import type Block from './index'; +import type { BlockToolData, ToolConfig, ToolboxConfigEntry } from '../../../types/tools'; +import type { SavedData } from '../../../types/data-formats'; +import type { BlockAPI as BlockAPIInterface } from '../../../types/api'; /** * Constructs new BlockAPI object @@ -84,6 +84,13 @@ function BlockAPI( return block.stretched; }, + /** + * True if Block has inputs to be focused + */ + get focusable(): boolean { + return block.focusable; + }, + /** * Call Tool method with errors handler under-the-hood * @@ -121,6 +128,14 @@ function BlockAPI( dispatchChange(): void { block.dispatchChange(); }, + + /** + * Tool could specify several entries to be displayed at the Toolbox (for example, "Heading 1", "Heading 2", "Heading 3") + * This method returns the entry that is related to the Block (depended on the Block data) + */ + getActiveToolboxEntry(): Promise { + return block.getActiveToolboxEntry(); + }, }; Object.setPrototypeOf(this, blockAPI); diff --git a/src/components/block/index.ts b/src/components/block/index.ts index f76329362..36c4aa2ae 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -1,4 +1,4 @@ -import { +import type { BlockAPI as BlockAPIInterface, BlockTool as IBlockTool, BlockToolData, @@ -6,22 +6,28 @@ import { SanitizerConfig, ToolConfig, ToolboxConfigEntry, - PopoverItem + PopoverItemParams } from '../../../types'; -import { SavedData } from '../../../types/data-formats'; -import $ from '../dom'; +import type { SavedData } from '../../../types/data-formats'; +import $, { toggleEmptyMark } from '../dom'; import * as _ from '../utils'; -import ApiModules from '../modules/api'; +import type ApiModules from '../modules/api'; import BlockAPI from './api'; import SelectionUtils from '../selection'; -import BlockTool from '../tools/block'; +import type BlockToolAdapter from '../tools/block'; -import BlockTune from '../tools/tune'; -import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; -import ToolsCollection from '../tools/collection'; +import type BlockTuneAdapter from '../tools/tune'; +import type { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; +import type ToolsCollection from '../tools/collection'; import EventsDispatcher from '../utils/events'; -import { TunesMenuConfigItem } from '../../../types/tools'; +import type { TunesMenuConfigItem } from '../../../types/tools'; +import { isMutationBelongsToElement } from '../utils/mutations'; +import type { EditorEventMap } from '../events'; +import { FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events'; +import type { RedactorDomChangedPayload } from '../events/RedactorDomChanged'; +import { convertBlockDataToString, isSameBlockData } from '../utils/blocks'; +import { PopoverItemType } from '@/types/utils/popover/popover-item-type'; /** * Interface describes Block class constructor argument @@ -40,7 +46,7 @@ interface BlockConstructorOptions { /** * Tool object */ - tool: BlockTool; + tool: BlockToolAdapter; /** * Editor's API methods @@ -84,9 +90,11 @@ export enum BlockToolAPI { } /** - * Names of events supported by Block class + * Names of events used in Block */ -type BlockEvents = 'didMutated'; +interface BlockEvents { + 'didMutated': Block, +} /** * @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance @@ -105,7 +113,6 @@ export default class Block extends EventsDispatcher { wrapper: 'ce-block', wrapperStretched: 'ce-block--stretched', content: 'ce-block__content', - focused: 'ce-block--focused', selected: 'ce-block--selected', dropTarget: 'ce-block--drop-target', }; @@ -124,7 +131,7 @@ export default class Block extends EventsDispatcher { /** * Instance of the Tool Block represents */ - public readonly tool: BlockTool; + public readonly tool: BlockToolAdapter; /** * User Tool configuration @@ -139,7 +146,7 @@ export default class Block extends EventsDispatcher { /** * Tunes used by Tool */ - public readonly tunes: ToolsCollection; + public readonly tunes: ToolsCollection; /** * Tool's user configuration @@ -148,11 +155,14 @@ export default class Block extends EventsDispatcher { /** * Cached inputs - * - * @type {HTMLElement[]} */ private cachedInputs: HTMLElement[] = []; + /** + * We'll store a reference to the tool's rendered element to access it later + */ + private toolRenderedElement: HTMLElement | null = null; + /** * Tool class instance */ @@ -174,11 +184,6 @@ export default class Block extends EventsDispatcher { */ private unavailableTunesData: { [name: string]: BlockTuneData } = {}; - /** - * Editor`s API module - */ - private readonly api: ApiModules; - /** * Focused input index * @@ -187,89 +192,14 @@ export default class Block extends EventsDispatcher { private inputIndex = 0; /** - * Mutation observer to handle DOM mutations - * - * @type {MutationObserver} + * Common editor event bus */ - private mutationObserver: MutationObserver; + private readonly editorEventBus: EventsDispatcher | null = null; /** - * Debounce Timer - * - * @type {number} + * Link to editor dom change callback. Used to remove listener on remove */ - private readonly modificationDebounceTimer = 450; - - /** - * Is fired when DOM mutation has been happened - * - * mutationsOrInputEvent — actual changes - * - MutationRecord[] - any DOM change - * - InputEvent — change - * - undefined — manual triggering of block.dispatchChange() - */ - private didMutated = _.debounce((mutationsOrInputEvent: MutationRecord[] | InputEvent = undefined): void => { - /** - * We won't fire a Block mutation event if mutation contain only nodes marked with 'data-mutation-free' attributes - */ - let shouldFireUpdate; - - if (mutationsOrInputEvent === undefined) { - shouldFireUpdate = true; - } else if (mutationsOrInputEvent instanceof InputEvent) { - shouldFireUpdate = true; - } else { - /** - * Update from 2023, Feb 17: - * Changed mutationsOrInputEvent.some() to mutationsOrInputEvent.every() - * since there could be a real mutations same-time with mutation-free changes, - * for example when Block Tune change: block is changing along with FakeCursor (mutation-free) removing - * — we should fire 'didMutated' event in that case - */ - const everyRecordIsMutationFree = mutationsOrInputEvent.length > 0 && mutationsOrInputEvent.every((record) => { - const { addedNodes, removedNodes } = record; - const changedNodes = [ - ...Array.from(addedNodes), - ...Array.from(removedNodes), - ]; - - return changedNodes.some((node) => { - if ($.isElement(node) === false) { - return false; - } - - return (node as HTMLElement).dataset.mutationFree === 'true'; - }); - }); - - if (everyRecordIsMutationFree) { - shouldFireUpdate = false; - } else { - shouldFireUpdate = true; - } - } - - /** - * In case some mutation free elements are added or removed, do not trigger didMutated event - */ - if (!shouldFireUpdate) { - return; - } - - /** - * Drop cache - */ - this.cachedInputs = []; - - /** - * Update current input - */ - this.updateCurrentInput(); - - this.call(BlockToolAPI.UPDATED); - - this.emit('didMutated', this); - }, this.modificationDebounceTimer); + private redactorDomChangedCallback: (payload: RedactorDomChangedPayload) => void; /** * Current block API interface @@ -277,49 +207,66 @@ export default class Block extends EventsDispatcher { private readonly blockAPI: BlockAPIInterface; /** - * @param {object} options - block constructor options - * @param {string} [options.id] - block's id. Will be generated if omitted. - * @param {BlockToolData} options.data - Tool's initial data - * @param {BlockTool} options.tool — block's tool + * @param options - block constructor options + * @param [options.id] - block's id. Will be generated if omitted. + * @param options.data - Tool's initial data + * @param options.tool — block's tool * @param options.api - Editor API module for pass it to the Block Tunes - * @param {boolean} options.readOnly - Read-Only flag + * @param options.readOnly - Read-Only flag + * @param [eventBus] - Editor common event bus. Allows to subscribe on some Editor events. Could be omitted when "virtual" Block is created. See BlocksAPI@composeBlockData. */ constructor({ id = _.generateBlockId(), data, tool, - api, readOnly, tunesData, - }: BlockConstructorOptions) { + }: BlockConstructorOptions, eventBus?: EventsDispatcher) { super(); - this.name = tool.name; this.id = id; this.settings = tool.settings; this.config = tool.settings.config || {}; - this.api = api; + this.editorEventBus = eventBus || null; this.blockAPI = new BlockAPI(this); - this.mutationObserver = new MutationObserver(this.didMutated); - this.tool = tool; this.toolInstance = tool.create(data, this.blockAPI, readOnly); /** - * @type {BlockTune[]} + * @type {BlockTuneAdapter[]} */ this.tunes = tool.tunes; this.composeTunes(tunesData); this.holder = this.compose(); + + /** + * Bind block events in RIC for optimizing of constructing process time + */ + window.requestIdleCallback(() => { + /** + * Start watching block mutations + */ + this.watchBlockMutations(); + + /** + * Mutation observer doesn't track changes in "" and "