Skip to content

Commit e51ea45

Browse files
authored
deduplicate marked (#284)
1 parent 9fd7b4a commit e51ea45

File tree

2 files changed

+109
-132
lines changed

2 files changed

+109
-132
lines changed

apps/svelte.dev/src/routes/tutorial/[slug]/content.server.ts

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
// @ts-expect-error has no types
2+
import PrismJS from 'prismjs';
13
import { read } from '$app/server';
24
import { index } from '$lib/server/content';
3-
import { transform } from './markdown.server';
5+
import { markedTransform } from '@sveltejs/site-kit/markdown';
46
import type { Exercise, ExerciseStub, PartStub, Scope } from '$lib/tutorial';
57
import { error } from '@sveltejs/kit';
68
import { text_files } from './shared';
79
import type { Document } from '@sveltejs/site-kit';
10+
import { escape_html } from '$lib/utils/escape';
11+
import type { Renderer } from 'marked';
812

913
const lookup: Record<
1014
string,
@@ -87,6 +91,108 @@ async function get(assets: Record<string, string>, key: string) {
8791
: Buffer.from(await response.arrayBuffer()).toString('base64');
8892
}
8993

94+
const languages = {
95+
bash: 'bash',
96+
env: 'bash',
97+
html: 'markup',
98+
svelte: 'svelte',
99+
js: 'javascript',
100+
css: 'css',
101+
diff: 'diff',
102+
ts: 'typescript',
103+
'': ''
104+
};
105+
106+
const delimiter_substitutes = {
107+
'+++': ' ',
108+
'---': ' ',
109+
':::': ' '
110+
};
111+
112+
function highlight_spans(content: string, classname: string) {
113+
return `<span class="${classname}">${content}</span>`;
114+
// return content.replace(/<span class="([^"]+)"/g, (_, classnames) => {
115+
// return `<span class="${classname} ${classnames}"`;
116+
// });
117+
}
118+
119+
const default_renderer: Partial<Renderer> = {
120+
code: ({ text, lang = '' }) => {
121+
/** @type {Record<string, string>} */
122+
const options: Record<string, string> = {};
123+
124+
let source = text
125+
.replace(/\/\/\/ (.+?)(?:: (.+))?\n/gm, (_, key, value) => {
126+
options[key] = value;
127+
return '';
128+
})
129+
.replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => {
130+
if (prefix && lang !== 'diff') return match;
131+
132+
// for no good reason at all, marked replaces tabs with spaces
133+
let tabs = '';
134+
for (let i = 0; i < spaces.length; i += 4) {
135+
tabs += '\t';
136+
}
137+
return prefix + tabs;
138+
})
139+
.replace(/(\+\+\+|---|:::)/g, (_, delimiter: keyof typeof delimiter_substitutes) => {
140+
return delimiter_substitutes[delimiter];
141+
})
142+
.replace(/\*\\\//g, '*/');
143+
144+
let html = '<div class="code-block"><div class="controls">';
145+
146+
if (options.file) {
147+
html += `<span class="filename">${options.file}</span>`;
148+
}
149+
150+
html += '</div>';
151+
152+
if (lang === 'diff') {
153+
const lines = source.split('\n').map((content) => {
154+
let type = null;
155+
if (/^[\+\-]/.test(content)) {
156+
type = content[0] === '+' ? 'inserted' : 'deleted';
157+
content = content.slice(1);
158+
}
159+
160+
return {
161+
type,
162+
content: escape_html(content)
163+
};
164+
});
165+
166+
html += `<pre class="language-diff"><code>${lines
167+
.map((line) => {
168+
if (line.type) return `<span class="${line.type}">${line.content}\n</span>`;
169+
return line.content + '\n';
170+
})
171+
.join('')}</code></pre>`;
172+
} else {
173+
const plang = languages[lang as keyof typeof languages];
174+
const highlighted = plang
175+
? PrismJS.highlight(source, PrismJS.languages[plang], lang)
176+
: escape_html(source);
177+
178+
html += `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
179+
}
180+
181+
html += '</div>';
182+
183+
return html
184+
.replace(/ {13}([^ ][^]+?) {13}/g, (_, content) => {
185+
return highlight_spans(content, 'highlight add');
186+
})
187+
.replace(/ {11}([^ ][^]+?) {11}/g, (_, content) => {
188+
return highlight_spans(content, 'highlight remove');
189+
})
190+
.replace(/ {9}([^ ][^]+?) {9}/g, (_, content) => {
191+
return highlight_spans(content, 'highlight');
192+
});
193+
}
194+
};
195+
90196
export async function load_exercise(slug: string): Promise<Exercise> {
91197
if (!(slug in lookup)) {
92198
error(404, 'No such tutorial found');
@@ -147,7 +253,8 @@ export async function load_exercise(slug: string): Promise<Exercise> {
147253
prev: prev && { slug: prev.slug },
148254
next,
149255
markdown: exercise.body,
150-
html: await transform(exercise.body, {
256+
html: await markedTransform(exercise.body, {
257+
...default_renderer,
151258
codespan: ({ text }) =>
152259
filenames.size > 1 && filenames.has(text)
153260
? `<code data-file="${scope.prefix + text}">${text}</code>`

apps/svelte.dev/src/routes/tutorial/[slug]/markdown.server.js

Lines changed: 0 additions & 130 deletions
This file was deleted.

0 commit comments

Comments
 (0)