diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index e20648b4..ad540a07 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -39,6 +39,11 @@ import { useFilenameAutolinkGuard } from './useFilenameAutolinkGuard' import './Editor.css' import './EditorTheme.css' +const RICH_EDITOR_BIDI_DOM_ATTRIBUTES = { + blockContent: { dir: 'auto' }, + inlineContent: { dir: 'auto' }, +} + interface Tab { entry: VaultEntry content: string @@ -206,6 +211,7 @@ function useEditorSetup({ const editor = useCreateBlockNote({ schema, + domAttributes: RICH_EDITOR_BIDI_DOM_ATTRIBUTES, uploadFile: (file: File) => uploadImageFile(file, vaultPathRef.current), _tiptapOptions: { injectNonce: RUNTIME_STYLE_NONCE }, extensions: [ diff --git a/src/components/EditorTheme.css b/src/components/EditorTheme.css index cc497c57..3f6c5db9 100644 --- a/src/components/EditorTheme.css +++ b/src/components/EditorTheme.css @@ -21,13 +21,15 @@ .editor-content-width--wide .editor__blocknote-container .bn-editor { max-width: none; - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; + margin-inline: 0; + padding-inline: 0; } /* Let each rich-text block resolve its own base direction for mixed LTR/RTL notes. */ +.editor__blocknote-container .bn-block-content { + text-align: start; +} + .editor__blocknote-container .bn-inline-content { unicode-bidi: plaintext; text-align: start; @@ -65,7 +67,7 @@ /* The side menu is positioned via transform — we shift it further left */ } .editor__blocknote-container .bn-block-content { - margin-left: 8px; + margin-inline-start: 8px; } .editor__blocknote-container .bn-editor ::selection { @@ -154,7 +156,7 @@ /* BlockNote renders bullet as ::before on [data-content-type="bulletListItem"] with content: "•", display: flex, width: 24px */ .editor__blocknote-container [data-content-type="bulletListItem"] { - padding-left: var(--lists-padding-left); + padding-inline-start: var(--lists-padding-left); margin-bottom: var(--lists-item-spacing); gap: var(--lists-bullet-gap) !important; } @@ -172,17 +174,17 @@ /* Nested list indentation */ .editor__blocknote-container .bn-block-group .bn-block-group { - margin-left: var(--lists-indent-size); + margin-inline-start: var(--lists-indent-size); } /* Hide the vertical nesting lines — cleaner without them */ .editor__blocknote-container .bn-block-group .bn-block-group .bn-block-outer::before { - border-left: none !important; + border-inline-start: none !important; } /* --- Numbered lists --- */ .editor__blocknote-container [data-content-type="numberedListItem"] { - padding-left: var(--lists-padding-left); + padding-inline-start: var(--lists-padding-left); margin-bottom: var(--lists-item-spacing); gap: var(--lists-bullet-gap) !important; } @@ -537,8 +539,8 @@ /* --- Blockquote --- */ .editor__blocknote-container [data-content-type="blockquote"], .editor__blocknote-container blockquote { - border-left: var(--blockquote-border-left-width) solid var(--blockquote-border-left-color); - padding-left: var(--blockquote-padding-left); + border-inline-start: var(--blockquote-border-left-width) solid var(--blockquote-border-left-color); + padding-inline-start: var(--blockquote-padding-left); margin-top: var(--blockquote-margin-vertical); margin-bottom: var(--blockquote-margin-vertical); color: var(--blockquote-color); @@ -559,6 +561,7 @@ .editor__blocknote-container [data-content-type="table"] td { padding: var(--table-cell-padding-vertical) var(--table-cell-padding-horizontal); border-color: var(--table-border-color); + text-align: start; } /* --- Horizontal rule --- */ diff --git a/tests/smoke/rtl-editor-direction.spec.ts b/tests/smoke/rtl-editor-direction.spec.ts index 1c771176..74af4791 100644 --- a/tests/smoke/rtl-editor-direction.spec.ts +++ b/tests/smoke/rtl-editor-direction.spec.ts @@ -7,6 +7,9 @@ import { executeCommand, openCommandPalette } from './helpers' const RTL_TITLE = 'RTL Mixed Direction' const RTL_PARAGRAPH = 'مرحبا بالعالم' const MIXED_PARAGRAPH = 'English then مرحبا' +const RTL_LIST_ITEM = 'רשימת בדיקה' +const RTL_QUOTE = 'ציטוט חשוב' +const RTL_TABLE_CELL = 'תא בטבלה' let tempVaultDir: string @@ -26,6 +29,14 @@ test.beforeEach(async ({ page }, testInfo) => { '', MIXED_PARAGRAPH, '', + `- ${RTL_LIST_ITEM}`, + '', + `> ${RTL_QUOTE}`, + '', + '| Field | Value |', + '| --- | --- |', + `| ${RTL_TABLE_CELL} | 42 |`, + '', ].join('\n'), ) await openFixtureVault(page, tempVaultDir) @@ -62,3 +73,31 @@ test('rich and raw editors resolve text direction per line for Arabic and mixed await expect(rawLines.filter({ hasText: RTL_PARAGRAPH })).toHaveCSS('unicode-bidi', 'plaintext') await expect(rawLines.filter({ hasText: RTL_PARAGRAPH })).toHaveCSS('text-align', 'start') }) + +test('rich editor uses logical spacing for RTL block elements', async ({ page }) => { + await openNote(page, RTL_TITLE) + + const rtlListItem = page.locator('[data-content-type="bulletListItem"]', { hasText: RTL_LIST_ITEM }).first() + await expect(rtlListItem).toBeVisible({ timeout: 5_000 }) + await expect(rtlListItem).toHaveAttribute('dir', 'auto') + await expect(rtlListItem).toHaveCSS('direction', 'rtl') + await expect.poll(async () => rtlListItem.evaluate((element) => { + const style = getComputedStyle(element) + return Number.parseFloat(style.paddingRight) > Number.parseFloat(style.paddingLeft) + })).toBe(true) + + const rtlQuote = page.locator('.bn-block-content', { hasText: RTL_QUOTE }).first() + await expect(rtlQuote).toBeVisible({ timeout: 5_000 }) + await expect(rtlQuote).toHaveAttribute('dir', 'auto') + await expect(rtlQuote).toHaveCSS('direction', 'rtl') + const rtlQuoteElement = page.locator('blockquote', { hasText: RTL_QUOTE }).first() + await expect(rtlQuoteElement).toBeVisible({ timeout: 5_000 }) + await expect.poll(async () => rtlQuoteElement.evaluate((element) => { + const style = getComputedStyle(element) + return Number.parseFloat(style.borderRightWidth) > Number.parseFloat(style.borderLeftWidth) + })).toBe(true) + + const rtlTableCell = page.locator('td', { hasText: RTL_TABLE_CELL }).first() + await expect(rtlTableCell).toBeVisible({ timeout: 5_000 }) + await expect(rtlTableCell).toHaveCSS('text-align', 'start') +})