Skip to content

Commit

Permalink
Merge branch 'main' into 11_custom_tags
Browse files Browse the repository at this point in the history
  • Loading branch information
deer committed Jan 29, 2024
2 parents 88bf857 + 1c3f599 commit 21e7a9f
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 36 deletions.
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dev": "deno run -A --unstable --watch --no-check ./example/main.ts",
"ok": "deno fmt --check && deno lint && deno task check:types && deno task test",
"report": "deno coverage cov_profile --html",
"server": "deno run -A --watch=test/,mod.ts ./test/runTestServer.ts",
"test": "deno test --allow-read --allow-env --allow-write --allow-run --allow-net"
},
"fmt": {
Expand Down
2 changes: 2 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export { default as GitHubSlugger } from "https://esm.sh/[email protected]?pi

export { default as markedAlert } from "https://esm.sh/[email protected]?pin=v135";

export { default as markedFootnote } from "https://esm.sh/[email protected]?pin=v135";

export { gfmHeadingId } from "https://esm.sh/[email protected]?pin=v135";

export { default as Prism } from "https://esm.sh/[email protected]?pin=v135";
Expand Down
26 changes: 22 additions & 4 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
katex,
Marked,
markedAlert,
markedFootnote,
Prism,
sanitizeHtml,
} from "./deps.ts";
Expand All @@ -14,25 +15,26 @@ export { CSS, KATEX_CSS, Marked };

Marked.marked.use(markedAlert());
Marked.marked.use(gfmHeadingId());

const slugger = new GitHubSlugger();
Marked.marked.use(markedFootnote());

