diff --git a/src/components/modules/blockManager.ts b/src/components/modules/blockManager.ts index be8e1e247..8c063b0ad 100644 --- a/src/components/modules/blockManager.ts +++ b/src/components/modules/blockManager.ts @@ -689,13 +689,15 @@ export default class BlockManager extends Module { element = element.parentNode as HTMLElement; } - const nodes = this._blocks.nodes, - firstLevelBlock = element.closest(`.${Block.CSS.wrapper}`), - index = nodes.indexOf(firstLevelBlock as HTMLElement); + const firstLevelBlock = element.closest(`.${Block.CSS.wrapper}`); - if (index >= 0) { - return this._blocks[index]; - } + /** + * Resolve the Block by holder identity (as getBlockByChildNode does) instead of + * indexing into the working area's children: the working area may contain + * non-block elements (e.g. decoration nodes added by the host application), + * which would skew a child-list index against the blocks array + */ + return this.blocks.find((block) => block.holder === firstLevelBlock); } /** @@ -732,12 +734,25 @@ export default class BlockManager extends Module { return; } + /** + * Resolve the Block's index by holder identity instead of indexing into the + * working area's children: the working area may contain non-block elements + * (e.g. decoration nodes added by the host application), which would skew a + * child-list index against the blocks array — selecting the wrong Block, or + * crashing on 'updateCurrentInput' when the found index is past the array end + */ + const index = this.blocks.findIndex((block) => block.holder === parentFirstLevelBlock); + + if (index === -1) { + return; + } + /** * Update current Block's index * * @type {number} */ - this.currentBlockIndex = this._blocks.nodes.indexOf(parentFirstLevelBlock as HTMLElement); + this.currentBlockIndex = index; /** * Update current block active input diff --git a/test/cypress/tests/modules/Ui.cy.ts b/test/cypress/tests/modules/Ui.cy.ts index eaf2246a8..e6f9f63ff 100644 --- a/test/cypress/tests/modules/Ui.cy.ts +++ b/test/cypress/tests/modules/Ui.cy.ts @@ -117,6 +117,45 @@ describe('Ui module', function () { }); }); + it('should update current block by click on block when the redactor contains non-block elements', function () { + createEditorWithTextBlocks([ + 'first block', + 'second block', + 'third block', + ]) + .as('editorInstance'); + + /** + * Insert a non-block element between blocks — host applications do this + * for visual decorations (e.g. page-break spacers in a paginated view) + */ + cy.get('[data-cy=editorjs]') + .find('.codex-editor__redactor') + .then(($redactor) => { + const spacer = document.createElement('div'); + + spacer.setAttribute('data-mutation-free', 'true'); + $redactor[0].insertBefore(spacer, $redactor[0].lastElementChild); + }); + + /** + * Click the block BELOW the non-block element: a child-list index would + * be skewed by the extra element (resolving past the end of the blocks + * array and throwing on 'updateCurrentInput') + */ + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .eq(2) + .click(); + + cy.get('@editorInstance') + .then(async (editor) => { + const currentBlockIndex = await editor.blocks.getCurrentBlockIndex(); + + expect(currentBlockIndex).to.eq(2); + }); + }); + it('(in readonly) should update current block by click on block', function () { createEditorWithTextBlocks([ 'first block',