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() }