Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions EditorPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EditorState, RangeSetBuilder } from "@codemirror/state";
import { syntaxTree } from "@codemirror/language";
import RtlPlugin from './main';
import { editorInfoField, MarkdownView } from 'obsidian';
import { Direction } from './direction.util';
import { Direction, hasRtlChar } from './direction.util';

type Region = {from: number; to: number;};
type DecorationRegion = Region & {dec: Decoration};
Expand Down Expand Up @@ -110,11 +110,6 @@ export function getEditorPlugin(rtlPlugin: RtlPlugin) {
if (!viewport)
return builder.finish();

let decoration = this.emptyDirDec;
if (this.direction != 'auto') {
decoration = this.direction === 'ltr' ? this.ltrDec : this.rtlDec;
}

let pos = viewport.from,
preventRTLFrontmatter = this.rtlPlugin.settings.preventRTLFrontmatter;
// Move the pos after the frontmatter if the pos touches it.
Expand All @@ -126,6 +121,14 @@ export function getEditorPlugin(rtlPlugin: RtlPlugin) {

while (pos <= viewport.to) {
const line = view.state.doc.lineAt(pos);
let decoration: Decoration;
if (this.direction != 'auto') {
decoration = this.direction === 'ltr' ? this.ltrDec : this.rtlDec;
} else if (this.rtlPlugin.settings.autoRTLForMixedContent && hasRtlChar(line.text)) {
decoration = this.rtlDec;
} else {
decoration = this.emptyDirDec;
}
builder.add(line.from, line.from, decoration);
pos = line.to + 1;
}
Expand Down
15 changes: 14 additions & 1 deletion MarkdownPostProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MarkdownPostProcessorContext } from 'obsidian';
import { Direction } from './direction.util';
import { Direction, hasRtlChar } from './direction.util';

// Try to detect if the postprocessor was asked to render an export, which seems to be the only case on which
// the post processor is called with a top-level Markdown Preview View div.
Expand All @@ -12,6 +12,15 @@ function detectExport(el: HTMLElement, ctx: MarkdownPostProcessorContext, setPre

type SetPreviewDirection = (path: string, markdownPreviewElement: HTMLDivElement) => void;

function applyMixedContentRTL(el: HTMLElement) {
const elements = el.querySelectorAll('p, li, h1, h2, h3, h4, h5, h6, td, th, blockquote');
for (const element of elements) {
if (hasRtlChar(element.textContent || '')) {
(element as HTMLElement).setAttribute('dir', 'rtl');
}
}
}

/*
* This Markdown post-processor handles the Reading view and other rendered components of notes.
* It detects the direction for each node individually and adds corresponding CSS classes that are
Expand All @@ -20,8 +29,12 @@ type SetPreviewDirection = (path: string, markdownPreviewElement: HTMLDivElement
export const autoDirectionPostProcessor = (
el: HTMLElement,
ctx: MarkdownPostProcessorContext,
autoRTLForMixedContent: boolean,
setPreviewDirection: SetPreviewDirection,
) => {
detectExport(el, ctx, setPreviewDirection);
if (autoRTLForMixedContent) {
applyMixedContentRTL(el);
}
}

6 changes: 6 additions & 0 deletions direction.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ export const LTR_CLASS = 'is-ltr';
export const RTL_CLASS = 'is-rtl';
export const AUTO_CLASS = 'is-auto';

export const RTL_CHAR_REGEX = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/;

export function hasRtlChar(text: string): boolean {
return RTL_CHAR_REGEX.test(text);
}

2 changes: 1 addition & 1 deletion main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class RtlPlugin extends Plugin {
// extensions and can override the direction that they set.
this.registerEditorExtension(Prec.lowest(this.editorPlugin));
this.registerMarkdownPostProcessor((el, ctx) => {
autoDirectionPostProcessor(el, ctx, (path, markdownPreviewElement) => this.setPreviewDirectionByFileSettings(path, markdownPreviewElement));
autoDirectionPostProcessor(el, ctx, this.settings.autoRTLForMixedContent, (path, markdownPreviewElement) => this.setPreviewDirectionByFileSettings(path, markdownPreviewElement));
});

this.addSettingTab(new RtlSettingsTab(this.app, this));
Expand Down
14 changes: 13 additions & 1 deletion settingsTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type Settings = {
setYamlDirection: boolean;
preventRTLFrontmatter: boolean; // Frontmatter == Raw YAML
statusBar: boolean;
autoRTLForMixedContent: boolean;
};

export const DEFAULT_SETTINGS: Settings = {
Expand All @@ -19,7 +20,8 @@ export const DEFAULT_SETTINGS: Settings = {
setNoteTitleDirection: true,
setYamlDirection: false,
preventRTLFrontmatter: false,
statusBar: true
statusBar: true,
autoRTLForMixedContent: false
};

export class RtlSettingsTab extends PluginSettingTab {
Expand Down Expand Up @@ -94,6 +96,16 @@ export class RtlSettingsTab extends PluginSettingTab {
this.plugin.adjustDirectionToActiveView();
}));

new Setting(containerEl)
.setName('Auto-RTL for mixed LTR/RTL content')
.setDesc('When in auto mode, lines containing RTL scripts (Arabic, Hebrew, Persian, etc.) will be treated as RTL regardless of their first directional character.')
.addToggle(toggle => toggle.setValue(this.settings.autoRTLForMixedContent ?? false)
.onChange((value) => {
this.settings.autoRTLForMixedContent = value;
this.plugin.saveSettings();
this.plugin.adjustDirectionToActiveView();
}));

new Setting(containerEl)
.setName('Show status bar item')
.setDesc('Show a clickable status bar item showing the current direction.')
Expand Down