mirror of
https://github.com/orelby/obsidian-rtl-math-text-plugin.git
synced 2026-07-22 05:48:01 +00:00
Support configurable RTL & LTR command names
This commit is contained in:
parent
a55177fc4d
commit
7c3df15ebc
3 changed files with 158 additions and 52 deletions
204
main.ts
204
main.ts
|
|
@ -1,4 +1,7 @@
|
|||
import { Plugin, loadMathJax, finishRenderMath } from 'obsidian';
|
||||
import {
|
||||
App, Plugin, PluginSettingTab, Setting,
|
||||
debounce, finishRenderMath, loadMathJax
|
||||
} from 'obsidian';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -6,27 +9,80 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
interface BidiCommands {
|
||||
RTL: string[];
|
||||
LTR: string[];
|
||||
}
|
||||
|
||||
interface Settings {
|
||||
cmds: BidiCommands;
|
||||
}
|
||||
|
||||
const DEFAULT_CMDS: BidiCommands = {
|
||||
RTL: ['R'],
|
||||
LTR: ['L'],
|
||||
};
|
||||
|
||||
export default class RtlMathTextPlugin extends Plugin {
|
||||
settings: Settings;
|
||||
private mathjaxPatcher?: MathJaxBidiCommandPatcher;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new RtlMathTextSettingsTab(this.app, this));
|
||||
await this.patchMathJax();
|
||||
}
|
||||
|
||||
private async patchMathJax() {
|
||||
this.mathjaxPatcher?.destroy();
|
||||
this.mathjaxPatcher = new MathJaxBidiCommandPatcher(this.settings.cmds);
|
||||
await this.mathjaxPatcher.init();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
const data = await this.loadData();
|
||||
this.settings = {
|
||||
cmds: Object.assign({}, DEFAULT_CMDS, data?.cmds),
|
||||
};
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
await this.patchMathJax();
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.mathjaxPatcher?.destroy();
|
||||
this.mathjaxPatcher = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MathJaxBidiCommandPatcher {
|
||||
private cmds: BidiCommands;
|
||||
private mathjaxStyleObserver?: MutationObserver;
|
||||
private styleEl?: HTMLStyleElement;
|
||||
|
||||
async onload() {
|
||||
constructor(cmds: BidiCommands) {
|
||||
this.cmds = cmds;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await loadMathJax();
|
||||
|
||||
// Extend MathJax macros
|
||||
window.MathJax.tex2chtml(`
|
||||
\\def\\R#1{\\class{mjx-rtl}{#1}}
|
||||
\\def\\L#1{\\class{mjx-ltr}{#1}}
|
||||
\\def\\RLE#1{\\class{mjx-rtl}{#1}}
|
||||
\\def\\LRE#1{\\class{mjx-ltr}{#1}}
|
||||
`,
|
||||
{ display: false }
|
||||
);
|
||||
|
||||
// Re-typeset existing math
|
||||
if (window.MathJax.typesetPromise) {
|
||||
await window.MathJax.typesetPromise();
|
||||
const defs = [];
|
||||
for (const dir in this.cmds) {
|
||||
for (const cmd of this.cmds[dir as keyof BidiCommands]) {
|
||||
defs.push(`\\def\\${cmd}#1{\\class{mjx-${dir.toLowerCase()}}{#1}}`);
|
||||
}
|
||||
}
|
||||
window.MathJax.tex2chtml(defs.join('\n'), { display: false });
|
||||
|
||||
// // Re-typeset existing math
|
||||
// if (window.MathJax.typesetPromise) {
|
||||
// await window.MathJax.typesetPromise();
|
||||
// }
|
||||
|
||||
// Patch styles after initial MathJax stylesheet flush
|
||||
await finishRenderMath();
|
||||
|
|
@ -34,55 +90,105 @@ export default class RtlMathTextPlugin extends Plugin {
|
|||
|
||||
// Patch styles on MathJax stylesheet change
|
||||
this.mathjaxStyleObserver = new MutationObserver(() => this.patchStyles());
|
||||
this.mathjaxStyleObserver.observe(getMathJaxStyleElement(), {
|
||||
this.mathjaxStyleObserver.observe(this.getMathJaxStyleElement(), {
|
||||
attributes: true,
|
||||
});
|
||||
}
|
||||
|
||||
onunload() {
|
||||
destroy() {
|
||||
this.mathjaxStyleObserver?.disconnect();
|
||||
this.styleEl?.remove();
|
||||
}
|
||||
|
||||
patchStyles() {
|
||||
const rules = convertMathJaxStylesToLogicalProperties();
|
||||
private patchStyles() {
|
||||
const rules = this.convertMathJaxStylesToLogicalProperties();
|
||||
this.styleEl?.remove();
|
||||
this.styleEl = document.head.createEl('style', {
|
||||
text: rules.join('\n')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getMathJaxStyleElement() {
|
||||
return document.getElementById('MJX-CHTML-styles') as HTMLStyleElement;
|
||||
}
|
||||
|
||||
function convertMathJaxStylesToLogicalProperties(): string[] {
|
||||
const rules: string[] = [];
|
||||
const styleEl = getMathJaxStyleElement();
|
||||
|
||||
if (!styleEl || !styleEl.sheet) return rules;
|
||||
|
||||
for (const rule of Array.from(styleEl.sheet.cssRules)) {
|
||||
if (!(rule instanceof CSSStyleRule)) continue;
|
||||
|
||||
const styleLines: string[] = [];
|
||||
|
||||
const paddingLeft = rule.style.getPropertyValue('padding-left');
|
||||
const paddingRight = rule.style.getPropertyValue('padding-right');
|
||||
const marginLeft = rule.style.getPropertyValue('margin-left');
|
||||
const marginRight = rule.style.getPropertyValue('margin-right');
|
||||
|
||||
if (paddingLeft) styleLines.push(`padding-inline-start: ${paddingLeft};`);
|
||||
if (paddingRight) styleLines.push(`padding-inline-end: ${paddingRight};`);
|
||||
if (marginLeft) styleLines.push(`margin-inline-start: ${marginLeft};`);
|
||||
if (marginRight) styleLines.push(`margin-inline-end: ${marginRight};`);
|
||||
|
||||
if (styleLines.length > 0) {
|
||||
const newRule = `${rule.selectorText} {\n ${styleLines.join('\n ')}\n}`;
|
||||
rules.push(newRule);
|
||||
}
|
||||
private getMathJaxStyleElement() {
|
||||
return document.getElementById('MJX-CHTML-styles') as HTMLStyleElement;
|
||||
}
|
||||
|
||||
return rules;
|
||||
private convertMathJaxStylesToLogicalProperties(): string[] {
|
||||
const rules: string[] = [];
|
||||
const styleEl = this.getMathJaxStyleElement();
|
||||
|
||||
if (!styleEl || !styleEl.sheet) return rules;
|
||||
|
||||
for (const rule of Array.from(styleEl.sheet.cssRules)) {
|
||||
if (!(rule instanceof CSSStyleRule)) continue;
|
||||
|
||||
const styleLines: string[] = [];
|
||||
|
||||
const paddingLeft = rule.style.getPropertyValue('padding-left');
|
||||
const paddingRight = rule.style.getPropertyValue('padding-right');
|
||||
const marginLeft = rule.style.getPropertyValue('margin-left');
|
||||
const marginRight = rule.style.getPropertyValue('margin-right');
|
||||
|
||||
if (paddingLeft) styleLines.push(`padding-inline-start: ${paddingLeft};`);
|
||||
if (paddingRight) styleLines.push(`padding-inline-end: ${paddingRight};`);
|
||||
if (marginLeft) styleLines.push(`margin-inline-start: ${marginLeft};`);
|
||||
if (marginRight) styleLines.push(`margin-inline-end: ${marginRight};`);
|
||||
|
||||
if (styleLines.length > 0) {
|
||||
const newRule = `${rule.selectorText} {\n ${styleLines.join('\n ')}\n}`;
|
||||
rules.push(newRule);
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
|
||||
class RtlMathTextSettingsTab extends PluginSettingTab {
|
||||
plugin: RtlMathTextPlugin;
|
||||
|
||||
constructor(app: App, plugin: RtlMathTextPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('p', {
|
||||
text: 'Changes may require restarting Obsidian to take full effect.',
|
||||
cls: 'setting-item-description'
|
||||
});
|
||||
|
||||
const cmds = this.plugin.settings.cmds;
|
||||
for (const dir in cmds) {
|
||||
new Setting(containerEl)
|
||||
.setName(`${dir} commands`)
|
||||
.setDesc(`Comma-separated list of commands for ${dir}`)
|
||||
.addText(text =>
|
||||
text
|
||||
.setPlaceholder(dir === 'RTL' ? 'e.g. R, RLE' : 'e.g. L, LRE')
|
||||
.setValue(cmds[dir as keyof BidiCommands].join(', '))
|
||||
.onChange(debounce(
|
||||
async (val) => {
|
||||
cmds[dir as keyof BidiCommands] = val
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
await this.plugin.saveSettings();
|
||||
}, 2000, true))
|
||||
);
|
||||
}
|
||||
|
||||
new Setting(containerEl)
|
||||
.addButton(btn =>
|
||||
btn
|
||||
.setButtonText('Restore Defaults')
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.cmds = { ...DEFAULT_CMDS };
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"name": "RTL Math Text",
|
||||
"version": "0.0.1",
|
||||
"minAppVersion": "0.16.0",
|
||||
"description": "Mix right-to-left and left-to-right text in math expressions with \\R{} (RTL) and \\L{} (LTR).",
|
||||
"description": "Mix right-to-left and left-to-right text in math expressions using configurable commands (default: \\R{} for RTL and \\L{} for LTR).",
|
||||
"author": "orelby",
|
||||
"authorUrl": "",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.mjx-rtl{
|
||||
.mjx-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.mjx-ltr,
|
||||
.mjx-rtl :where(:not(.mjx-rtl, mjx-mtext, mjx-c)) {
|
||||
.mjx-rtl :where(:not(.mjx-rtl, mjx-mtext, mjx-c, mjx-utext)) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue