@@ -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+
2128type 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+
235276function 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
271312type InlineCodeRange = { from : number ; to : number } ;
313+ type EmphasisRange = { from : number ; to : number ; italic : boolean ; bold : boolean } ;
314+ type DelimitedRange = { from : number ; to : number } ;
272315
273316function 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+
314480function 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 }
0 commit comments