From 33de6fef6718e5b24f5787ef1256f24a24a9aa0d Mon Sep 17 00:00:00 2001 From: Daniel Pivovarov <98488056+aguydan@users.noreply.github.com> Date: Fri, 14 Jun 2024 05:51:52 +0300 Subject: [PATCH] fix(horizontal-rule): fix insertion behavior (#4898) --- .../Nodes/HorizontalRule/React/index.spec.js | 22 +++++++ .../Nodes/HorizontalRule/Vue/index.spec.js | 22 +++++++ .../src/horizontal-rule.ts | 23 +++++-- .../extensions/horizontalRule.spec.ts | 65 +++++++++++++++++++ 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 tests/cypress/integration/extensions/horizontalRule.spec.ts diff --git a/demos/src/Nodes/HorizontalRule/React/index.spec.js b/demos/src/Nodes/HorizontalRule/React/index.spec.js index d4af550c5..051182cef 100644 --- a/demos/src/Nodes/HorizontalRule/React/index.spec.js +++ b/demos/src/Nodes/HorizontalRule/React/index.spec.js @@ -54,4 +54,26 @@ context('/src/Nodes/HorizontalRule/React/', () => { cy.get('.tiptap hr').should('exist') }) }) + + it('should replace selection correctly', () => { + cy.get('.tiptap').then(([{ editor }]) => { + editor.commands.setContent('

Example Text

Example Text

') + + // From the start of the document to the start of the second textblock. + editor.commands.setTextSelection({ from: 0, to: 15 }) + editor.commands.setHorizontalRule() + + expect(editor.getHTML()).to.eq('

Example Text

') + + editor.commands.setContent('

Example Text

Example Text

') + + // From the end of the first textblock to the start of the second textblock. + editor.commands.setTextSelection({ from: 13, to: 15 }) + editor.commands.setHorizontalRule() + + expect(editor.getHTML()).to.eq( + '

Example Text


Example Text

', + ) + }) + }) }) diff --git a/demos/src/Nodes/HorizontalRule/Vue/index.spec.js b/demos/src/Nodes/HorizontalRule/Vue/index.spec.js index 764a69c2d..69d1707b9 100644 --- a/demos/src/Nodes/HorizontalRule/Vue/index.spec.js +++ b/demos/src/Nodes/HorizontalRule/Vue/index.spec.js @@ -63,4 +63,26 @@ context('/src/Nodes/HorizontalRule/Vue/', () => { .should('exist') }) }) + + it('should replace selection correctly', () => { + cy.get('.tiptap').then(([{ editor }]) => { + editor.commands.setContent('

Example Text

Example Text

') + + // From the start of the document to the start of the second textblock. + editor.commands.setTextSelection({ from: 0, to: 15 }) + editor.commands.setHorizontalRule() + + expect(editor.getHTML()).to.eq('

Example Text

') + + editor.commands.setContent('

Example Text

Example Text

') + + // From the end of the first textblock to the start of the second textblock. + editor.commands.setTextSelection({ from: 13, to: 15 }) + editor.commands.setHorizontalRule() + + expect(editor.getHTML()).to.eq( + '

Example Text


Example Text

', + ) + }) + }) }) diff --git a/packages/extension-horizontal-rule/src/horizontal-rule.ts b/packages/extension-horizontal-rule/src/horizontal-rule.ts index a80e02968..ae1d56386 100644 --- a/packages/extension-horizontal-rule/src/horizontal-rule.ts +++ b/packages/extension-horizontal-rule/src/horizontal-rule.ts @@ -1,4 +1,6 @@ -import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core' +import { + isNodeSelection, mergeAttributes, Node, nodeInputRule, +} from '@tiptap/core' import { NodeSelection, TextSelection } from '@tiptap/pm/state' export interface HorizontalRuleOptions { @@ -49,12 +51,25 @@ export const HorizontalRule = Node.create({ return { setHorizontalRule: () => ({ chain, state }) => { - const { $to: $originTo } = state.selection + const { selection } = state + const { $from: $originFrom, $to: $originTo } = selection const currentChain = chain() - if ($originTo.parentOffset === 0) { - currentChain.insertContentAt(Math.max($originTo.pos - 2, 0), { type: this.name }) + if ($originFrom.parentOffset === 0) { + currentChain.insertContentAt( + { + from: Math.max($originFrom.pos - 1, 0), + to: $originTo.pos, + }, + { + type: this.name, + }, + ) + } else if (isNodeSelection(selection)) { + currentChain.insertContentAt($originTo.pos, { + type: this.name, + }) } else { currentChain.insertContent({ type: this.name }) } diff --git a/tests/cypress/integration/extensions/horizontalRule.spec.ts b/tests/cypress/integration/extensions/horizontalRule.spec.ts new file mode 100644 index 000000000..5c9925c0d --- /dev/null +++ b/tests/cypress/integration/extensions/horizontalRule.spec.ts @@ -0,0 +1,65 @@ +import { Editor } from '@tiptap/core' +import Document from '@tiptap/extension-document' +import HorizontalRule from '@tiptap/extension-horizontal-rule' +import Image from '@tiptap/extension-image' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' + +describe('extension-horizontal-rule', () => { + const editorElClass = 'tiptap' + let editor: Editor | null = null + + const createEditorEl = () => { + const editorEl = document.createElement('div') + + editorEl.classList.add(editorElClass) + document.body.appendChild(editorEl) + + return editorEl + } + + const getEditorEl = () => document.querySelector(`.${editorElClass}`) + + it('should be inserted after block leaf nodes correctly', () => { + editor = new Editor({ + element: createEditorEl(), + extensions: [ + Document, + Text, + Paragraph, + HorizontalRule, + Image, + ], + content: { + type: 'doc', + content: [ + { + type: 'image', + attrs: { + src: 'https://source.unsplash.com/8xznAGy4HcY/800x400', + }, + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Example Text', + }, + ], + }, + ], + }, + }) + + editor.commands.setTextSelection(2) + editor.commands.setHorizontalRule() + + expect(editor.getHTML()).to.match( + /

Example Text<\/p>/, + ) + + editor?.destroy() + getEditorEl()?.remove() + }) +})