Skip to content

Commit 46fa230

Browse files
Allow titles for code blocks (#127)
* Allow titles for code blocks * Add tests and CSS; fmt * fix --------- Co-authored-by: crowlkats <[email protected]>
1 parent f4ae669 commit 46fa230

File tree

5 files changed

+160
-130
lines changed

5 files changed

+160
-130
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
cov_profile
2-
deno.lock
2+
deno.lock
3+
.DS_Store

deno.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"marked-footnote": "npm:marked-footnote@^1.2",
1111
"marked-gfm-heading-id": "npm:marked-gfm-heading-id@^3.1",
1212
"prismjs": "npm:prismjs@^1.29",
13+
"prismjs-yaml": "npm:prismjs@^1.29/components/prism-yaml.js",
1314
"sanitize-html": "npm:sanitize-html@^2.11",
1415
"he": "npm:he@^1.2",
1516
"katex": "npm:katex@^0.16",
@@ -29,6 +30,11 @@
2930
"test": "deno test --allow-read --allow-env --allow-write --allow-run --allow-net"
3031
},
3132
"fmt": {
32-
"exclude": ["./test/fixtures/alerts.md", "./test/fixtures/lineBreaks.md"]
33+
"exclude": [
34+
"./test/fixtures/alerts.md",
35+
"./test/fixtures/lineBreaks.md",
36+
"./test/fixtures/footnote.md",
37+
"./example/content.md"
38+
]
3339
}
3440
}

mod.ts

+53-38
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { emojify } from "emoji";
2-
import * as Marked from "marked";
32
import GitHubSlugger from "github-slugger";
3+
import he from "he";
4+
import katex from "katex";
5+
import * as Marked from "marked";
46
import markedAlert from "marked-alert";
57
import markedFootnote from "marked-footnote";
68
import { gfmHeadingId } from "marked-gfm-heading-id";
79
import Prism from "prismjs";
810
import sanitizeHtml from "sanitize-html";
9-
import he from "he";
10-
import katex from "katex";
11+
import "prismjs-yaml";
1112

1213
import { CSS, KATEX_CLASSES, KATEX_CSS } from "./style.ts";
1314
export { CSS, KATEX_CSS, Marked };
14-
import "https://esm.sh/[email protected]/components/prism-yaml";
1515

1616
Marked.marked.use(markedAlert());
1717
Marked.marked.use(gfmHeadingId());
@@ -39,11 +39,7 @@ export class Renderer extends Marked.Renderer {
3939
this.#slugger = new GitHubSlugger();
4040
}
4141

