All files / markdown / renderer.ts

100.00% Branches 32/32
100.00% Functions 3/3
100.00% Lines 57/57
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
 
x19
 
x19
x19
x19
x19
x19
x19
x19
x19
x19
x19
x19
x19
x19
 
 
x19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x19
 
x19
x39
x39
x39
x39
x39
 
x1
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
x39
 
 
x19
 
 
x19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x19
x66
x66
x66
x66
x19











































































































































// Imports
import untyped from "markdown-it"
import type { MarkdownIt, MarkdownItConstructor } from "./types.ts"
import anchors from "./plugins/anchors.ts"
import callouts from "./plugins/callouts.ts"
import directives, { type DirectivesOptions } from "./plugins/directives.ts"
import emojis from "./plugins/emojis.ts"
import frontmatter from "./plugins/frontmatter.ts"
import gfm from "./plugins/gfm.ts"
import highlighting from "./plugins/highlighting.ts"
import markers from "./plugins/markers.ts"
import math from "./plugins/math.ts"
import mermaid from "./plugins/mermaid.ts"
import ruby from "./plugins/ruby.ts"
import uncomments from "./plugins/uncomments.ts"
import wikilinks, { type WikilinksOptions } from "./plugins/wikilinks.ts"

/** markdown-it engine (untyped npm package, see ./types.ts). */
const MarkdownItEngine = untyped as unknown as MarkdownItConstructor

/** Renderer options. */
export type RendererOptions = {
  /** Enable GitHub Flavored Markdown (tables, strikethrough, autolinks, footnotes and tasklists). */
  gfm?: boolean
  /**
   * Allow raw HTML to pass through.
   *
   * When disabled (default), raw HTML is escaped and rendered as text, which makes the output safe against HTML injection.
   */
  html?: boolean
  /** Render line-breaks as hard-breaks (`<br>`). */
  breaks?: boolean
  /** Add anchors to headings and autolink them. */
  anchors?: boolean
  /** Enable Obsidian-style callouts (`> [!note]`). */
  callouts?: boolean
  /** Enable custom directives (`:::name`). */
  directives?: boolean | DirectivesOptions
  /** Enable emojis shortcodes (`:bento:`). */
  emojis?: boolean
  /** Parse YAML frontmatter (retrieve it by passing `metadata: true` to the render method). */
  frontmatter?: boolean
  /** Enable syntax highlighting of code blocks. */
  highlighting?: boolean
  /** Enable markers (`==foo==`). */
  markers?: boolean
  /** Enable math expressions (`$foo$`, `$$foo$$`), rendered as markup intended for client-side processing by KaTeX or MathJax. */
  math?: boolean
  /** Enable mermaid diagrams, rendered as markup intended for client-side processing by mermaid. */
  mermaid?: boolean
  /** Enable ruby annotations (`{漢字}^(kanji)`). */
  ruby?: boolean
  /** Remove HTML comments. */
  uncomments?: boolean
  /** Enable wiki links (`[[foo]]`). */
  wikilinks?: boolean | WikilinksOptions
  /** Hook direct access to the underlying {@link https://github.com/markdown-it/markdown-it | markdown-it} instance for custom rules and renderers. */
  hooks?: (engine: MarkdownIt) => void
}

/**
 * Markdown renderer.
 *
 * ```ts
 * import { Renderer } from "./renderer.ts"
 * const renderer = new Renderer({ highlighting: true, math: true, wikilinks: true })
 * renderer.render("# Hello, world!")
 * ```
 */
export class Renderer {
  /** Constructor. */
  constructor(options = {} as RendererOptions) {
    this.options = Object.freeze({ ...options })
    const { gfm: flavored = true, html = false, breaks = false, hooks } = options
    this.#engine = new MarkdownItEngine({ html, breaks, linkify: flavored })
    if (flavored)
      this.#engine.use(gfm)
    else
      this.#engine.disable(["table", "strikethrough"])
    if (options.frontmatter)
      this.#engine.use(frontmatter)
    if (options.uncomments)
      this.#engine.use(uncomments)
    if (options.anchors)
      this.#engine.use(anchors)
    if (options.callouts)
      this.#engine.use(callouts)
    if (options.directives)
      this.#engine.use(directives, options.directives === true ? {} : options.directives)
    if (options.emojis)
      this.#engine.use(emojis)
    if (options.highlighting)
      this.#engine.use(highlighting)
    if (options.markers)
      this.#engine.use(markers)
    if (options.math)
      this.#engine.use(math)
    if (options.mermaid)
      this.#engine.use(mermaid)
    if (options.ruby)
      this.#engine.use(ruby)
    if (options.wikilinks)
      this.#engine.use(wikilinks, options.wikilinks === true ? {} : options.wikilinks)
    hooks?.(this.#engine)
  }

  /** Options used to instantiate this renderer. */
  readonly options: Readonly<RendererOptions>

  /** Underlying {@link https://github.com/markdown-it/markdown-it | markdown-it} instance. */
  readonly #engine: MarkdownIt

  /**
   * Render markdown content into an HTML string.
   *
   * ```ts
   * import { Renderer } from "./renderer.ts"
   * new Renderer().render("# Hello, world!")
   * ```
   */
  render(content: string, options?: { metadata?: false }): string
  /**
   * Render markdown content into an HTML string with parsed metadata.
   *
   * ```ts
   * import { Renderer } from "./renderer.ts"
   * const renderer = new Renderer({ frontmatter: true })
   * const { value, metadata } = renderer.render("---\ntitle: foo\n---\nbar", { metadata: true })
   * console.log(metadata.frontmatter) // { title: "foo" }
   * ```
   */
  render(content: string, options: { metadata: true }): { value: string; metadata: Record<PropertyKey, unknown> }
  /**
   * Render markdown content.
   */
  render(content: string, { metadata = false } = {} as { metadata?: boolean }): string | { value: string; metadata: Record<PropertyKey, unknown> } {
    const env = { metadata: {} as Record<PropertyKey, unknown> }
    const value = this.#engine.render(content, env).trimEnd()
    return metadata ? { value, metadata: env.metadata } : value
  }
}