@lowlighter/markdown 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +29 -0
  2. package/deno.jsonc +119 -0
  3. package/deno.lock +2777 -0
  4. package/mod.mjs +1 -0
  5. package/mod.ts +2 -0
  6. package/mod_test.ts +1 -0
  7. package/package.json +42 -0
  8. package/plugins/anchors.mjs +1 -0
  9. package/plugins/anchors.ts +21 -0
  10. package/plugins/anchors_test.ts +8 -0
  11. package/plugins/directives.mjs +1 -0
  12. package/plugins/directives.ts +112 -0
  13. package/plugins/directives_test.ts +13 -0
  14. package/plugins/emojis.mjs +1 -0
  15. package/plugins/emojis.ts +20 -0
  16. package/plugins/emojis_test.ts +8 -0
  17. package/plugins/frontmatter.mjs +1 -0
  18. package/plugins/frontmatter.ts +43 -0
  19. package/plugins/frontmatter_test.ts +18 -0
  20. package/plugins/gfm.mjs +1 -0
  21. package/plugins/gfm.ts +14 -0
  22. package/plugins/gfm_test.ts +28 -0
  23. package/plugins/highlighting.mjs +1 -0
  24. package/plugins/highlighting.ts +24 -0
  25. package/plugins/highlighting_test.ts +8 -0
  26. package/plugins/linebreaks.mjs +1 -0
  27. package/plugins/linebreaks.ts +21 -0
  28. package/plugins/linebreaks_test.ts +8 -0
  29. package/plugins/markers.mjs +1 -0
  30. package/plugins/markers.ts +25 -0
  31. package/plugins/markers_test.ts +13 -0
  32. package/plugins/math.ts +25 -0
  33. package/plugins/math_test.ts +8 -0
  34. package/plugins/mermaid.ts +41 -0
  35. package/plugins/mermaid_test.ts +8 -0
  36. package/plugins/ruby.mjs +1 -0
  37. package/plugins/ruby.ts +20 -0
  38. package/plugins/ruby_test.ts +8 -0
  39. package/plugins/sanitize.mjs +1 -0
  40. package/plugins/sanitize.ts +26 -0
  41. package/plugins/sanitize_test.ts +15 -0
  42. package/plugins/uncomments.mjs +1 -0
  43. package/plugins/uncomments.ts +20 -0
  44. package/plugins/uncomments_test.ts +8 -0
  45. package/plugins/wikilinks.mjs +1 -0
  46. package/plugins/wikilinks.ts +40 -0
  47. package/plugins/wikilinks_test.ts +14 -0
  48. package/renderer.mjs +1 -0
  49. package/renderer.ts +124 -0
  50. package/renderer_test.ts +14 -0
