Files
Momento/memento-note/lib/editor/apply-clip-rtl-direction.ts
Antigravity e881004c77
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m7s
CI / Deploy production (on server) (push) Has been skipped
feat(insights): fix DBSCAN, Persian embeddings crash, D3 physics layouts, and D3 node not found runtime error
2026-05-24 18:57:33 +00:00

73 lines
2.2 KiB
TypeScript

import type { Editor } from '@tiptap/core'
import { detectTextDirection, inferLangFromUrl } from '@/lib/clip/rtl-content'
const RTL_BLOCK_TYPES = new Set([
'clipArticle',
'bulletList',
'orderedList',
'listItem',
'heading',
'paragraph',
'blockquote',
])
function nodeShouldBeRtl(
node: { type: { name: string }; textContent: string; attrs: { dir?: string | null } },
urlIsRtl: boolean,
): boolean {
if (node.attrs.dir === 'rtl') return false
if (urlIsRtl) return true
const text = node.textContent || ''
if (!text.trim()) return false
return detectTextDirection(text) === 'rtl'
}
/**
* Applique dir="rtl" explicitement sur les nœuds TipTap (titres, listes, paragraphes).
* Basé sur la doc TipTap setTextDirection — évite textDirection:'auto' (bug listes #7338).
* @see https://tiptap.dev/docs/editor/api/commands/nodes-and-marks/set-text-direction
* @see https://github.com/ueberdosis/tiptap/issues/7338
*/
export function applyClipRtlDirection(
editor: Editor,
options?: { sourceUrl?: string | null },
): boolean {
if (!editor || editor.isDestroyed) return false
const urlIsRtl = Boolean(options?.sourceUrl && inferLangFromUrl(options.sourceUrl))
const ranges: Array<{ from: number; to: number }> = []
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'clipArticle') {
ranges.push({ from: pos, to: pos + node.nodeSize })
}
})
if (ranges.length === 0) {
editor.state.doc.descendants((node, pos) => {
if (node.isText || !RTL_BLOCK_TYPES.has(node.type.name)) return
if (!nodeShouldBeRtl(node, urlIsRtl)) return
ranges.push({ from: pos, to: pos + node.nodeSize })
})
}
if (ranges.length === 0) return false
return editor
.chain()
.command(({ tr, state, dispatch }) => {
let changed = false
for (const { from, to } of ranges) {
state.doc.nodesBetween(from, to, (node, pos) => {
if (node.isText || node.attrs.dir === 'rtl') return
if (!RTL_BLOCK_TYPES.has(node.type.name)) return
tr.setNodeMarkup(pos, undefined, { ...node.attrs, dir: 'rtl' })
changed = true
})
}
if (dispatch && changed) dispatch(tr)
return changed
})
.run()
}