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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ jobs:
run: |
deno task check:types

- name: Install Chromium
run: deno run -A npm:puppeteer browsers install chrome

- name: Run tests
run: |
deno task test
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
cov_profile
deno.lock
.DS_Store
node_modules/
32 changes: 23 additions & 9 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,46 @@
"nodeModulesDir": "auto",
"imports": {
"emoji": "jsr:@denosaurs/emoji@^0.3.1",
"marked": "npm:marked@^12",
"marked": "npm:marked@^17.0.1",
"github-slugger": "npm:github-slugger@^2.0",
"marked-alert": "npm:marked-alert@^2.0",
"marked-footnote": "npm:marked-footnote@^1.2",
"marked-gfm-heading-id": "npm:marked-gfm-heading-id@^3.1",
"prismjs": "npm:prismjs@^1.29",
"prismjs-yaml": "npm:prismjs@^1.29/components/prism-yaml.js",
"sanitize-html": "npm:sanitize-html@^2.13",
"marked-alert": "npm:marked-alert@^2.1.2",
"marked-footnote": "npm:marked-footnote@^1.4.0",
"marked-gfm-heading-id": "npm:marked-gfm-heading-id@^4.1.3",
"prismjs": "npm:prismjs@^1.30.0",
"prismjs-yaml": "npm:prismjs@^1.30.0/components/prism-yaml.js",
"prismjs-jsx": "npm:prismjs@^1.30.0/components/prism-jsx.js",
"prismjs-typescript": "npm:prismjs@^1.30.0/components/prism-typescript.js",
"prismjs-tsx": "npm:prismjs@^1.30.0/components/prism-tsx.js",
"prismjs-bash": "npm:prismjs@^1.30.0/components/prism-bash.js",
"prismjs-powershell": "npm:prismjs@^1.30.0/components/prism-powershell.js",
"prismjs-json": "npm:prismjs@^1.30.0/components/prism-json.js",
"prismjs-diff": "npm:prismjs@^1.30.0/components/prism-diff.js",
"@astral/astral": "jsr:@astral/astral@0.5.5",
"deno-dom": "jsr:@b-fuze/deno-dom@^0.1.56",
"sanitize-html": "npm:sanitize-html@^2.17.0",
"he": "npm:he@^1.2",
"katex": "npm:katex@^0.16",
"css": "npm:css@^3.0.0",
"@std/assert": "jsr:@std/assert@^1.0"
"@std/assert": "jsr:@std/assert@^1.0.18"
},
"compilerOptions": {
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
},
"tasks": {
"build": "deno run --allow-read --allow-write --allow-net --allow-run --allow-env ./style/patch.ts && deno fmt",
"check:types": "deno check **/*.ts",
"coverage": "rm -rf cov_profile && deno test --allow-read --allow-env --allow-write --allow-run --allow-net --coverage=cov_profile",
"coverage": "rm -rf cov_profile && deno test --allow-sys --allow-read --allow-env --allow-write --allow-run --allow-net --coverage=cov_profile",
"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-sys --allow-read --allow-env --allow-write --allow-run --allow-net"
},
"exclude": [
"./style/node_modules/",
"./style/dist/",
"./style/.parcel-cache/"
],
"fmt": {
"exclude": [
"./test/fixtures/",
Expand Down
20 changes: 8 additions & 12 deletions example/main.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { serve } from "https://deno.land/std@0.192.0/http/server.ts";

import { CSS, KATEX_CSS, render } from "../mod.ts";

import "https://esm.sh/prismjs@1.29.0/components/prism-jsx?no-check&pin=v57";
import "https://esm.sh/prismjs@1.29.0/components/prism-typescript?no-check&pin=v57";
import "https://esm.sh/prismjs@1.29.0/components/prism-tsx?no-check&pin=v57";
import "https://esm.sh/prismjs@1.29.0/components/prism-bash?no-check&pin=v57";
import "https://esm.sh/prismjs@1.29.0/components/prism-powershell?no-check&pin=v57";
import "https://esm.sh/prismjs@1.29.0/components/prism-json?no-check&pin=v57";
import "https://esm.sh/prismjs@1.29.0/components/prism-diff?no-check&pin=v57";
import "prismjs-jsx";
import "prismjs-typescript";
import "prismjs-tsx";
import "prismjs-bash";
import "prismjs-powershell";
import "prismjs-json";
import "prismjs-diff";

const CONTENT_PATH = new URL("./content.md", import.meta.url);

Expand Down Expand Up @@ -57,6 +55,4 @@ async function handler(_req: Request): Promise<Response> {
}
}

serve(handler, {
port: 8001,
});
Deno.serve({ port: 8001 }, handler);
33 changes: 17 additions & 16 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,22 @@ export class Renderer extends Marked.Renderer {
this.#slugger = new GitHubSlugger();
}

override heading(
text: string,
level: 1 | 2 | 3 | 4 | 5 | 6,
raw: string,
): string {
override heading({
tokens,
depth,
text: raw,
}: Marked.Tokens.Heading): string {
const text = this.parser.parseInline(tokens);
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}>\n`;
return `<h${depth} 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${depth}>\n`;
}

override image(src: string, title: string | null, alt: string): string {
return `<img src="${src}" alt="${alt}" title="${title ?? ""}" />`;
override image({ href, title, text }: Marked.Tokens.Image): string {
return `<img src="${href}" alt="${text}" title="${title ?? ""}" />`;
}

override code(code: string, language?: string): string {
override code({ text, lang }: Marked.Tokens.Code): string {
let language = lang;
const isTitleIncluded = language?.match(/\stitle="(.+)"/);
let title = null;
if (isTitleIncluded) {
Expand All @@ -67,23 +69,24 @@ export class Renderer extends Marked.Renderer {
// transform math code blocks into HTML+MathML
// https://github.blog/changelog/2022-06-28-fenced-block-syntax-for-mathematical-expressions/
if (language === "math" && this.allowMath) {
return katex.renderToString(code, { displayMode: true });
return katex.renderToString(text, { displayMode: true });
}
const grammar =
language && Object.hasOwnProperty.call(Prism.languages, language)
? Prism.languages[language]
: undefined;
if (grammar === undefined) {
return `<pre><code class="notranslate">${he.encode(code)}</code></pre>`;
return `<pre><code class="notranslate">${he.encode(text)}</code></pre>`;
}
const html = Prism.highlight(code, grammar, language!);
const html = Prism.highlight(text, grammar, language!);
const titleHtml = title
? `<div class="markdown-code-title">${title}</div>`
: ``;
return `<div class="highlight highlight-source-${language} notranslate">${titleHtml}<pre>${html}</pre></div>`;
}

override link(href: string, title: string | null, text: string): string {
override link({ href, title, tokens }: Marked.Tokens.Link): string {
const text = this.parser.parseInline(tokens);
const titleAttr = title ? ` title="${title}"` : "";
if (href.startsWith("#")) {
return `<a href="${href}"${titleAttr}>${text}</a>`;
Expand Down Expand Up @@ -131,10 +134,8 @@ function mathify(markdown: string) {

function getOpts(opts: RenderOptions) {
return {
baseUrl: opts.baseUrl,
breaks: opts.breaks ?? false,
gfm: true,
mangle: false,
renderer: opts.renderer ? opts.renderer : new Renderer(opts),
async: false,
};
Expand Down Expand Up @@ -383,7 +384,7 @@ function stripTokens(
index += 1;
}

if ("tokens" in token && token.tokens) {
if ("tokens" in token && token.tokens && token.type !== "image") {
stripTokens(token.tokens, sections, token.type === "heading");
}

Expand Down
3 changes: 2 additions & 1 deletion style.ts

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions style/patch.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import $ from "https://deno.land/x/dax@0.36.0/mod.ts";
// deno-lint-ignore no-import-prefix
import $ from "jsr:@david/dax@^0.45.0";
import css from "css";

await $`rm -rf style/node_modules/@primer/primitives`;
await $`rm -rf style/node_modules/@primer/primitives style/.parcel-cache style/dist`;
await $`npm install`.cwd("./style");

const colorVariables = new Set<string>();
const variableRegex = /--[\w-]+/g;

const cwd = $.path("./style");
const markdownScssDir = cwd.join("node_modules/@primer/css/markdown");
const scssFiles = [
cwd.join("main.scss"),
...Array.from(
cwd.expandGlobSync("node_modules/@primer/css/markdown/*.scss"),
).map((e) => e.path),
...Array.from(Deno.readDirSync(markdownScssDir.toString()))
.filter((e) => e.isFile && e.name.endsWith(".scss"))
.map((e) => markdownScssDir.join(e.name)),
];

for (const pathRef of scssFiles) {
Expand Down Expand Up @@ -51,7 +53,7 @@ await $`npx parcel build main.scss --no-source-maps`.cwd("./style").quiet();
// KATEX

$.logStep("Fetching katex styles");
const KATEX_BASE_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist";
const KATEX_BASE_URL = "https://cdn.jsdelivr.net/npm/katex@0.16.28/dist";
let KATEX_CSS = await $.request(`${KATEX_BASE_URL}/katex.min.css`).text();

// Replace url of fonts with a cdn since we aren't packaging these
Expand Down
16 changes: 7 additions & 9 deletions test/server_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ Deno.test({
);
return cell ? cell.textContent?.trim() : null;
},
row,
col,
{ args: [row, col] },
);
};

Expand All @@ -46,7 +45,7 @@ Deno.test({

const getComputedStyle = (
selector: string,
property: keyof CSSStyleDeclaration,
property: string & keyof CSSStyleDeclaration,
) => {
return page.evaluate(
(selector, property) => {
Expand All @@ -57,8 +56,7 @@ Deno.test({
const style = globalThis.getComputedStyle(element);
return style[property];
},
selector,
property,
{ args: [selector, property] },
);
};

Expand Down Expand Up @@ -90,17 +88,17 @@ Deno.test(
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
await (await page.$("#footnote-ref-1"))!.click(); // 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");
await (await page.$("#footnote-ref-bignote"))!.click();
const scrollPositionAfter2 = await page.evaluate(() =>
globalThis.scrollY
);
assert(scrollPositionAfter2 === scrollPositionAfter);

await page.click("#footnote-1 > p > a");
await (await page.$("#footnote-1 > p > a"))!.click();
const scrollPositionAfter3 = await page.evaluate(() =>
globalThis.scrollY
);
Expand Down Expand Up @@ -152,7 +150,7 @@ Deno.test(
assertEquals(h2Style.border, "");

// 4. Verify blue box around the footnote after clicking
await page.click("#footnote-ref-1");
await (await page.$("#footnote-ref-1"))!.click();
const footnoteStyle = await page.evaluate(() => {
const element = document.querySelector("#footnote-1");
if (element) {
Expand Down
25 changes: 18 additions & 7 deletions test/test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { assertEquals, assertStringIncludes } from "@std/assert";
import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.43/deno-dom-wasm.ts";
import { render, Renderer, strip, stripSplitBySections } from "../mod.ts";
import { DOMParser } from "deno-dom";
import {
type Marked,
render,
Renderer,
strip,
stripSplitBySections,
} from "../mod.ts";

Deno.test("Basic markdown", async () => {
const markdown = await Deno.readTextFile("./test/fixtures/basic.md");
Expand Down Expand Up @@ -93,8 +99,9 @@ Deno.test("custom renderer", () => {
const expected = `<h1 id="custom-renderer">hello world</h1>`;

class CustomRenderer extends Renderer {
override heading(text: string, level: 1 | 2 | 3 | 4 | 5 | 6): string {
return `<h${level} id="custom-renderer">${text}</h${level}>`;
override heading(token: Marked.Tokens.Heading): string {
const text = this.parser.parseInline(token.tokens);
return `<h${token.depth} id="custom-renderer">${text}</h${token.depth}>`;
}
}

Expand Down Expand Up @@ -229,9 +236,13 @@ Deno.test("custom allowed classes", async () => {
"./test/fixtures/customAllowedClasses.html",
);
class CustomRenderer extends Renderer {
override list(body: string, ordered: boolean): string {
const type = ordered ? "list-decimal" : "list-disc";
const tag = ordered ? "ol" : "ul";
override list(token: Marked.Tokens.List): string {
let body = "";
for (const item of token.items) {
body += this.listitem(item);
}
const type = token.ordered ? "list-decimal" : "list-disc";
const tag = token.ordered ? "ol" : "ul";
return `<${tag} class="${type}">${body}</${tag}>`;
}
}
Expand Down
8 changes: 3 additions & 5 deletions test/test_utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { default as puppeteer, type Page } from "npm:puppeteer";
import { launch, type Page } from "@astral/astral";
import { CSS, render, type RenderOptions } from "../mod.ts";

type TestCase = {
Expand Down Expand Up @@ -33,14 +33,12 @@ export async function browserTest(
const { serverProcess, address } = await startServer();

try {
const browser = await puppeteer.launch({
const browser = await launch({
args: ["--no-sandbox"],
headless: true,
});

try {
const page = await browser.newPage();
await page.goto(`${address}/${test}`);
const page = await browser.newPage(`${address}/${test}`);
await fn(page);
} finally {
await browser.close();
Expand Down