package/renderer.ts ADDED
@@ -0,0 +1,124 @@
1
+ // Imports
2
+ import { type Processor as _Processor, unified } from "unified"
3
+ import remarkRehype from "remark-rehype"
4
+ import remarkParse from "remark-parse"
5
+ import rehypeRaw from "rehype-raw"
6
+ import rehypeStringify from "rehype-stringify"
7
+ import pluginGfm from "./plugins/gfm.ts"
8
+ import pluginSanitize from "./plugins/sanitize.ts"
9
+
10
+ /**
11
+ * Markdown renderer.
12
+ */
13
+ export class Renderer {
14
+ /** Constructor. */
15
+ constructor({ plugins = [] } = {} as { plugins: Plugin[] }) {
16
+ this.#processor = unified().use(remarkParse)
17
+ plugins.filter(({ remark }) => remark).forEach(({ remark }) => this.#processor = remark!(this.#processor, this as unknown as FriendlyRenderer))
18
+ this.#processor = this.#processor.use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw)
19
+ plugins.filter(({ rehype }) => rehype).forEach(({ rehype }) => this.#processor = rehype!(this.#processor, this as unknown as FriendlyRenderer))
20
+ this.#processor = this.#processor.use(rehypeStringify)
21
+ }
22
+
23
+ /** Renderer processor. */
24
+ #processor: Processor
25
+
26
+ /** Plugins storage by render id. */
27
+ protected storage = {} as Record<PropertyKey, Record<PropertyKey, unknown>>
28
+
29
+ /**
30
+ * Render markdown content into an HTML string.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { Renderer } from "./renderer.ts"
35
+ * import pluginGfm from "./plugins/gfm.ts"
36
+ *
37
+ * const renderer = new Renderer({ plugins: [ pluginGfm ] })
38
+ * await renderer.render("# Hello, world!")
39
+ * ```
40
+ */
41
+ async render(content: string, options?: { metadata?: false }): Promise<string>
42
+ /**
43
+ * Render markdown content into an HTML string with parsed metadata.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { Renderer } from "./renderer.ts"
48
+ * import pluginGfm from "./plugins/gfm.ts"
49
+ * import pluginFrontmatter from "./plugins/frontmatter.ts"
50
+ *
51
+ * const renderer = new Renderer({ plugins: [ pluginGfm, pluginFrontmatter ] })
52
+ * await renderer.render(`
53
+ * ---
54
+ * title: Hello, world!
55
+ * ---
56
+ * Lorem ipsum dolor sit amet.
57
+ * `.trim())
58
+ * ```
59
+ */
60
+ async render(content: string, options?: { metadata: true }): Promise<{ value: string; metadata: Record<PropertyKey, unknown> }>
61
+ /**
62
+ * Render markdown content.
63
+ */
64
+ async render(content: string, { metadata = false } = {} as { metadata?: boolean }): Promise<string | { value: string; metadata: Record<PropertyKey, unknown> }> {
65
+ const id = (this.#id++) % Number.MAX_SAFE_INTEGER
66
+ try {
67
+ if (metadata) {
68
+ this.storage[id] ??= {}
69
+ }
70
+ const value = `${await this.#processor.process({ id, value: content, cwd: "" })}`
71
+ return metadata ? { value, metadata: { ...this.storage[id] } } : value
72
+ } finally {
73
+ delete this.storage[id]
74
+ }
75
+ }
76
+
77
+ /** Render request id counter. */
78
+ #id = 0
79
+
80
+ /** Default renderer instance. */
81
+ static default = new Renderer({ plugins: [pluginGfm, pluginSanitize] }) as Renderer
82
+
83
+ /** See {@link Renderer.render}. */
84
+ static render = this.default.render.bind(this.default) as typeof Renderer.prototype.render
85
+
86
+ /**
87
+ * Instantiate a new renderer with specified plugins.
88
+ *
89
+ * Plugins may be specified as a URL or string path to a module, or as an already import {@link Plugin} object.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { Renderer } from "./renderer.ts"
94
+ * import frontmatter from "./plugins/frontmatter.ts"
95
+ *
96
+ * const renderer = await Renderer.with({
97
+ * plugins: [
98
+ * frontmatter,
99
+ * "./plugins/gfm.ts",
100
+ * new URL("./plugins/sanitize.ts", import.meta.url),
101
+ * ]
102
+ * })
103
+ * await renderer.render("# foo")
104
+ * ```
105
+ */
106
+ static async with({ plugins = [] }: { plugins: Array<Plugin | URL | string> }): Promise<Renderer> {
107
+ plugins = plugins.map((plugin) => plugin instanceof URL ? plugin.href : plugin)
108
+ const resolved = await Promise.all(plugins.map((plugin) => (typeof plugin === "string") ? import(plugin) : plugin))
109
+ return new Renderer({ plugins: resolved })
110
+ }
111
+ }
112
+
113
+ /** {@link Renderer} with exposed protected properties. */
114
+ export type FriendlyRenderer = Renderer & { storage: Record<PropertyKey, Record<PropertyKey, unknown>> }
115
+
116
+ /** Markdown processor. */
117
+ // deno-lint-ignore no-explicit-any
118
+ export type Processor = _Processor<any, any, any, any, any>
119
+
120
+ /** Markdown plugin. */
121
+ export type Plugin = {
122
+ remark?: (processor: Processor, renderer: FriendlyRenderer) => Processor
123
+ rehype?: (processor: Processor, renderer: FriendlyRenderer) => Processor
124
+ }
@@ -0,0 +1,14 @@
1
+ import { expect, test } from "@libs/testing"
2
+ import { Renderer } from "./renderer.ts"
3
+ import pluginGfm from "./plugins/gfm.ts"
4
+
5
+ test("deno")("Renderer.render() renders markdown", async () => {
6
+ const markdown = new Renderer()
7
+ await expect(markdown.render("# foo")).resolves.toBe("<h1>foo</h1>")
8
+ await expect(Renderer.render("# foo")).resolves.toBe("<h1>foo</h1>")
9
+ })
10
+
11
+ test("deno")("Renderer.with() instantiates a new customized renderer", async () => {
12
+ const markdown = await Renderer.with({ plugins: ["./plugins/gfm.ts", new URL("./plugins/gfm.ts", import.meta.url), pluginGfm] })
13
+ await expect(markdown.render("# foo")).resolves.toBe("<h1>foo</h1>")
14
+ })