Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion markdown/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
"./plugins/ruby": "./plugins/ruby.ts",
"./plugins/sanitize": "./plugins/sanitize.ts",
"./plugins/uncomments": "./plugins/uncomments.ts",
"./plugins/wikilinks": "./plugins/wikilinks.ts"
"./plugins/wikilinks": "./plugins/wikilinks.ts",
"./presets/default": "./presets/default.ts",
"./presets/svg": "./presets/svg.ts",
"./presets/text": "./presets/text.ts",
"./presets/web": "./presets/web.ts"
},
"test:permissions": {
"read": true,
Expand Down
12 changes: 12 additions & 0 deletions markdown/presets/default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Imports
import { Renderer } from "../renderer.ts"

/** Renderer instance. */
const renderer = new Renderer()

/**
* Renders a markdown expression.
*/
export function markdown(text: string): Promise<string> {
return renderer.render(text)
}
14 changes: 14 additions & 0 deletions markdown/presets/default_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) - 2025+ the lowlighter/esquie authors. AGPL-3.0-or-later
import { expect, inspect, test } from "@libs/testing"
import { markdown } from "./default.ts"

for (
const { text, render } of [
{ text: "**foo**", mode: "default", render: "<p><strong>foo</strong></p>" },
]
) {
test(`\`markdown(${inspect(text)})\` returns ${inspect(render)}`, async () => {
const rendered = await markdown(text)
expect(rendered).toBe(render)
})
}
91 changes: 91 additions & 0 deletions markdown/presets/svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Imports
import { Renderer } from "../renderer.ts"
import gfm from "../plugins/gfm.ts"
import { create as sanitize } from "../plugins/sanitize.ts"
import highlighting from "../plugins/highlighting.ts"

/** Renderer instance. */
const renderer = new Renderer({
plugins: [
gfm,
sanitize({
tagNames: [
// Headers
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
// Text
"p",
"strong",
"em",
"del",
"sup",
"sub",
// Blockquotes
"blockquote",
// Code
"pre",
"code",
"kbd",
// Links
"a",
// Images
"img",
// Tables
"table",
"thead",
"tbody",
"tfoot",
"tr",
"th",
"td",
// Lists
"ul",
"ol",
"li",
"input",
// Horizontal rules
"hr",
// Line breaks
"br",
],
strip: ["script"],
required: {
input: { disabled: true, type: "checkbox" },
},
ancestors: {
tbody: ["table"],
td: ["table"],
th: ["table"],
thead: ["table"],
tfoot: ["table"],
tr: ["table"],
},
attributes: {
a: ["href"],
code: [["className", /^language-./]],
img: ["src", "alt"],
input: [["disabled", true], ["type", "checkbox"], "checked"],
li: [["className", "task-list-item"]],
ol: [["className", "contains-task-list"]],
ul: [["className", "contains-task-list"]],
"*": ["align", "alt", "height", "width", "title", "width"],
},
protocols: {
href: ["http", "https"],
src: ["http", "https", "data"],
},
}),
highlighting,
],
})

/**
* Renders a markdown expression suitable for SVG (sanitized with a subset of tags and attributes).
*/
export function markdown(text: string): Promise<string> {
return renderer.render(text)
}
20 changes: 20 additions & 0 deletions markdown/presets/svg_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) - 2025+ the lowlighter/esquie authors. AGPL-3.0-or-later
import { expect, inspect, test } from "@libs/testing"
import { markdown } from "./svg.ts"

for (
const { text, render } of [
{ text: "**foo**", mode: "svg", render: "<p><strong>foo</strong></p>" },
{ text: "```ts\nconst foo = true\n```", mode: "svg", render: /class="hljs language-ts"/ },
{ text: "foo <script>1 + 1</script> bar", mode: "svg", render: "<p>foo bar</p>" },
]
) {
test(`\`markdown(${inspect(text)})\` ${render instanceof RegExp ? "matches" : "returns"} ${inspect(render)}`, async () => {
const rendered = await markdown(text)
if (render instanceof RegExp) {
expect(rendered).toMatch(render)
} else {
expect(rendered).toBe(render)
}
})
}
13 changes: 13 additions & 0 deletions markdown/presets/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) - 2025+ the lowlighter/esquie authors. AGPL-3.0-or-later
import { Renderer } from "../renderer.ts"
import { create as sanitize } from "../plugins/sanitize.ts"

/** Renderer instances. */
const renderer = new Renderer({ plugins: [sanitize({ tagNames: [] })] })

/**
* Renders a markdown expression as plain text (all HTML is stripped).
*/
export function markdown(text: string): Promise<string> {
return renderer.render(text)
}
16 changes: 16 additions & 0 deletions markdown/presets/text_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) - 2025+ the lowlighter/esquie authors. AGPL-3.0-or-later
import { expect, inspect, test } from "@libs/testing"
import { markdown } from "./text.ts"

for (
const { text, render } of [
{ text: "**foo**", mode: "text", render: "foo" },
{ text: "```ts\nconst foo = true\n```", mode: "text", render: "const foo = true\n" },
{ text: "foo <script>1 + 1</script> bar", mode: "text", render: "foo bar" },
]
) {
test(`\`markdown(${inspect(text)})\` returns ${inspect(render)}`, async () => {
const rendered = await markdown(text)
expect(rendered).toBe(render)
})
}
14 changes: 14 additions & 0 deletions markdown/presets/web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Imports
import { Renderer } from "../renderer.ts"
import gfm from "../plugins/gfm.ts"
import highlighting from "../plugins/highlighting.ts"

/** Renderer instance. */
const renderer = new Renderer({ plugins: [gfm, highlighting] })

/**
* Renders a markdown expression suitable for web pages (including untrusted HTML).
*/
export function markdown(text: string): Promise<string> {
return renderer.render(text)
}
20 changes: 20 additions & 0 deletions markdown/presets/web_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) - 2025+ the lowlighter/esquie authors. AGPL-3.0-or-later
import { expect, inspect, test } from "@libs/testing"
import { markdown } from "./web.ts"

for (
const { text, render } of [
{ text: "**foo**", mode: "web", render: "<p><strong>foo</strong></p>" },
{ text: "```ts\nconst foo = true\n```", mode: "web", render: /class="hljs language-ts"/ },
{ text: "foo <script>1 + 1</script> bar", mode: "web", render: "<p>foo <script>1 + 1</script> bar</p>" },
]
) {
test(`\`markdown(${inspect(text)}})\` ${render instanceof RegExp ? "matches" : "returns"} ${inspect(render)}`, async () => {
const rendered = await markdown(text)
if (render instanceof RegExp) {
expect(rendered).toMatch(render)
} else {
expect(rendered).toBe(render)
}
})
}
Loading