65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import { Extension } from '@tiptap/core'
|
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
|
import type { EditorState } from '@tiptap/pm/state'
|
|
import { Decoration, DecorationSet } from '@tiptap/pm/view'
|
|
import { LATIN_CLASS, RTL_CLASS, findScriptRuns } from '../../utils/bidiText'
|
|
|
|
/** نام مارکهای قدیمی که خودشان اندازه/جهت متن را تعیین میکنند */
|
|
const LEGACY_MARKS = new Set(['englishText', 'smallSize'])
|
|
|
|
type BidiState = { dir: 'rtl' | 'ltr' }
|
|
|
|
/** کلید پلاگین — جهت متن با متای همین کلید بهروز میشود */
|
|
export const bidiPluginKey = new PluginKey<BidiState>('bidiTypography')
|
|
|
|
/**
|
|
* تزئین دوزبانه متن ویرایشگر — اجرای لاتین (و در متن انگلیسی، اجرای فارسی) با یک
|
|
* span جهتدار نشانهگذاری میشود تا ویرایشگر هم دقیقاً همان اندازه «۲ پیکسل کوچکتر»
|
|
* خروجی سایت را نشان دهد. تزئینها سند را تغییر نمیدهند و ذخیره نمیشوند.
|
|
*/
|
|
export const BidiTypography = Extension.create({
|
|
name: 'bidiTypography',
|
|
|
|
addProseMirrorPlugins() {
|
|
return [
|
|
new Plugin<BidiState>({
|
|
key: bidiPluginKey,
|
|
state: {
|
|
init: () => ({ dir: 'rtl' }),
|
|
apply: (tr, value) => {
|
|
const dir = tr.getMeta(bidiPluginKey) as BidiState['dir'] | undefined
|
|
return dir && dir !== value.dir ? { dir } : value
|
|
},
|
|
},
|
|
props: {
|
|
decorations: (state) =>
|
|
buildDecorations(state, bidiPluginKey.getState(state)?.dir ?? 'rtl'),
|
|
},
|
|
}),
|
|
]
|
|
},
|
|
})
|
|
|
|
function buildDecorations(state: EditorState, dir: 'rtl' | 'ltr'): DecorationSet {
|
|
const className = dir === 'rtl' ? LATIN_CLASS : RTL_CLASS
|
|
const runDir = dir === 'rtl' ? 'ltr' : 'rtl'
|
|
const decorations: Decoration[] = []
|
|
const { doc, schema } = state
|
|
|
|
doc.descendants((node, pos, parent) => {
|
|
if (node.type.name === 'codeBlock' || parent?.type.name === 'codeBlock') return false
|
|
if (!node.isText || !node.text) return
|
|
if (node.marks.some((mark) => mark.type === schema.marks.code || LEGACY_MARKS.has(mark.type.name))) {
|
|
return
|
|
}
|
|
|
|
for (const run of findScriptRuns(node.text, dir)) {
|
|
decorations.push(
|
|
Decoration.inline(pos + run.from, pos + run.to, { class: className, dir: runDir }),
|
|
)
|
|
}
|
|
})
|
|
|
|
return DecorationSet.create(doc, decorations)
|
|
}
|