From a10882a78ce6d26482fad7242ee7f9ebe130560c Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 29 Jul 2025 10:42:03 +0200 Subject: [PATCH 1/3] fix(TextDirection): Only regard changed nodes in `appendTransaction` No need to iterate over all nodes of the document each time. Upstream PR: https://github.com/amirhhashemi/tiptap-text-direction/pull/23 Signed-off-by: Jonas --- src/extensions/TextDirection.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/extensions/TextDirection.ts b/src/extensions/TextDirection.ts index ccb0682e413..ed3af8c0b35 100644 --- a/src/extensions/TextDirection.ts +++ b/src/extensions/TextDirection.ts @@ -3,8 +3,13 @@ * SPDX-License-Identifier: MIT */ -import { Extension } from '@tiptap/core' -import { Plugin, PluginKey } from '@tiptap/pm/state' +import { + Extension, + combineTransactionSteps, + findChildrenInRange, + getChangedRanges, +} from '@tiptap/core' +import { Plugin, PluginKey, Transaction } from '@tiptap/pm/state' const RTL = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC' const LTR = @@ -54,11 +59,21 @@ function TextDirectionPlugin({ types }: { types: string[] }) { } let modified = false - const tr = newState.tr + const { tr } = newState + const transform = combineTransactionSteps( + oldState.doc, + transactions as Transaction[], + ) + const changes = getChangedRanges(transform) + tr.setMeta('addToHistory', false) - newState.doc.descendants((node, pos) => { - if (types.includes(node.type.name)) { + changes.forEach(({ newRange }) => { + const nodes = findChildrenInRange(newState.doc, newRange, (node) => + types.includes(node.type.name), + ) + + nodes.forEach(({ node, pos }) => { if (node.attrs.dir !== null && node.textContent.length > 0) { return } @@ -74,7 +89,7 @@ function TextDirectionPlugin({ types }: { types: string[] }) { tr.addStoredMark(mark) } modified = true - } + }) }) return modified ? tr : null From bfb19fdc6927c51c22cebfe1a27353fc4986611b Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 29 Jul 2025 10:52:39 +0200 Subject: [PATCH 2/3] fix(Keymap): Add Backspace shortcut to undo input rules The upstream Tiptap Keymap extension that we don't use (anymore) also provides this. It allows to undo the automatic changes from input rules directly after they got applied by hitting . Unfortunately this doesn't work for nodes that get changed by the TextDirection extension due to a bug, but there's still plenty of input rules where it works, so let's bring it in. Signed-off-by: Jonas --- src/extensions/Keymap.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extensions/Keymap.js b/src/extensions/Keymap.js index bf3a4261deb..14ba6b49495 100644 --- a/src/extensions/Keymap.js +++ b/src/extensions/Keymap.js @@ -20,6 +20,11 @@ const Keymap = Extension.create({ emit('text:keyboard:outline') return true }, + /** + * + * Allows to undo input rules after they got automatically applied + */ + Backspace: () => this.editor.commands.undoInputRule(), } }, From 3b81a75c952d8072b955c9ea7b339aa1deaa2547 Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 29 Jul 2025 12:13:41 +0200 Subject: [PATCH 3/3] fix(TextDirection): Regard table cell, details summary and callouts Fixes styling for details and callouts with RTL script. Signed-off-by: Jonas --- src/extensions/RichText.js | 8 +++-- src/nodes/Callout.vue | 6 +++- src/nodes/DetailsView.vue | 2 +- src/nodes/Table/TableCellView.vue | 5 ++- src/nodes/Table/TableHeaderView.vue | 9 ++++- .../fixtures/tables/handbook/handbook.html | 4 +-- .../tables/handbook/handbook.out.html | 26 +++++++------- src/tests/nodes/Table.spec.js | 34 ++++++++++++------- 8 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/extensions/RichText.js b/src/extensions/RichText.js index c0d19c7be3e..3d6ba2b3880 100644 --- a/src/extensions/RichText.js +++ b/src/extensions/RichText.js @@ -121,11 +121,15 @@ export default Extension.create({ TrailingNode, TextDirection.configure({ types: [ + 'blockquote', + 'callout', + 'detailsSummary', 'heading', - 'paragraph', 'listItem', + 'paragraph', + 'tableCell', + 'tableHeader', 'taskItem', - 'blockquote', ], }), ] diff --git a/src/nodes/Callout.vue b/src/nodes/Callout.vue index 6672d679c05..f066768fcd2 100644 --- a/src/nodes/Callout.vue +++ b/src/nodes/Callout.vue @@ -7,6 +7,7 @@ data-text-el="callout" class="callout" :class="`callout--${type}`" + :dir="dir" as="div"> @@ -42,7 +43,10 @@ export default { return ICONS_MAP[this.type] || Info }, type() { - return this.node?.attrs?.type || 'info' + return this.node.attrs.type || 'info' + }, + dir() { + return this.node.attrs.dir || '' }, }, } diff --git a/src/nodes/DetailsView.vue b/src/nodes/DetailsView.vue index 2b02fffde0b..7b157322f44 100644 --- a/src/nodes/DetailsView.vue +++ b/src/nodes/DetailsView.vue @@ -86,7 +86,7 @@ div.details { .details-container { width: 100%; - margin-right: 12px; + margin-inline-end: 12px; } :deep(summary) { diff --git a/src/nodes/Table/TableCellView.vue b/src/nodes/Table/TableCellView.vue index 125e1d38149..ba9cc3c285e 100644 --- a/src/nodes/Table/TableCellView.vue +++ b/src/nodes/Table/TableCellView.vue @@ -4,7 +4,7 @@ -->