1
1
import { emojify } from "emoji" ;
2
- import * as Marked from "marked" ;
3
2
import GitHubSlugger from "github-slugger" ;
3
+ import he from "he" ;
4
+ import katex from "katex" ;
5
+ import * as Marked from "marked" ;
4
6
import markedAlert from "marked-alert" ;
5
7
import markedFootnote from "marked-footnote" ;
6
8
import { gfmHeadingId } from "marked-gfm-heading-id" ;
7
9
import Prism from "prismjs" ;
8
10
import sanitizeHtml from "sanitize-html" ;
9
- import he from "he" ;
10
- import katex from "katex" ;
11
+ import "prismjs-yaml" ;
11
12
12
13
import { CSS , KATEX_CLASSES , KATEX_CSS } from "./style.ts" ;
13
14
export { CSS , KATEX_CSS , Marked } ;
14
- import "https://esm.sh/[email protected] /components/prism-yaml" ;
15
15
16
16
Marked . marked . use ( markedAlert ( ) ) ;
17
17
Marked . marked . use ( gfmHeadingId ( ) ) ;
@@ -39,11 +39,7 @@ export class Renderer extends Marked.Renderer {
39
39
this . #slugger = new GitHubSlugger ( ) ;
40
40
}
41
41
42
- heading (
43
- text : string ,
44
- level : 1 | 2 | 3 | 4 | 5 | 6 ,
45
- raw : string ,
46
- ) : string {
42
+ heading ( text : string , level : 1 | 2 | 3 | 4 | 5 | 6 , raw : string ) : string {
47
43
const slug = this . #slugger. slug ( raw ) ;
48
44
return `<h${ level } id="${ slug } "><a class="anchor" aria-hidden="true" tabindex="-1" href="#${ slug } "><svg class="octicon octicon-link" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a>${ text } </h${ level } >\n` ;
49
45
}
@@ -53,6 +49,13 @@ export class Renderer extends Marked.Renderer {
53
49
}
54
50
55
51
code ( code : string , language ?: string ) : string {
52
+ const isTitleIncluded = language ?. match ( / \s t i t l e = " ( .+ ) " / ) ;
53
+ let title = null ;
54
+ if ( isTitleIncluded ) {
55
+ language = language ! . split ( " " ) [ 0 ] ;
56
+ title = isTitleIncluded [ 1 ] ;
57
+ }
58
+
56
59
// a language of `ts, ignore` should really be `ts`
57
60
// and it should be lowercase to ensure it has parity with regular github markdown
58
61
language = language ?. split ( "," ) ?. [ 0 ] . toLocaleLowerCase ( ) ;
@@ -70,7 +73,10 @@ export class Renderer extends Marked.Renderer {
70
73
return `<pre><code class="notranslate">${ he . encode ( code ) } </code></pre>` ;
71
74
}
72
75
const html = Prism . highlight ( code , grammar , language ! ) ;
73
- return `<div class="highlight highlight-source-${ language } notranslate"><pre>${ html } </pre></div>` ;
76
+ const titleHtml = title
77
+ ? `<div class="markdown-code-title">${ title } </div>`
78
+ : `` ;
79
+ return `<div class="highlight highlight-source-${ language } notranslate">${ titleHtml } <pre>${ html } </pre></div>` ;
74
80
}
75
81
76
82
link ( href : string , title : string | null , text : string ) : string {
@@ -153,10 +159,11 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
153
159
154
160
const marked_opts = getOpts ( opts ) ;
155
161
156
- const html =
157
- ( opts . inline
162
+ const html = (
163
+ opts . inline
158
164
? Marked . marked . parseInline ( markdown , marked_opts )
159
- : Marked . marked . parse ( markdown , marked_opts ) ) as string ;
165
+ : Marked . marked . parse ( markdown , marked_opts )
166
+ ) as string ;
160
167
161
168
if ( opts . disableHtmlSanitization ) {
162
169
return html ;
@@ -231,6 +238,7 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
231
238
"notranslate" ,
232
239
"markdown-alert" ,
233
240
"markdown-alert-*" ,
241
+ "markdown-code-title" ,
234
242
] ,
235
243
span : [
236
244
"token" ,
@@ -312,18 +320,22 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
312
320
annotation : [ "encoding" ] , // Only enabled when math is enabled
313
321
details : [ "open" ] ,
314
322
section : [ "data-footnotes" ] ,
315
- input : [ "checked" , "disabled" , {
316
- name : "type" ,
317
- values : [ "checkbox" ] ,
318
- } ] ,
323
+ input : [
324
+ "checked" ,
325
+ "disabled" ,
326
+ {
327
+ name : "type" ,
328
+ values : [ "checkbox" ] ,
329
+ } ,
330
+ ] ,
319
331
} ;
320
332
321
333
return sanitizeHtml ( html , {
322
334
transformTags : {
323
335
img : transformMedia ,
324
336
video : transformMedia ,
325
337
} ,
326
- allowedTags : [ ...defaultAllowedTags , ...opts . allowedTags ?? [ ] ] ,
338
+ allowedTags : [ ...defaultAllowedTags , ...( opts . allowedTags ?? [ ] ) ] ,
327
339
allowedAttributes : mergeAttributes (
328
340
defaultAllowedAttributes ,
329
341
opts . allowedAttributes ?? { } ,
@@ -356,14 +368,12 @@ function stripTokens(
356
368
357
369
for ( const token of tokens ) {
358
370
if ( token . type === "heading" ) {
359
- sections [ index ] . header = sections [ index ] . header . trim ( ) . replace (
360
- / \n { 3 , } / g,
361
- "\n" ,
362
- ) ;
363
- sections [ index ] . content = sections [ index ] . content . trim ( ) . replace (
364
- / \n { 3 , } / g,
365
- "\n" ,
366
- ) ;
371
+ sections [ index ] . header = sections [ index ] . header
372
+ . trim ( )
373
+ . replace ( / \n { 3 , } / g, "\n" ) ;
374
+ sections [ index ] . content = sections [ index ] . content
375
+ . trim ( )
376
+ . replace ( / \n { 3 , } / g, "\n" ) ;
367
377
368
378
sections . push ( { header : "" , depth : token . depth , content : "" } ) ;
369
379
index += 1 ;
@@ -488,20 +498,21 @@ export function stripSplitBySections(
488
498
markdown : string ,
489
499
opts : RenderOptions = { } ,
490
500
) : MarkdownSections [ ] {
491
- markdown = emojify ( markdown ) . replace ( BLOCK_MATH_REGEXP , "" ) . replace (
492
- INLINE_MATH_REGEXP ,
493
- "" ,
494
- ) ;
501
+ markdown = emojify ( markdown )
502
+ . replace ( BLOCK_MATH_REGEXP , "" )
503
+ . replace ( INLINE_MATH_REGEXP , "" ) ;
495
504
const tokens = Marked . marked . lexer ( markdown , {
496
505
...getOpts ( opts ) ,
497
506
tokenizer : new StripTokenizer ( ) ,
498
507
} ) ;
499
508
500
- const sections : MarkdownSections [ ] = [ {
501
- header : "" ,
502
- depth : 0 ,
503
- content : "" ,
504
- } ] ;
509
+ const sections : MarkdownSections [ ] = [
510
+ {
511
+ header : "" ,
512
+ depth : 0 ,
513
+ content : "" ,
514
+ } ,
515
+ ] ;
505
516
stripTokens ( tokens , sections , false ) ;
506
517
507
518
return sections ;
@@ -511,7 +522,11 @@ export function stripSplitBySections(
511
522
* Strip all markdown syntax to get a plaintext output
512
523
*/
513
524
export function strip ( markdown : string , opts : RenderOptions = { } ) : string {
514
- return stripSplitBySections ( markdown , opts ) . map ( ( section ) =>
515
- section . header + "\n\n" + section . content
516
- ) . join ( "\n\n" ) . trim ( ) . replace ( / \n { 3 , } / g, "\n" ) + "\n" ;
525
+ return (
526
+ stripSplitBySections ( markdown , opts )
527
+ . map ( ( section ) => section . header + "\n\n" + section . content )
528
+ . join ( "\n\n" )
529
+ . trim ( )
530
+ . replace ( / \n { 3 , } / g, "\n" ) + "\n"
531
+ ) ;
517
532
}
0 commit comments