From e26994e9597ed13461a5729e2585c25d9aeaf7fe Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Sat, 21 Dec 2024 03:52:35 +0000 Subject: [PATCH 1/7] Bug Fix For When / Overides external text --- src/components/modules/ui.ts | 6 +++ test/cypress/tests/modules/Ui.cy.ts | 72 +++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index a4d3baad3..adf8e9648 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -477,6 +477,12 @@ export default class UI extends Module { * @param {KeyboardEvent} event - keyboard event */ private documentKeydown(event: KeyboardEvent): void { + const wasEventTriggeredInsideEditor = this.nodes.wrapper.contains(event.target as Node); + + if (!wasEventTriggeredInsideEditor && !this.someToolbarOpened) { + return; + } + switch (event.keyCode) { case _.keyCodes.ENTER: this.enterPressed(event); diff --git a/test/cypress/tests/modules/Ui.cy.ts b/test/cypress/tests/modules/Ui.cy.ts index eaf2246a8..2009c6917 100644 --- a/test/cypress/tests/modules/Ui.cy.ts +++ b/test/cypress/tests/modules/Ui.cy.ts @@ -3,6 +3,78 @@ import type EditorJS from '../../../../types/index'; describe('Ui module', function () { describe('documentKeydown', function () { + describe('Events outside editor', function () { + it('should ignore keyboard events when target is outside editor', function () { + // Create editor with a paragraph block + cy.createEditor({ + data: { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Editor content', + }, + }, + ], + }, + }); + + // Create a contenteditable div outside the editor + cy.get('[data-cy=editorjs]') + .parent() + .then(($parent) => { + const outsideDiv = document.createElement('div'); + + outsideDiv.contentEditable = 'true'; + outsideDiv.textContent = 'Text outside editor'; + outsideDiv.setAttribute('data-cy', 'outside-editor'); + $parent.append(outsideDiv); + }); + + // Select text outside editor and press slash + cy.get('[data-cy=outside-editor]') + .type('{selectall}') // Select all text in the outside div + .trigger('keydown', { key: '/' }); // Trigger slash key + + // Verify the text outside editor wasn't changed + cy.get('[data-cy=outside-editor]') + .should('have.text', 'Text outside editor'); + + // Verify editor content wasn't affected + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .should('have.text', 'Editor content'); + }); + + it('should handle keyboard events when target is inside editor', function () { + // Create editor with a paragraph block + cy.createEditor({ + data: { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Editor content', + }, + }, + ], + }, + }); + + // Select text inside editor and press slash + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .click() + .type('{selectall}') // Select all text in the paragraph + .type('/'); // Type slash directly instead of triggering keydown + + // Verify the text inside editor was changed + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .should('have.text', '/'); + }); + }); + describe('Backspace', function () { it('should remove selected blocks', function () { cy.createEditor({ From da53f1793dbd854eff9276055dad773991ff66fc Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Sat, 21 Dec 2024 16:03:00 +0000 Subject: [PATCH 2/7] Moved fix to blockEvents --- src/components/modules/blockEvents.ts | 6 ++ src/components/modules/ui.ts | 6 -- .../tests/modules/BlockEvents/Slash.cy.ts | 72 +++++++++++++++++++ test/cypress/tests/modules/Ui.cy.ts | 72 ------------------- 4 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/components/modules/blockEvents.ts b/src/components/modules/blockEvents.ts index c48bba536..b069be14a 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -238,6 +238,12 @@ export default class BlockEvents extends Module { */ private slashPressed(event: KeyboardEvent): void { const currentBlock = this.Editor.BlockManager.currentBlock; + const wasEventTriggeredInsideEditor = this.Editor.UI.nodes.wrapper.contains(event.target as Node); + + if (!wasEventTriggeredInsideEditor || !currentBlock) { + return; + } + const canOpenToolbox = currentBlock.isEmpty; /** diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index adf8e9648..a4d3baad3 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -477,12 +477,6 @@ export default class UI extends Module { * @param {KeyboardEvent} event - keyboard event */ private documentKeydown(event: KeyboardEvent): void { - const wasEventTriggeredInsideEditor = this.nodes.wrapper.contains(event.target as Node); - - if (!wasEventTriggeredInsideEditor && !this.someToolbarOpened) { - return; - } - switch (event.keyCode) { case _.keyCodes.ENTER: this.enterPressed(event); diff --git a/test/cypress/tests/modules/BlockEvents/Slash.cy.ts b/test/cypress/tests/modules/BlockEvents/Slash.cy.ts index 0d9db5fc1..5b81b3c8a 100644 --- a/test/cypress/tests/modules/BlockEvents/Slash.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Slash.cy.ts @@ -92,6 +92,78 @@ describe('Slash keydown', function () { .should('eq', 'Hello/'); }); }); + + describe('pressed outside editor', function () { + it('should not make any changes when target is outside editor', function () { + // Create editor with a paragraph block + cy.createEditor({ + data: { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Editor content', + }, + }, + ], + }, + }); + + // Create a contenteditable div outside the editor + cy.get('[data-cy=editorjs]') + .parent() + .then(($parent) => { + const outsideDiv = document.createElement('div'); + + outsideDiv.contentEditable = 'true'; + outsideDiv.textContent = 'Text outside editor'; + outsideDiv.setAttribute('data-cy', 'outside-editor'); + $parent.append(outsideDiv); + }); + + // Select text outside editor and press slash + cy.get('[data-cy=outside-editor]') + .type('{selectall}') // Select all text in the outside div + .trigger('keydown', { key: '/' }); // Trigger slash key + + // Verify the text outside editor wasn't changed + cy.get('[data-cy=outside-editor]') + .should('have.text', 'Text outside editor'); + + // Verify editor content wasn't affected + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .should('have.text', 'Editor content'); + }); + + it('should make changes when target is inside editor', function () { + // Create editor with a paragraph block + cy.createEditor({ + data: { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Editor content', + }, + }, + ], + }, + }); + + // Select text inside editor and press slash + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .click() + .type('{selectall}') // Select all text in the paragraph + .type('/'); // Type slash directly instead of triggering keydown + + // Verify the text inside editor was changed + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .should('have.text', '/'); + }); + }); }); describe('CMD+Slash keydown', function () { diff --git a/test/cypress/tests/modules/Ui.cy.ts b/test/cypress/tests/modules/Ui.cy.ts index 2009c6917..eaf2246a8 100644 --- a/test/cypress/tests/modules/Ui.cy.ts +++ b/test/cypress/tests/modules/Ui.cy.ts @@ -3,78 +3,6 @@ import type EditorJS from '../../../../types/index'; describe('Ui module', function () { describe('documentKeydown', function () { - describe('Events outside editor', function () { - it('should ignore keyboard events when target is outside editor', function () { - // Create editor with a paragraph block - cy.createEditor({ - data: { - blocks: [ - { - type: 'paragraph', - data: { - text: 'Editor content', - }, - }, - ], - }, - }); - - // Create a contenteditable div outside the editor - cy.get('[data-cy=editorjs]') - .parent() - .then(($parent) => { - const outsideDiv = document.createElement('div'); - - outsideDiv.contentEditable = 'true'; - outsideDiv.textContent = 'Text outside editor'; - outsideDiv.setAttribute('data-cy', 'outside-editor'); - $parent.append(outsideDiv); - }); - - // Select text outside editor and press slash - cy.get('[data-cy=outside-editor]') - .type('{selectall}') // Select all text in the outside div - .trigger('keydown', { key: '/' }); // Trigger slash key - - // Verify the text outside editor wasn't changed - cy.get('[data-cy=outside-editor]') - .should('have.text', 'Text outside editor'); - - // Verify editor content wasn't affected - cy.get('[data-cy=editorjs]') - .find('.ce-paragraph') - .should('have.text', 'Editor content'); - }); - - it('should handle keyboard events when target is inside editor', function () { - // Create editor with a paragraph block - cy.createEditor({ - data: { - blocks: [ - { - type: 'paragraph', - data: { - text: 'Editor content', - }, - }, - ], - }, - }); - - // Select text inside editor and press slash - cy.get('[data-cy=editorjs]') - .find('.ce-paragraph') - .click() - .type('{selectall}') // Select all text in the paragraph - .type('/'); // Type slash directly instead of triggering keydown - - // Verify the text inside editor was changed - cy.get('[data-cy=editorjs]') - .find('.ce-paragraph') - .should('have.text', '/'); - }); - }); - describe('Backspace', function () { it('should remove selected blocks', function () { cy.createEditor({ From 99ffdfadd4a467b351a436dbef230b0ffde7cbf4 Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Sat, 21 Dec 2024 16:12:32 +0000 Subject: [PATCH 3/7] Moved fix to blockEvents --- src/components/modules/blockEvents.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/modules/blockEvents.ts b/src/components/modules/blockEvents.ts index b069be14a..68efb694b 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -237,13 +237,14 @@ export default class BlockEvents extends Module { * @param event - keydown */ private slashPressed(event: KeyboardEvent): void { - const currentBlock = this.Editor.BlockManager.currentBlock; const wasEventTriggeredInsideEditor = this.Editor.UI.nodes.wrapper.contains(event.target as Node); - if (!wasEventTriggeredInsideEditor || !currentBlock) { + if (!wasEventTriggeredInsideEditor) { return; } + const currentBlock = this.Editor.BlockManager.currentBlock; + const canOpenToolbox = currentBlock.isEmpty; /** From 757b7931aea10bfa1b005a343e4e08e1ea79c58b Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Sat, 21 Dec 2024 16:13:09 +0000 Subject: [PATCH 4/7] Moved fix to blockEvents --- src/components/modules/blockEvents.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/modules/blockEvents.ts b/src/components/modules/blockEvents.ts index 68efb694b..40e0973e2 100644 --- a/src/components/modules/blockEvents.ts +++ b/src/components/modules/blockEvents.ts @@ -244,7 +244,6 @@ export default class BlockEvents extends Module { } const currentBlock = this.Editor.BlockManager.currentBlock; - const canOpenToolbox = currentBlock.isEmpty; /** From 642e46a39f7b6e2005257ab1cc948d3e2a2034b5 Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Sat, 21 Dec 2024 19:56:17 +0000 Subject: [PATCH 5/7] Refactored test to simulate behaviour --- .../tests/modules/BlockEvents/Slash.cy.ts | 95 +++++++++---------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/test/cypress/tests/modules/BlockEvents/Slash.cy.ts b/test/cypress/tests/modules/BlockEvents/Slash.cy.ts index 5b81b3c8a..f620b0161 100644 --- a/test/cypress/tests/modules/BlockEvents/Slash.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Slash.cy.ts @@ -94,74 +94,65 @@ describe('Slash keydown', function () { }); describe('pressed outside editor', function () { - it('should not make any changes when target is outside editor', function () { - // Create editor with a paragraph block + it('should not modify any text outside editor when text block is selected', () => { + // Create editor with an empty block cy.createEditor({ data: { blocks: [ { type: 'paragraph', data: { - text: 'Editor content', + text: '', }, }, ], }, }); - // Create a contenteditable div outside the editor - cy.get('[data-cy=editorjs]') - .parent() - .then(($parent) => { - const outsideDiv = document.createElement('div'); - - outsideDiv.contentEditable = 'true'; - outsideDiv.textContent = 'Text outside editor'; - outsideDiv.setAttribute('data-cy', 'outside-editor'); - $parent.append(outsideDiv); - }); - - // Select text outside editor and press slash - cy.get('[data-cy=outside-editor]') - .type('{selectall}') // Select all text in the outside div - .trigger('keydown', { key: '/' }); // Trigger slash key - - // Verify the text outside editor wasn't changed - cy.get('[data-cy=outside-editor]') - .should('have.text', 'Text outside editor'); - - // Verify editor content wasn't affected + // First click the plus button to open the toolbox cy.get('[data-cy=editorjs]') .find('.ce-paragraph') - .should('have.text', 'Editor content'); - }); - - it('should make changes when target is inside editor', function () { - // Create editor with a paragraph block - cy.createEditor({ - data: { - blocks: [ - { - type: 'paragraph', - data: { - text: 'Editor content', - }, - }, - ], - }, - }); + .click(); - // Select text inside editor and press slash - cy.get('[data-cy=editorjs]') - .find('.ce-paragraph') - .click() - .type('{selectall}') // Select all text in the paragraph - .type('/'); // Type slash directly instead of triggering keydown + cy.get('[data-cy="toolbox"] .ce-popover__container') + .should('not.be.visible'); - // Verify the text inside editor was changed - cy.get('[data-cy=editorjs]') - .find('.ce-paragraph') - .should('have.text', '/'); + // Get the heading text content before the slash key press + cy.get('h1') + .contains('Editor.js test page') + .invoke('text') + .then((originalText) => { + // Simulate selecting the heading text + cy.get('h1') + .contains('Editor.js test page') + .trigger('mousedown') + .trigger('mouseup'); + + // Press the slash key + cy.get('h1') + .contains('Editor.js test page') + .trigger('keydown', { + key: '/', + code: 'Slash', + keyCode: 191, + which: 191, + ctrlKey: false, + metaKey: false + }); + + // Verify the heading text hasn't changed + cy.get('h1') + .contains('Editor.js test page') + .should('have.text', originalText); + + // Verify editor content hasn't changed and toolbox isn't open + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .should('have.text', ''); + + cy.get('[data-cy="toolbox"] .ce-popover__container') + .should('not.be.visible'); + }); }); }); }); From 0a1460295ebf518fe01f75dd9924c79757cb97fd Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Tue, 31 Dec 2024 14:00:50 +0000 Subject: [PATCH 6/7] Added fix to change log --- docs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 33aa0a099..08987bb1a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,7 @@ - `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 when / overides selected text outside of the editor ### 2.30.7 From d5f124612fe306fcd034513415ba5ed498b5118c Mon Sep 17 00:00:00 2001 From: Omotayo Obafemi Date: Wed, 8 Jan 2025 00:44:06 +0000 Subject: [PATCH 7/7] Refactored test to mimick exact behaviour of the bug --- .../tests/modules/BlockEvents/Slash.cy.ts | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/test/cypress/tests/modules/BlockEvents/Slash.cy.ts b/test/cypress/tests/modules/BlockEvents/Slash.cy.ts index f620b0161..49fe1fddb 100644 --- a/test/cypress/tests/modules/BlockEvents/Slash.cy.ts +++ b/test/cypress/tests/modules/BlockEvents/Slash.cy.ts @@ -95,7 +95,6 @@ describe('Slash keydown', function () { describe('pressed outside editor', function () { it('should not modify any text outside editor when text block is selected', () => { - // Create editor with an empty block cy.createEditor({ data: { blocks: [ @@ -109,50 +108,42 @@ describe('Slash keydown', function () { }, }); - // First click the plus button to open the toolbox + cy.document().then((doc) => { + const title = doc.querySelector('h1'); + + if (title) { + title.setAttribute('data-cy', 'page-title'); + } + }); + + // Step 1 + // Click on the plus button and select the text option cy.get('[data-cy=editorjs]') .find('.ce-paragraph') .click(); - + cy.get('[data-cy=editorjs]') + .find('.ce-toolbar__plus') + .click({ force: true }); cy.get('[data-cy="toolbox"] .ce-popover__container') - .should('not.be.visible'); + .contains('Text') + .click(); - // Get the heading text content before the slash key press - cy.get('h1') - .contains('Editor.js test page') - .invoke('text') - .then((originalText) => { - // Simulate selecting the heading text - cy.get('h1') - .contains('Editor.js test page') - .trigger('mousedown') - .trigger('mouseup'); - - // Press the slash key - cy.get('h1') - .contains('Editor.js test page') - .trigger('keydown', { - key: '/', - code: 'Slash', - keyCode: 191, - which: 191, - ctrlKey: false, - metaKey: false - }); - - // Verify the heading text hasn't changed - cy.get('h1') - .contains('Editor.js test page') - .should('have.text', originalText); - - // Verify editor content hasn't changed and toolbox isn't open - cy.get('[data-cy=editorjs]') - .find('.ce-paragraph') - .should('have.text', ''); - - cy.get('[data-cy="toolbox"] .ce-popover__container') - .should('not.be.visible'); - }); + // Step 2 + // Select the 'Editor.js test page' text + cy.get('[data-cy=page-title]') + .invoke('attr', 'contenteditable', 'true') + .click() + .type('{selectall}') + .invoke('removeAttr', 'contenteditable'); + + // Step 3 + // Press the Slash key + cy.get('[data-cy=page-title]') + .trigger('keydown', { key: '/', + code: 'Slash', + which: 191 }); + + cy.get('[data-cy=page-title]').should('have.text', 'Editor.js test page'); }); }); });