-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from bsoule/support-for-ids-in-md
Fix IDs not being applied to headings with inline styling
- Loading branch information
Showing
16 changed files
with
320 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { describe, it, expect } from "vitest"; | ||
import { marked } from "marked"; | ||
import ids from "./ids.js"; | ||
|
||
marked.use(ids); | ||
|
||
describe("marked ids extension", () => { | ||
it("works", () => { | ||
const r = marked.parse("{#foo}bar"); | ||
expect(r).toContain('<p id="foo">bar</p>'); | ||
}); | ||
|
||
it("works in combination with italics", () => { | ||
const r = marked.parse("(words *foo*) {#bar}"); | ||
expect(r).toContain('<p id="bar">(words <em>foo</em>)</p>'); | ||
}); | ||
|
||
it("leaves comments intact", () => { | ||
const r = marked.parse("<!-- {#foo} -->\nbar"); | ||
|
||
expect(r).toContain("<!-- {#foo} -->"); | ||
}); | ||
|
||
it("leaves multiline comments intact", () => { | ||
const r = marked.parse("<!-- {#foo}\nbar -->\nbaz"); | ||
|
||
expect(r).toContain("<!-- {#foo}\nbar -->"); | ||
}); | ||
|
||
it("does not aggressively apply ids to parents", () => { | ||
const r = marked.parse("> {#foo}bar"); | ||
|
||
expect(r).toBe('<blockquote>\n<p id="foo">bar</p>\n</blockquote>\n'); | ||
}); | ||
|
||
it("does not decode entities", () => { | ||
const r = marked.parse( | ||
"Anyway, in terms of signals that we’re alive, we have" | ||
); | ||
|
||
expect(r).toContain( | ||
"<p>Anyway, in terms of signals that we’re alive, we have</p>" | ||
); | ||
}); | ||
|
||
it("does not concat words surrounding the id", () => { | ||
const r = marked.parse("foo {#bar} baz"); | ||
|
||
expect(r).toContain('<p id="bar">foo baz</p>'); | ||
}); | ||
|
||
it("handles code blocks", () => { | ||
const r = marked.parse("```\n{#foo}\nbar\n```"); | ||
|
||
expect(r).toContain('<pre id="foo">'); | ||
}); | ||
|
||
it("handles headings", () => { | ||
const r = marked.parse("# heading {#foo}"); | ||
|
||
expect(r).toContain('<h1 id="foo">heading</h1>'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { type Tokens, type MarkedExtension, Renderer } from "marked"; | ||
|
||
function parseContent(token: Tokens.Generic, ctx: Renderer): string { | ||
const c = token.tokens ? ctx.parser.parseInline(token.tokens) : token.text; | ||
const r = new RegExp(`\\s?\\{#${token.id}\\}`, "g"); | ||
return c.replace(r, ""); | ||
} | ||
|
||
const ids: MarkedExtension = { | ||
walkTokens(token: Tokens.Generic): void { | ||
if (token.type === "text") return; | ||
|
||
const match = token.tokens?.find( | ||
(t) => t.type === "text" && /\{#(.*?)\}/.test(t.raw) | ||
); | ||
|
||
if (!match && token.tokens?.length) return; | ||
|
||
const idMatch = token.raw.match(/\{#(.*?)\}/); | ||
|
||
if (!idMatch) return; | ||
|
||
token.id = idMatch[1]; | ||
}, | ||
renderer: { | ||
paragraph(t: Tokens.Generic) { | ||
if (!t.id) return false; | ||
return `<p id="${t.id}">${parseContent(t, this)}</p>\n`; | ||
}, | ||
code(t: Tokens.Generic) { | ||
if (!t.id) return false; | ||
return `<pre id="${t.id}"><code>${parseContent(t, this)}</code></pre>`; | ||
}, | ||
heading(t: Tokens.Generic) { | ||
if (!t.id) return false; | ||
return `<h${t.depth} id="${t.id}">${parseContent(t, this)}</h${ | ||
t.depth | ||
}>\n`; | ||
}, | ||
}, | ||
}; | ||
|
||
export default ids; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { describe, it, expect } from "vitest"; | ||
import { marked } from "marked"; | ||
import smartypants from "./smartypants.js"; | ||
|
||
marked.use(smartypants); | ||
|
||
describe("marked ids extension", () => { | ||
it("handles arrows", () => { | ||
const r = marked.parse("-> `a`"); | ||
|
||
expect(r).toContain("<p>-> <code>a</code></p>"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import type { MarkedExtension, Tokens } from "marked"; | ||
import { smartypants } from "smartypants"; | ||
|
||
const extension: MarkedExtension = { | ||
tokenizer: { | ||
inlineText(src: string): false | Tokens.Text | undefined { | ||
// don't escape inlineText | ||
const cap = this.rules.inline.text.exec(src); | ||
const raw = cap?.[0] ?? ""; | ||
const text = raw.replace("<", "<").replace(">", ">"); | ||
|
||
return { | ||
type: "text", | ||
raw, | ||
text: text, | ||
}; | ||
}, | ||
}, | ||
hooks: { | ||
postprocess(html: string) { | ||
return smartypants(html, "1"); | ||
}, | ||
}, | ||
}; | ||
|
||
export default extension; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { Tokens } from "marked"; | ||
|
||
export default { | ||
tokenizer: { | ||
url(src: string): Tokens.Link | false { | ||
const urlRegex = /^https?:\/\/[^\s\]]+/; | ||
const match = src.match(urlRegex); | ||
|
||
if (match) { | ||
return { | ||
type: "link", | ||
raw: match[0], | ||
href: match[0], | ||
text: match[0], | ||
tokens: [ | ||
{ | ||
type: "text", | ||
raw: match[0], | ||
text: match[0], | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
return false; | ||
}, | ||
}, | ||
renderer: { | ||
link({ href, text }: Tokens.Link) { | ||
const emailRegex = /^mailto:\S+@\S+\.\S+$/; | ||
if (emailRegex.test(href)) { | ||
return text; | ||
} | ||
|
||
return false; | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { describe, it, expect } from "vitest"; | ||
import parse from "./parse.js"; | ||
|
||
describe("marked ids extension", () => { | ||
it("preserves HTML entities", () => { | ||
const r = parse( | ||
'```\n{#example} <a id="foo1" href="#foo">[N]</a>\n```' | ||
); | ||
|
||
expect(r).toContain('<a id="foo1" href="#foo">[N]</a>'); | ||
}); | ||
|
||
it("parses ~~ as strikethrough", () => { | ||
const r = parse("~~foo~~"); | ||
|
||
expect(r).toContain("<del>foo</del>"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { marked } from "marked"; | ||
import smartypants from "./extentions/smartypants.js"; | ||
import urls from "./extentions/urls.js"; | ||
import ids from "./extentions/ids.js"; | ||
|
||
marked.use(urls, smartypants, ids); | ||
|
||
export default function parse(markdown: string): string { | ||
// WORKAROUND: `marked.parse` shouldn't return a promise if | ||
// the `async` option has not been set to `true` | ||
// https://marked.js.org/using_pro#async | ||
return marked.parse(markdown) as string; | ||
} |
Oops, something went wrong.