fix(admin): resolve Tiptap editor bugs, dynamic RTL/LTR layout, sticky TOC, and equalize Bidi typography
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* دوزبانهسازی خودکار متن مقاله — قاعده: متن لاتین همیشه «۲ پیکسل کوچکتر» از متن
|
||||
* فارسیِ همان بلوک است (تیتر۱ ۱۸→۱۶، تیتر۲ ۱۶→۱۴، تیتر۳ و پاراگراف ۱۴→۱۲) و در
|
||||
* مقاله انگلیسی برعکس، واژههای فارسی ۲ پیکسل بزرگتر میشوند.
|
||||
*
|
||||
* اجرای غیرغالب متن با یک span جهتدار جدا میشود تا نه جهت متن بههم بریزد
|
||||
* (unicode-bidi: isolate) و نه خط پایه جابهجا شود؛ اندازه در CSS با
|
||||
* calc(1em - 2px) نسبت به همان بلوک حساب میشود.
|
||||
*/
|
||||
|
||||
/** اجرای متن لاتین — حروف/ارقام لاتین بههمراه فاصله و نشانههای داخلی */
|
||||
const LATIN_PATTERN =
|
||||
"[A-Za-z0-9\\u00c0-\\u024f]+(?:[ \\t.,:;'\"()&/+%\\u2013\\u2014-]+[A-Za-z0-9\\u00c0-\\u024f]+)*"
|
||||
|
||||
/** اجرای متن فارسی/عربی — با نیمفاصله و ارقام فارسی */
|
||||
const RTL_PATTERN = '[\\u0590-\\u08ff\\u200c]+(?:[ \\t\\u200c]+[\\u0590-\\u08ff\\u200c]+)*'
|
||||
|
||||
/** کلاس span متن لاتین و متن فارسی — همینها در CSS اندازه ۲ پیکسل تفاضلی میگیرند */
|
||||
export const LATIN_CLASS = 'latin'
|
||||
export const RTL_CLASS = 'persian'
|
||||
|
||||
/** عناصری که متنشان دستنخورده میماند (کد، مارکهای قدیمی و اجرای بستهبندیشده) */
|
||||
const SKIP_SELECTOR = 'code, pre, kbd, samp, script, style, .english-text, [data-small], .latin, .persian'
|
||||
|
||||
/** همه اجراهای غیرغالب یک متن — با بازه نسبت به ابتدای همان متن */
|
||||
export function findScriptRuns(text: string, dir: 'rtl' | 'ltr'): { from: number; to: number }[] {
|
||||
const scan = new RegExp(dir === 'rtl' ? LATIN_PATTERN : RTL_PATTERN, 'g')
|
||||
const runs: { from: number; to: number }[] = []
|
||||
|
||||
for (const match of text.matchAll(scan)) {
|
||||
const from = match.index ?? 0
|
||||
runs.push({ from, to: from + match[0].length })
|
||||
}
|
||||
|
||||
return runs
|
||||
}
|
||||
|
||||
/**
|
||||
* بستهبندی اجرای غیرغالب همه گرههای متنی داخل یک ریشه HTML.
|
||||
* در مقاله فارسی اجرای لاتین و در مقاله انگلیسی اجرای فارسی جدا میشود.
|
||||
*/
|
||||
export function wrapScriptRuns(root: HTMLElement, dir: 'rtl' | 'ltr'): void {
|
||||
const document = root.ownerDocument
|
||||
const className = dir === 'rtl' ? LATIN_CLASS : RTL_CLASS
|
||||
const runDir = dir === 'rtl' ? 'ltr' : 'rtl'
|
||||
|
||||
const texts: Text[] = []
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
||||
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||
const text = node as Text
|
||||
if (!text.parentElement || text.parentElement.closest(SKIP_SELECTOR)) continue
|
||||
if (!text.data.trim()) continue
|
||||
if (findScriptRuns(text.data, dir).length) texts.push(text)
|
||||
}
|
||||
|
||||
for (const text of texts) {
|
||||
const value = text.data
|
||||
const fragment = document.createDocumentFragment()
|
||||
let cursor = 0
|
||||
|
||||
for (const run of findScriptRuns(value, dir)) {
|
||||
if (run.from > cursor) fragment.append(value.slice(cursor, run.from))
|
||||
const span = document.createElement('span')
|
||||
span.className = className
|
||||
span.setAttribute('dir', runDir)
|
||||
span.textContent = value.slice(run.from, run.to)
|
||||
fragment.append(span)
|
||||
cursor = run.to
|
||||
}
|
||||
|
||||
if (cursor < value.length) fragment.append(value.slice(cursor))
|
||||
text.replaceWith(fragment)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user