Skip to content

Commit 2d5acbd

Browse files
committed
Enable general markdown styling to be prioritized
This will prioritize markdown stylings over the stylings of the plugin. Plugin stylings will essentially be treated as additions.
1 parent 9f1ac00 commit 2d5acbd

2 files changed

Lines changed: 228 additions & 9 deletions

File tree

src/editor-metadata.ts

Lines changed: 208 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ type LineMarkers = {
1818
markers: LineMarker[];
1919
};
2020

21+
type MarkdownStyle = {
22+
italic: boolean;
23+
bold: boolean;
24+
strike: boolean;
25+
highlight: boolean;
26+
};
27+
2128
type FenceState = {
2229
inFence: boolean;
2330
fenceChar: string;
@@ -157,7 +164,6 @@ function buildDecorations(
157164
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber += 1) {
158165
const line = view.state.doc.line(lineNumber);
159166
const {lineInFence, isFenceLine} = updateFenceStateForLine(line.text, fenceState);
160-
const inlineCodeRanges = lineInFence || isFenceLine ? [] : getInlineCodeRanges(line.text);
161167
if (seenLines.has(lineNumber)) {
162168
continue;
163169
}
@@ -168,6 +174,19 @@ function buildDecorations(
168174
continue;
169175
}
170176

177+
const inlineCodeRanges = lineInFence || isFenceLine ? [] : getInlineCodeRanges(line.text);
178+
// Mask marker contents so underscores in keys don't confuse markdown emphasis parsing.
179+
const maskedText = maskMarkerText(line.text, markers);
180+
const emphasisRanges = lineInFence || isFenceLine
181+
? []
182+
: getEmphasisRanges(maskedText, inlineCodeRanges);
183+
const strikeRanges = lineInFence || isFenceLine
184+
? []
185+
: getDelimitedRanges(maskedText, "~~", inlineCodeRanges);
186+
const highlightRanges = lineInFence || isFenceLine
187+
? []
188+
: getDelimitedRanges(maskedText, "==", inlineCodeRanges);
189+
171190
for (const marker of markers) {
172191
if (lineInFence || isFenceLine || isMarkerInInlineCode(marker, inlineCodeRanges)) {
173192
continue;
@@ -185,11 +204,17 @@ function buildDecorations(
185204
continue;
186205
}
187206

207+
const markdownStyle = getMarkdownStyleForMarker(
208+
marker,
209+
emphasisRanges,
210+
strikeRanges,
211+
highlightRanges
212+
);
188213
builder.add(
189214
start,
190215
end,
191216
Decoration.replace({
192-
widget: new MetadataWidget(value, file.path, plugin, styleKey),
217+
widget: new MetadataWidget(value, file.path, plugin, styleKey, markdownStyle),
193218
inclusive: false,
194219
})
195220
);
@@ -232,6 +257,22 @@ function getLineMarkers(
232257
return markers;
233258
}
234259

260+
function maskMarkerText(text: string, markers: LineMarker[]): string {
261+
if (markers.length === 0) {
262+
return text;
263+
}
264+
265+
const chars = text.split("");
266+
for (const marker of markers) {
267+
const start = Math.max(0, marker.from);
268+
const end = Math.min(chars.length, marker.to);
269+
for (let i = start; i < end; i += 1) {
270+
chars[i] = "x";
271+
}
272+
}
273+
return chars.join("");
274+
}
275+
235276
function getFenceStateBeforeLine(doc: Text, lineNumber: number): FenceState {
236277
const state: FenceState = {inFence: false, fenceChar: "", fenceLen: 0};
237278
for (let line = 1; line < lineNumber; line += 1) {
@@ -269,6 +310,8 @@ function updateFenceStateForLine(
269310
}
270311

271312
type InlineCodeRange = {from: number; to: number};
313+
type EmphasisRange = {from: number; to: number; italic: boolean; bold: boolean};
314+
type DelimitedRange = {from: number; to: number};
272315

273316
function getInlineCodeRanges(text: string): InlineCodeRange[] {
274317
const ranges: InlineCodeRange[] = [];
@@ -311,6 +354,129 @@ function isMarkerInInlineCode(marker: LineMarker, ranges: InlineCodeRange[]): bo
311354
return false;
312355
}
313356

357+
function getDelimitedRanges(
358+
text: string,
359+
delimiter: string,
360+
inlineCodeRanges: InlineCodeRange[]
361+
): DelimitedRange[] {
362+
const ranges: DelimitedRange[] = [];
363+
let openStart: number | null = null;
364+
let i = 0;
365+
366+
while (i <= text.length - delimiter.length) {
367+
const inlineRange = findInlineRangeAt(i, inlineCodeRanges);
368+
if (inlineRange) {
369+
i = inlineRange.to;
370+
continue;
371+
}
372+
373+
if (text.slice(i, i + delimiter.length) !== delimiter) {
374+
i += 1;
375+
continue;
376+
}
377+
378+
if (openStart === null) {
379+
openStart = i;
380+
} else {
381+
ranges.push({from: openStart + delimiter.length, to: i});
382+
openStart = null;
383+
}
384+
385+
i += delimiter.length;
386+
}
387+
388+
return ranges;
389+
}
390+
391+
function getEmphasisRanges(text: string, inlineCodeRanges: InlineCodeRange[]): EmphasisRange[] {
392+
const ranges: EmphasisRange[] = [];
393+
const stack: {char: string; len: number; pos: number}[] = [];
394+
let i = 0;
395+
396+
while (i < text.length) {
397+
const inlineRange = findInlineRangeAt(i, inlineCodeRanges);
398+
if (inlineRange) {
399+
i = inlineRange.to;
400+
continue;
401+
}
402+
403+
const ch = text[i];
404+
if (ch !== "*" && ch !== "_") {
405+
i += 1;
406+
continue;
407+
}
408+
409+
let j = i;
410+
while (j < text.length && text[j] === ch) {
411+
j += 1;
412+
}
413+
const len = Math.min(j - i, 3);
414+
const top = stack[stack.length - 1];
415+
416+
if (top && top.char === ch && top.len === len) {
417+
const from = top.pos + top.len;
418+
const to = i;
419+
ranges.push({
420+
from,
421+
to,
422+
italic: len === 1 || len === 3,
423+
bold: len === 2 || len === 3,
424+
});
425+
stack.pop();
426+
} else {
427+
stack.push({char: ch, len, pos: i});
428+
}
429+
430+
i = j;
431+
}
432+
433+
return ranges;
434+
}
435+
436+
function findInlineRangeAt(pos: number, ranges: InlineCodeRange[]): InlineCodeRange | null {
437+
for (const range of ranges) {
438+
if (pos >= range.from && pos < range.to) {
439+
return range;
440+
}
441+
}
442+
return null;
443+
}
444+
445+
function getMarkdownStyleForMarker(
446+
marker: LineMarker,
447+
emphasisRanges: EmphasisRange[],
448+
strikeRanges: DelimitedRange[],
449+
highlightRanges: DelimitedRange[]
450+
): MarkdownStyle {
451+
let italic = false;
452+
let bold = false;
453+
let strike = false;
454+
let highlight = false;
455+
456+
for (const range of emphasisRanges) {
457+
if (rangesOverlap(marker.from, marker.to, range.from, range.to)) {
458+
italic = italic || range.italic;
459+
bold = bold || range.bold;
460+
}
461+
}
462+
463+
for (const range of strikeRanges) {
464+
if (rangesOverlap(marker.from, marker.to, range.from, range.to)) {
465+
strike = true;
466+
break;
467+
}
468+
}
469+
470+
for (const range of highlightRanges) {
471+
if (rangesOverlap(marker.from, marker.to, range.from, range.to)) {
472+
highlight = true;
473+
break;
474+
}
475+
}
476+
477+
return {italic, bold, strike, highlight};
478+
}
479+
314480
function pruneLineCache(lineCache: Map<number, LineMarkers>, maxLine: number): void {
315481
for (const lineNumber of lineCache.keys()) {
316482
if (lineNumber > maxLine) {
@@ -455,21 +621,33 @@ class MetadataWidget extends WidgetType {
455621
private readonly sourcePath: string;
456622
private readonly plugin: EmbedMetadataPlugin;
457623
private readonly styleKey: string;
624+
private readonly markdownStyle: MarkdownStyle;
458625
private readonly isEmpty: boolean;
459626

460-
constructor(value: string, sourcePath: string, plugin: EmbedMetadataPlugin, styleKey: string) {
627+
constructor(
628+
value: string,
629+
sourcePath: string,
630+
plugin: EmbedMetadataPlugin,
631+
styleKey: string,
632+
markdownStyle: MarkdownStyle
633+
) {
461634
super();
462635
this.value = value;
463636
this.sourcePath = sourcePath;
464637
this.plugin = plugin;
465638
this.styleKey = styleKey;
639+
this.markdownStyle = markdownStyle;
466640
this.isEmpty = value.length === 0;
467641
}
468642

469643
eq(other: MetadataWidget): boolean {
470644
return this.value === other.value
471645
&& this.sourcePath === other.sourcePath
472-
&& this.styleKey === other.styleKey;
646+
&& this.styleKey === other.styleKey
647+
&& this.markdownStyle.italic === other.markdownStyle.italic
648+
&& this.markdownStyle.bold === other.markdownStyle.bold
649+
&& this.markdownStyle.strike === other.markdownStyle.strike
650+
&& this.markdownStyle.highlight === other.markdownStyle.highlight;
473651
}
474652

475653
ignoreEvent(): boolean {
@@ -479,8 +657,33 @@ class MetadataWidget extends WidgetType {
479657
// Render the replacement widget node for a single syntax marker.
480658
toDOM(): HTMLElement {
481659
const span = document.createElement("span");
482-
renderInlineMarkdown(this.plugin.app, this.sourcePath, span, this.value, this.plugin);
483660
applyValueStyles(span, this.plugin.settings);
661+
let container = span;
662+
if (this.markdownStyle.highlight) {
663+
const mark = document.createElement("mark");
664+
mark.classList.add("cm-highlight");
665+
container.appendChild(mark);
666+
container = mark;
667+
}
668+
if (this.markdownStyle.strike) {
669+
const del = document.createElement("del");
670+
del.classList.add("cm-strikethrough");
671+
container.appendChild(del);
672+
container = del;
673+
}
674+
if (this.markdownStyle.bold) {
675+
const strong = document.createElement("strong");
676+
strong.classList.add("cm-strong");
677+
container.appendChild(strong);
678+
container = strong;
679+
}
680+
if (this.markdownStyle.italic) {
681+
const em = document.createElement("em");
682+
em.classList.add("cm-em");
683+
container.appendChild(em);
684+
container = em;
685+
}
686+
renderInlineMarkdown(this.plugin.app, this.sourcePath, container, this.value, this.plugin);
484687
if (this.isEmpty) {
485688
span.classList.add("embed-metadata-empty");
486689
}

styles.css

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
.embed-metadata-bold {
2-
font-weight: 600;
1+
.embed-metadata-value.embed-metadata-bold,
2+
.embed-metadata-value.cm-strong {
3+
font-weight: var(--bold-weight, 600);
4+
}
5+
6+
.embed-metadata-value.embed-metadata-bold *,
7+
.embed-metadata-value.cm-strong * {
8+
font-weight: inherit;
39
}
410

5-
.embed-metadata-italic {
11+
:where(.embed-metadata-italic) {
612
font-style: italic;
713
}
814

@@ -11,7 +17,17 @@
1117
text-decoration-color: var(--embed-metadata-underline-color, currentColor);
1218
}
1319

14-
.embed-metadata-highlight {
20+
:where(.embed-metadata-strike) {
21+
text-decoration: line-through;
22+
text-decoration-color: currentColor;
23+
}
24+
25+
.embed-metadata-underline.embed-metadata-strike {
26+
text-decoration: underline line-through;
27+
text-decoration-color: var(--embed-metadata-underline-color, currentColor);
28+
}
29+
30+
:where(.embed-metadata-highlight) {
1531
background-color: var(--embed-metadata-highlight-color, var(--text-highlight-bg, #fff59d));
1632
border-radius: 2px;
1733
padding: 0 2px;

0 commit comments

Comments
 (0)