42-
heading(
43-
text: string,
44-
level: 1 | 2 | 3 | 4 | 5 | 6,
45-
raw: string,
46-
): string {
42+
heading(text: string, level: 1 | 2 | 3 | 4 | 5 | 6, raw: string): string {
4743
const slug = this.#slugger.slug(raw);
4844
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`;
4945
}
@@ -53,6 +49,13 @@ export class Renderer extends Marked.Renderer {
5349
}
5450

5551
code(code: string, language?: string): string {
52+
const isTitleIncluded = language?.match(/\stitle="(.+)"/);
53+
let title = null;
54+
if (isTitleIncluded) {
55+
language = language!.split(" ")[0];
56+
title = isTitleIncluded[1];
57+
}
58+
5659
// a language of `ts, ignore` should really be `ts`
5760
// and it should be lowercase to ensure it has parity with regular github markdown
5861
language = language?.split(",")?.[0].toLocaleLowerCase();
@@ -70,7 +73,10 @@ export class Renderer extends Marked.Renderer {
7073
return `<pre><code class="notranslate">${he.encode(code)}</code></pre>`;
7174
}
7275
const html = Prism.highlight(code, grammar, language!);
73-
return `<div class="highlight highlight-source-${language} notranslate"><pre>${html}</pre></div>`;
76+
const titleHtml = title
77+
? `<div class="markdown-code-title">${title}</div>`
78+
: ``;
79+
return `<div class="highlight highlight-source-${language} notranslate">${titleHtml}<pre>${html}</pre></div>`;
7480
}
7581

7682
link(href: string, title: string | null, text: string): string {
@@ -153,10 +159,11 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
153159

154160
const marked_opts = getOpts(opts);
155161

156-
const html =
157-
(opts.inline
162+
const html = (
163+
opts.inline
158164
? Marked.marked.parseInline(markdown, marked_opts)
159-
: Marked.marked.parse(markdown, marked_opts)) as string;
165+
: Marked.marked.parse(markdown, marked_opts)
166+
) as string;
160167

161168
if (opts.disableHtmlSanitization) {
162169
return html;
@@ -231,6 +238,7 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
231238
"notranslate",
232239
"markdown-alert",
233240
"markdown-alert-*",
241+
"markdown-code-title",
234242
],
235243
span: [
236244
"token",
@@ -312,18 +320,22 @@ export function render(markdown: string, opts: RenderOptions = {}): string {
312320
annotation: ["encoding"], // Only enabled when math is enabled
313321
details: ["open"],
314322
section: ["data-footnotes"],
315-
input: ["checked", "disabled", {
316-
name: "type",
317-
values: ["checkbox"],
318-
}],
323+
input: [
324+
"checked",
325+
"disabled",
326+
{
327+
name: "type",
328+
values: ["checkbox"],
329+
},
330+
],
319331
};
320332

321333
return sanitizeHtml(html, {
322334
transformTags: {
323335
img: transformMedia,
324336
video: transformMedia,
325337
},
326-
allowedTags: [...defaultAllowedTags, ...opts.allowedTags ?? []],
338+
allowedTags: [...defaultAllowedTags, ...(opts.allowedTags ?? [])],
327339
allowedAttributes: mergeAttributes(
328340
defaultAllowedAttributes,
329341
opts.allowedAttributes ?? {},
@@ -356,14 +368,12 @@ function stripTokens(
356368

357369
for (const token of tokens) {
358370
if (token.type === "heading") {
359-
sections[index].header = sections[index].header.trim().replace(
360-
/\n{3,}/g,
361-
"\n",
362-
);
363-
sections[index].content = sections[index].content.trim().replace(
364-
/\n{3,}/g,
365-
"\n",
366-
);
371+
sections[index].header = sections[index].header
372+
.trim()
373+
.replace(/\n{3,}/g, "\n");
374+
sections[index].content = sections[index].content
375+
.trim()
376+
.replace(/\n{3,}/g, "\n");
367377

368378
sections.push({ header: "", depth: token.depth, content: "" });
369379
index += 1;
@@ -488,20 +498,21 @@ export function stripSplitBySections(
488498
markdown: string,
489499
opts: RenderOptions = {},
490500
): MarkdownSections[] {
491-
markdown = emojify(markdown).replace(BLOCK_MATH_REGEXP, "").replace(
492-
INLINE_MATH_REGEXP,
493-
"",
494-
);
501+
markdown = emojify(markdown)
502+
.replace(BLOCK_MATH_REGEXP, "")
503+
.replace(INLINE_MATH_REGEXP, "");
495504
const tokens = Marked.marked.lexer(markdown, {
496505
...getOpts(opts),
497506
tokenizer: new StripTokenizer(),
498507
});
499508

500-
const sections: MarkdownSections[] = [{
501-
header: "",
502-
depth: 0,
503-
content: "",
504-
}];
509+
const sections: MarkdownSections[] = [
510+
{
511+
header: "",
512+
depth: 0,
513+
content: "",
514+
},
515+
];
505516
stripTokens(tokens, sections, false);
506517

507518
return sections;
@@ -511,7 +522,11 @@ export function stripSplitBySections(
511522
* Strip all markdown syntax to get a plaintext output
512523
*/
513524
export function strip(markdown: string, opts: RenderOptions = {}): string {
514-
return stripSplitBySections(markdown, opts).map((section) =>
515-
section.header + "\n\n" + section.content
516-
).join("\n\n").trim().replace(/\n{3,}/g, "\n") + "\n";
525+
return (
526+
stripSplitBySections(markdown, opts)
527+
.map((section) => section.header + "\n\n" + section.content)
528+
.join("\n\n")
529+
.trim()
530+
.replace(/\n{3,}/g, "\n") + "\n"
531+
);
517532
}

style/main.scss

+14-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
&[align="center"] {
2020
margin: 0 auto;
2121
}
22-
22+
2323
display: inline;
2424
}
2525

@@ -41,15 +41,15 @@
4141
padding-top: 2px;
4242
text-align: center;
4343
}
44-
44+
4545
ol {
4646
list-style: decimal;
4747
}
48-
48+
4949
ul {
5050
list-style: disc;
5151
}
52-
52+
5353
table {
5454
width: fit-content;
5555
}
@@ -123,6 +123,16 @@
123123
}
124124
}
125125

126+
.markdown-body .markdown-code-title {
127+
background-color: var(--bgColor-muted, var(--color-canvas-subtle));
128+
padding: 8px 16px;
129+
border-bottom: 1px solid var(--borderColor-muted,var(--color-border-muted));
130+
margin: 0;
131+
font-size: 85%;
132+
font-weight: bold;
133+
display: block;
134+
}
135+
126136
.sr-only {
127137
position: absolute;
128138
width: 1px;

0 commit comments

Comments
 (0)