From 56a003ba146ea0cfb3b92479f464462594d5b4f4 Mon Sep 17 00:00:00 2001 From: Michael Uray Date: Sun, 18 Oct 2026 05:27:54 +0000 Subject: [PATCH] fix(text-editor): render paragraphs and headings with dir="auto" for RTL text Paragraphs and headings in the editor and in the read-only markup renderers had no direction attribute, so every block inherited the LTR page direction. Hebrew or Arabic lines were left-aligned, and lines that mixed RTL text with English words had their punctuation and word order resolved against an LTR base, which scrambled them. The editor kit now configures the paragraph and heading nodes with HTMLAttributes dir="auto", and the markup renderers (NodeContent, LiteNodeContent) set dir="auto" on the same blocks. Each block then takes its base direction from its first strong character. The attribute only exists in the rendered DOM. Stored markup and server-side HTML conversion are unchanged. Table header cells use text-align: start instead of left, so RTL headers align to the right. Fixes #8767 Signed-off-by: Michael Uray --- .../src/components/markup/NodeContent.svelte | 4 ++-- .../src/components/markup/lite/LiteNodeContent.svelte | 9 ++++++++- packages/theme/styles/prose.scss | 2 +- plugins/text-editor-resources/src/kits/editor-kit.ts | 7 +++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/presentation/src/components/markup/NodeContent.svelte b/packages/presentation/src/components/markup/NodeContent.svelte index fcce7a7d297..d1613fa6959 100644 --- a/packages/presentation/src/components/markup/NodeContent.svelte +++ b/packages/presentation/src/components/markup/NodeContent.svelte @@ -112,7 +112,7 @@ {/if} {:else if node.type === MarkupNodeType.paragraph} -

+

{#if nodes.length > 0} {#each nodes as node} @@ -132,7 +132,7 @@ {:else if node.type === MarkupNodeType.heading} {@const level = toNumber(node.attrs?.level) ?? 1} {@const element = `h${level}`} - + {#if nodes.length > 0} {#each nodes as node} diff --git a/packages/presentation/src/components/markup/lite/LiteNodeContent.svelte b/packages/presentation/src/components/markup/lite/LiteNodeContent.svelte index f89bda14540..a783a2e3909 100644 --- a/packages/presentation/src/components/markup/lite/LiteNodeContent.svelte +++ b/packages/presentation/src/components/markup/lite/LiteNodeContent.svelte @@ -81,7 +81,14 @@ {/each} {/if} {:else if node.type === MarkupNodeType.paragraph} -

+

{:else if node.type === MarkupNodeType.blockquote} diff --git a/packages/theme/styles/prose.scss b/packages/theme/styles/prose.scss index 68126e92c3e..762e57bc3ed 100644 --- a/packages/theme/styles/prose.scss +++ b/packages/theme/styles/prose.scss @@ -84,7 +84,7 @@ table.proseTable { } th { - text-align: left; + text-align: start; background-color: var(--text-editor-table-header-color); } diff --git a/plugins/text-editor-resources/src/kits/editor-kit.ts b/plugins/text-editor-resources/src/kits/editor-kit.ts index 82669655cb0..305033ea522 100644 --- a/plugins/text-editor-resources/src/kits/editor-kit.ts +++ b/plugins/text-editor-resources/src/kits/editor-kit.ts @@ -24,7 +24,9 @@ import { extensionKit, mergeKitOptions, TextColorStylingKit, + Heading, Highlight, + Paragraph, Subscript, Superscript } from '@hcengineering/text' @@ -89,6 +91,11 @@ const StaticEditorKit = extensionKit( ({ ...CommonKitFactory(e), + // Let each text block take its direction from its first strong character, + // so RTL and mixed RTL/LTR text is rendered correctly (client-side only, stored content is unchanged) + paragraph: e(Paragraph, { HTMLAttributes: { dir: 'auto' } }), + heading: e(Heading, { HTMLAttributes: { dir: 'auto' } }), + shortcuts: e(subKits.shortcuts, context), // needs to be loaded as early as possible to override the default behavior // ===========================================================================================