diff --git a/.github/workflows/bump-version-on-merge-next.yml b/.github/workflows/bump-version-on-merge-next.yml index d1e94ea0..67d303e7 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 4016b7b8..4d923c3c 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/publish-package-to-npm.yml b/.github/workflows/publish-package-to-npm.yml index a614f700..ae926224 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/.vscode/settings.json b/.vscode/settings.json index 8b3ac96d..5d01b0ef 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/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7ec3ddd9..6600d997 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,20 +1,49 @@ # Changelog +### 2.28.0 + +- `New` - Block ids now displayed in DOM via a data-id attribute. Could be useful for plugins that want access a Block's element by id. +- `New` - The `.convert(blockId, newType)` API method added +- `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 current. +- `Improvement` - Tools shortcuts could be used to convert one Block to another. +- `Improvement` - Tools shortcuts displayed in the Conversion Toolbar + +### 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 -- `New` — *Toolbar API* — Toolbox toggling method added. -- `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. -- `Fix` — Resolved a problem when document was being scrolled to the beginning after moving up a Block above the viewport. +- `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. -- `Fix`- Several bugs caused by random browser extensions. -- `Improvement` — *Dependencies* — Upgrade TypeScript to v5. +- `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. -- `Improvement` — *Dependencies* — Upgrade Cypress to v12, upgrade related libraries to latest versions. -- `CI` — Use Ubuntu container for Edge tests runner. -- `Improvement` — Use Vite as build system instead of Webpack. +- `CI` — Ubuntu container is now used for Edge tests runner. +- `CI` — Node 16 is used for GitHib Actions. ### 2.26.5 diff --git a/example/tools/table b/example/tools/table index 3cc50675..605a73d2 160000 --- a/example/tools/table +++ b/example/tools/table @@ -1 +1 @@ -Subproject commit 3cc506758440ac3f1bc83008a6ef75813b6386c3 +Subproject commit 605a73d2b7bec6438c7c0d5ab09eae86b5e9212e diff --git a/index.html b/index.html index db380636..43c8b4ae 100644 --- a/index.html +++ b/index.html @@ -406,4 +406,4 @@ }) - \ No newline at end of file + diff --git a/package.json b/package.json index dd77f292..47f435d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@editorjs/editorjs", - "version": "2.27.0-rc.4", + "version": "2.28.0-rc.1", "description": "Editor.js — Native JS, based on API and Open Source", "main": "dist/editorjs.umd.js", "module": "dist/editorjs.mjs", @@ -47,6 +47,7 @@ "@editorjs/paragraph": "^2.9.0", "@editorjs/simple-image": "^1.4.1", "@types/node": "^18.15.11", + "chai-subset": "^1.6.0", "codex-notifier": "^1.1.2", "codex-tooltip": "^1.0.5", "core-js": "3.30.0", diff --git a/src/components/__module.ts b/src/components/__module.ts index c5391422..4ad521ad 100644 --- a/src/components/__module.ts +++ b/src/components/__module.ts @@ -3,6 +3,7 @@ import { EditorConfig } from '../../types'; import { ModuleConfig } from '../types-internal/module-config'; import Listeners from './utils/listeners'; import EventsDispatcher from './utils/events'; +import { 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/index.ts b/src/components/block/index.ts index e6c863fb..a9befea8 100644 --- a/src/components/block/index.ts +++ b/src/components/block/index.ts @@ -22,6 +22,10 @@ import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; import ToolsCollection from '../tools/collection'; import EventsDispatcher from '../utils/events'; import { TunesMenuConfigItem } from '../../../types/tools'; +import { isMutationBelongsToElement } from '../utils/mutations'; +import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events'; +import { RedactorDomChangedPayload } from '../events/RedactorDomChanged'; +import { convertBlockDataToString } from '../utils/blocks'; /** * Interface describes Block class constructor argument @@ -93,9 +97,11 @@ export enum BlockDropZonePosition { } /** - * 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 @@ -203,89 +209,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 @@ -293,12 +224,13 @@ 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(), @@ -307,7 +239,7 @@ export default class Block extends EventsDispatcher { api, readOnly, tunesData, - }: BlockConstructorOptions) { + }: BlockConstructorOptions, eventBus?: EventsDispatcher) { super(); this.name = tool.name; @@ -315,10 +247,9 @@ export default class Block extends EventsDispatcher { 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); @@ -330,6 +261,17 @@ export default class Block extends EventsDispatcher { this.composeTunes(tunesData); this.holder = this.compose(); + + /** + * Start watching block mutations + */ + this.watchBlockMutations(); + + /** + * Mutation observer doesn't track changes in "" and "