From bb3f424d9f22a8a86e4838db5773c79347806f7e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 8 Oct 2024 07:04:56 -0400 Subject: [PATCH] deduplicate marked --- .../routes/tutorial/[slug]/content.server.ts | 111 ++++++++++++++- .../routes/tutorial/[slug]/markdown.server.js | 130 ------------------ 2 files changed, 109 insertions(+), 132 deletions(-) delete mode 100644 apps/svelte.dev/src/routes/tutorial/[slug]/markdown.server.js diff --git a/apps/svelte.dev/src/routes/tutorial/[slug]/content.server.ts b/apps/svelte.dev/src/routes/tutorial/[slug]/content.server.ts index e182f8bee5..c0f08073b0 100644 --- a/apps/svelte.dev/src/routes/tutorial/[slug]/content.server.ts +++ b/apps/svelte.dev/src/routes/tutorial/[slug]/content.server.ts @@ -1,10 +1,14 @@ +// @ts-expect-error has no types +import PrismJS from 'prismjs'; import { read } from '$app/server'; import { index } from '$lib/server/content'; -import { transform } from './markdown.server'; +import { markedTransform } from '@sveltejs/site-kit/markdown'; import type { Exercise, ExerciseStub, PartStub, Scope } from '$lib/tutorial'; import { error } from '@sveltejs/kit'; import { text_files } from './shared'; import type { Document } from '@sveltejs/site-kit'; +import { escape_html } from '$lib/utils/escape'; +import type { Renderer } from 'marked'; const lookup: Record< string, @@ -87,6 +91,108 @@ async function get(assets: Record, key: string) { : Buffer.from(await response.arrayBuffer()).toString('base64'); } +const languages = { + bash: 'bash', + env: 'bash', + html: 'markup', + svelte: 'svelte', + js: 'javascript', + css: 'css', + diff: 'diff', + ts: 'typescript', + '': '' +}; + +const delimiter_substitutes = { + '+++': ' ', + '---': ' ', + ':::': ' ' +}; + +function highlight_spans(content: string, classname: string) { + return `${content}`; + // return content.replace(/ { + // return ` = { + code: ({ text, lang = '' }) => { + /** @type {Record} */ + const options: Record = {}; + + let source = text + .replace(/\/\/\/ (.+?)(?:: (.+))?\n/gm, (_, key, value) => { + options[key] = value; + return ''; + }) + .replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => { + if (prefix && lang !== 'diff') return match; + + // for no good reason at all, marked replaces tabs with spaces + let tabs = ''; + for (let i = 0; i < spaces.length; i += 4) { + tabs += '\t'; + } + return prefix + tabs; + }) + .replace(/(\+\+\+|---|:::)/g, (_, delimiter: keyof typeof delimiter_substitutes) => { + return delimiter_substitutes[delimiter]; + }) + .replace(/\*\\\//g, '*/'); + + let html = '
'; + + if (options.file) { + html += `${options.file}`; + } + + html += '
'; + + if (lang === 'diff') { + const lines = source.split('\n').map((content) => { + let type = null; + if (/^[\+\-]/.test(content)) { + type = content[0] === '+' ? 'inserted' : 'deleted'; + content = content.slice(1); + } + + return { + type, + content: escape_html(content) + }; + }); + + html += `
${lines
+				.map((line) => {
+					if (line.type) return `${line.content}\n`;
+					return line.content + '\n';
+				})
+				.join('')}
`; + } else { + const plang = languages[lang as keyof typeof languages]; + const highlighted = plang + ? PrismJS.highlight(source, PrismJS.languages[plang], lang) + : escape_html(source); + + html += `
${highlighted}
`; + } + + html += '
'; + + return html + .replace(/ {13}([^ ][^]+?) {13}/g, (_, content) => { + return highlight_spans(content, 'highlight add'); + }) + .replace(/ {11}([^ ][^]+?) {11}/g, (_, content) => { + return highlight_spans(content, 'highlight remove'); + }) + .replace(/ {9}([^ ][^]+?) {9}/g, (_, content) => { + return highlight_spans(content, 'highlight'); + }); + } +}; + export async function load_exercise(slug: string): Promise { if (!(slug in lookup)) { error(404, 'No such tutorial found'); @@ -147,7 +253,8 @@ export async function load_exercise(slug: string): Promise { prev: prev && { slug: prev.slug }, next, markdown: exercise.body, - html: await transform(exercise.body, { + html: await markedTransform(exercise.body, { + ...default_renderer, codespan: ({ text }) => filenames.size > 1 && filenames.has(text) ? `${text}` diff --git a/apps/svelte.dev/src/routes/tutorial/[slug]/markdown.server.js b/apps/svelte.dev/src/routes/tutorial/[slug]/markdown.server.js deleted file mode 100644 index e561b3687e..0000000000 --- a/apps/svelte.dev/src/routes/tutorial/[slug]/markdown.server.js +++ /dev/null @@ -1,130 +0,0 @@ -// @ts-ignore has no types -import PrismJS from 'prismjs'; -import 'prismjs/components/prism-bash.js'; -import 'prismjs/components/prism-diff.js'; -import 'prismjs/components/prism-typescript.js'; -import 'prism-svelte'; -import { Marked } from 'marked'; -import { escape_html } from '$lib/utils/escape.js'; - -const languages = { - bash: 'bash', - env: 'bash', - html: 'markup', - svelte: 'svelte', - js: 'javascript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '' -}; - -const delimiter_substitutes = { - '+++': ' ', - '---': ' ', - ':::': ' ' -}; - -/** - * @param {string} content - * @param {string} classname - */ -function highlight_spans(content, classname) { - return `${content}`; - // return content.replace(/ { - // return `} */ -const default_renderer = { - code: ({ text, lang = '' }) => { - /** @type {Record} */ - const options = {}; - - let source = text - .replace(/\/\/\/ (.+?)(?:: (.+))?\n/gm, (_, key, value) => { - options[key] = value; - return ''; - }) - .replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => { - if (prefix && lang !== 'diff') return match; - - // for no good reason at all, marked replaces tabs with spaces - let tabs = ''; - for (let i = 0; i < spaces.length; i += 4) { - tabs += '\t'; - } - return prefix + tabs; - }) - .replace(/(\+\+\+|---|:::)/g, (_, /** @type {keyof delimiter_substitutes} */ delimiter) => { - return delimiter_substitutes[delimiter]; - }) - .replace(/\*\\\//g, '*/'); - - let html = '
'; - - if (options.file) { - html += `${options.file}`; - } - - html += '
'; - - if (lang === 'diff') { - const lines = source.split('\n').map((content) => { - let type = null; - if (/^[\+\-]/.test(content)) { - type = content[0] === '+' ? 'inserted' : 'deleted'; - content = content.slice(1); - } - - return { - type, - content: escape_html(content) - }; - }); - - html += `
${lines
-				.map((line) => {
-					if (line.type) return `${line.content}\n`;
-					return line.content + '\n';
-				})
-				.join('')}
`; - } else { - const plang = languages[/** @type {keyof languages} */ (lang)]; - const highlighted = plang - ? PrismJS.highlight(source, PrismJS.languages[plang], lang) - : escape_html(source); - - html += `
${highlighted}
`; - } - - html += '
'; - - return html - .replace(/ {13}([^ ][^]+?) {13}/g, (_, content) => { - return highlight_spans(content, 'highlight add'); - }) - .replace(/ {11}([^ ][^]+?) {11}/g, (_, content) => { - return highlight_spans(content, 'highlight remove'); - }) - .replace(/ {9}([^ ][^]+?) {9}/g, (_, content) => { - return highlight_spans(content, 'highlight'); - }); - } -}; - -/** - * @param {string} markdown - * @param {Partial} options - */ -export async function transform(markdown, options) { - const marked = new Marked({ - renderer: { - ...default_renderer, - ...options - } - }); - - return (await marked.parse(markdown)) ?? ''; -}