export class Renderer extends Marked.Renderer {
allowMath: boolean;
baseUrl: string | undefined;
#slugger: GitHubSlugger;

constructor(options: Marked.MarkedOptions & RenderOptions = {}) {
super(options);
this.baseUrl = options.baseUrl;
this.allowMath = options.allowMath ?? false;
this.#slugger = new GitHubSlugger();
}

heading(
text: string,
level: 1 | 2 | 3 | 4 | 5 | 6,
raw: string,
): string {
const slug = slugger.slug(raw);
const slug = this.#slugger.slug(raw);
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}>`;
}

Expand Down Expand Up @@ -238,6 +240,8 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
a: ["anchor"],
p: ["markdown-alert-title"],
svg: ["octicon", "octicon-alert", "octicon-link"],
h2: ["sr-only"],
section: ["footnotes"],
};

const defaultAllowedAttributes = {
Expand All @@ -256,7 +260,19 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
"controls",
"title",
],
a: ["id", "aria-hidden", "href", "tabindex", "rel", "target", "title"],
a: [
"id",
"aria-hidden",
"href",
"tabindex",
"rel",
"target",
"title",
"data-footnote-ref",
"data-footnote-backref",
"aria-label",
"aria-describedby",
],
svg: ["viewbox", "width", "height", "aria-hidden", "background"],
path: ["fill-rule", "d"],
circle: ["cx", "cy", "r", "stroke", "stroke-width", "fill", "alpha"],
Expand All @@ -267,11 +283,13 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
h4: ["id"],
h5: ["id"],
h6: ["id"],
li: ["id"],
td: ["colspan", "rowspan", "align"],
iframe: ["src", "width", "height"], // Only used when iframe tags are allowed in the first place.
math: ["xmlns"], // Only enabled when math is enabled
annotation: ["encoding"], // Only enabled when math is enabled
details: ["open"],
section: ["data-footnotes"],
};

return sanitizeHtml(html, {
Expand Down
2 changes: 1 addition & 1 deletion style.js

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions style/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,22 @@
background-color: var(--color-prettylights-syntax-markup-inserted-bg);
}
}

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
word-wrap: normal;
border: 0;
}

[data-footnote-ref]::before {
content: '[';
}

[data-footnote-ref]::after {
content: ']';
}
57 changes: 57 additions & 0 deletions test/fixtures/footnote.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<h1 id="example"><a class="anchor" aria-hidden="true" tabindex="-1" href="#example"><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>Example</h1><p>Here is a simple footnote<sup><a id="footnote-ref-1" href="#footnote-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>.
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /> With some additional text after it<sup><a id="footnote-ref-%40%23%24%25" href="#footnote-%40%23%24%25" data-footnote-ref aria-describedby="footnote-label">2</a></sup>
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /> and without disrupting the
blocks<sup><a id="footnote-ref-bignote" href="#footnote-bignote" data-footnote-ref aria-describedby="footnote-label">3</a></sup>.
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
<section class="footnotes" data-footnotes>
<h2 id="footnote-label" class="sr-only">Footnotes</h2>
<ol>
<li id="footnote-1">
<p>This is a footnote content. <a href="#footnote-ref-1" data-footnote-backref aria-label="Back to reference 1"></a></p>
</li>
<li id="footnote-%40%23%24%25">
<p>A footnote on the label: "@#$%". <a href="#footnote-ref-%40%23%24%25" data-footnote-backref aria-label="Back to reference @#$%"></a></p>
</li>
<li id="footnote-bignote">
<p>The first paragraph of the definition.</p>
<p>Paragraph two of the definition.</p>
<blockquote>
<p>A blockquote with
multiple lines.</p>
</blockquote>
<pre><code>a code block</code></pre><table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody><tr>
<td>Cell 1</td>
<td>Cell 2</td>
</tr>
</tbody></table>
<p>A `final` paragraph before list.</p>
<ul>
<li>Item 1</li>
<li>Item 2<ul>
<li>Subitem 1</li>
<li>Subitem 2</li>
</ul>
</li>
</ul> <a href="#footnote-ref-bignote" data-footnote-backref aria-label="Back to reference bignote"></a>
</li>
</ol>
</section>
45 changes: 45 additions & 0 deletions test/fixtures/footnote.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Example

[^1]: This is a footnote content.

Here is a simple footnote[^1].
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br> With some additional text after it[^@#$%]
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br> and without disrupting the
blocks[^bignote].
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br>

[^bignote]: The first paragraph of the definition.

Paragraph two of the definition.

> A blockquote with
> multiple lines.
~~~
a code block
~~~
| Header 1 | Header 2 |
| -------- | -------- |
| Cell 1 | Cell 2 |
A \`final\` paragraph before list.
- Item 1
- Item 2
- Subitem 1
- Subitem 2
[^@#$%]: A footnote on the label: "@#$%".
2 changes: 2 additions & 0 deletions test/runTestServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { startServer } from "./test_utils.ts";
startServer();
97 changes: 79 additions & 18 deletions test/server_test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
import { assert } from "./test_deps.ts";
import { render } from "../mod.ts";
import { browserTest, setupHtmlWithCss } from "./test_utils.ts";
import { assert, assertEquals } from "./test_deps.ts";
import { browserTest } from "./test_utils.ts";

Deno.test("basic md table with dollar signs", async () => {
const markdown = `| Fruit Name | Quantity | Unit Cost per Item | Subtotal |
|------------|----------|--------------------|----------|
| Apple | 1 | $1.50 | $1.50 |
| Pear | 2 | $2.00 | $4.00 |
| Orange | 3 | $2.50 | $7.50 |
| Grape | 60 | $0.05 | $3.00 |
| Total | | | $16.00 |`;

const body = render(markdown);
const html = setupHtmlWithCss(body);

await browserTest(html, async (page, address) => {
await page.goto(`${address}`);

await browserTest("basicMarkdownTable", async (page) => {
await page.waitForSelector("table", { timeout: 1000 });
const tableExists = await page.evaluate(() => {
const table = document.querySelector("table");
Expand Down Expand Up @@ -64,7 +50,7 @@ Deno.test("basic md table with dollar signs", async () => {
if (!element) {
return null;
}
const style = window.getComputedStyle(element);
const style = globalThis.getComputedStyle(element);
return style[property];
},
selector,
Expand All @@ -91,3 +77,78 @@ Deno.test("basic md table with dollar signs", async () => {
);
});
});

Deno.test("footnote with style", async () => {
await browserTest("footnotes", async (page) => {
// 1. Test page jump on clicking footnote links
const scrollPositionBefore = await page.evaluate(() => globalThis.scrollY);
await page.click("#footnote-ref-1"); // click the first footnote link. note that we select by id, not href
const scrollPositionAfter = await page.evaluate(() => globalThis.scrollY);
assert(scrollPositionAfter > scrollPositionBefore);

await page.click("#footnote-ref-bignote");
const scrollPositionAfter2 = await page.evaluate(() => globalThis.scrollY);
assert(scrollPositionAfter2 === scrollPositionAfter);

await page.click("#footnote-1 > p > a");
const scrollPositionAfter3 = await page.evaluate(() => globalThis.scrollY);
assert(scrollPositionAfter3 < scrollPositionAfter2);
assert(scrollPositionAfter3 > scrollPositionBefore);

// 2. Verify footnote link styling
const beforeContent = await page.evaluate(() => {
const element = document.querySelector("#footnote-ref-1");
if (element) {
return globalThis.getComputedStyle(element, "::before").content;
}
return null;
});
const afterContent = await page.evaluate(() => {
const element = document.querySelector("#footnote-ref-1");
if (element) {
return globalThis.getComputedStyle(element, "::after").content;
}
return null;
});
assertEquals(beforeContent, '"["');
assertEquals(afterContent, '"]"');

// 3. Check Visibility of "Footnotes" H2
const h2Style = await page.evaluate(() => {
const element = document.querySelector("#footnote-label");
if (element) {
const computedStyle = globalThis.getComputedStyle(element);
return {
position: computedStyle.position,
width: computedStyle.width,
height: computedStyle.height,
overflow: computedStyle.overflow,
clip: computedStyle.clip,
wordWrap: computedStyle.wordWrap,
border: computedStyle.border,
};
}
return null;
});
assert(h2Style);
assertEquals(h2Style.position, "absolute");
assertEquals(h2Style.width, "1px");
assertEquals(h2Style.height, "1px");
assertEquals(h2Style.overflow, "hidden");
assertEquals(h2Style.clip, "rect(0px, 0px, 0px, 0px)");
assertEquals(h2Style.wordWrap, "normal");
assertEquals(h2Style.border, "");

// 4. Verify blue box around the footnote after clicking
await page.click("#footnote-ref-1");
const footnoteStyle = await page.evaluate(() => {
const element = document.querySelector("#footnote-1");
if (element) {
return globalThis.getComputedStyle(element)
.outlineColor;
}
return null;
});
assertEquals(footnoteStyle, "rgb(31, 35, 40)");
});
});
17 changes: 17 additions & 0 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,23 @@ Deno.test("expect console warning from invalid math", () => {
console.warn = originalWarn;
});

Deno.test("render github-slugger not reused", function () {
for (let i = 0; i < 2; i++) {
const html = render("## Hello");
const expected =
`<h2 id="hello"><a class="anchor" aria-hidden="true" tabindex="-1" href="#hello"><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>Hello</h2>`;
assertEquals(html, expected);
}
});

Deno.test("footnotes", () => {
const markdown = Deno.readTextFileSync("./test/fixtures/footnote.md");
const expected = Deno.readTextFileSync("./test/fixtures/footnote.html");

const html = render(markdown);
assertEquals(html, expected);
});

Deno.test(
"custom allowed tags and attributes",
() => {
Expand Down
Loading

0 comments on commit 21e7a9f

Please sign in to comment.