From 7809dea4e22c8d769e4ac275fac32bc4eb2b3396 Mon Sep 17 00:00:00 2001 From: Puru Vijay Date: Mon, 3 Apr 2023 16:06:38 +0530 Subject: [PATCH] Redo --- sites/svelte.dev/package-lock.json | 8 +- sites/svelte.dev/package.json | 2 +- .../src/lib/server/blog/get-blog-data.js | 60 ++++++ sites/svelte.dev/src/lib/server/blog/index.js | 141 +++++++------ .../svelte.dev/src/lib/server/blog/marked.js | 187 ------------------ .../svelte.dev/src/lib/server/blog/types.d.ts | 3 + sites/svelte.dev/src/lib/server/docs/index.js | 33 +--- sites/svelte.dev/src/lib/server/faq/index.js | 21 +- .../src/lib/server/markdown/index.js | 13 ++ .../src/lib/server/tutorial/index.js | 17 +- .../src/routes/blog/+page.server.js | 4 +- .../src/routes/blog/[slug]/+page.server.js | 5 +- .../src/routes/blog/[slug]/+page.svelte | 2 +- 13 files changed, 183 insertions(+), 313 deletions(-) create mode 100644 sites/svelte.dev/src/lib/server/blog/get-blog-data.js delete mode 100644 sites/svelte.dev/src/lib/server/blog/marked.js diff --git a/sites/svelte.dev/package-lock.json b/sites/svelte.dev/package-lock.json index 54e5ca61b3..786b5416a6 100644 --- a/sites/svelte.dev/package-lock.json +++ b/sites/svelte.dev/package-lock.json @@ -21,7 +21,7 @@ "@resvg/resvg-js": "^2.4.1", "@sveltejs/adapter-vercel": "^2.4.1", "@sveltejs/kit": "^1.15.0", - "@sveltejs/site-kit": "^3.3.6", + "@sveltejs/site-kit": "^3.3.7", "@sveltejs/vite-plugin-svelte": "^2.0.4", "@types/marked": "^4.0.8", "@types/prismjs": "^1.26.0", @@ -866,9 +866,9 @@ "integrity": "sha512-HBLtfNdLr5Ykl8i8CvJcYjib7zMIJupg4T/omplp3ccpgpiUh26tk71vRG1+a6yMkfbfy0ShoPb9uwNril5cnw==" }, "node_modules/@sveltejs/site-kit": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-3.3.6.tgz", - "integrity": "sha512-MUqHseHHFJOT6AcJYTbUqK7hMy1OkBa231tS4TXkmbgQ8EaFVI2unjs9+WY8m3CXASc4p31givOiKVSqtorbbg==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-3.3.7.tgz", + "integrity": "sha512-VVtI4rs0HcBnFbshx6EzkYEeY1MY9OxtC1qZ64v4Kii0JWzGWlOT8bdBzcQgW2nRHvuUdOafEmy5N+LJJbkO9w==", "dev": true, "peerDependencies": { "@sveltejs/kit": "^1.0.0", diff --git a/sites/svelte.dev/package.json b/sites/svelte.dev/package.json index ea2791886f..f3904a7d7b 100644 --- a/sites/svelte.dev/package.json +++ b/sites/svelte.dev/package.json @@ -29,7 +29,7 @@ "@resvg/resvg-js": "^2.4.1", "@sveltejs/adapter-vercel": "^2.4.1", "@sveltejs/kit": "^1.15.0", - "@sveltejs/site-kit": "^3.3.6", + "@sveltejs/site-kit": "^3.3.7", "@sveltejs/vite-plugin-svelte": "^2.0.4", "@types/marked": "^4.0.8", "@types/prismjs": "^1.26.0", diff --git a/sites/svelte.dev/src/lib/server/blog/get-blog-data.js b/sites/svelte.dev/src/lib/server/blog/get-blog-data.js new file mode 100644 index 0000000000..0474c674a3 --- /dev/null +++ b/sites/svelte.dev/src/lib/server/blog/get-blog-data.js @@ -0,0 +1,60 @@ +// @ts-check +import fs from 'fs'; +import { extract_frontmatter } from '../markdown'; + +const BLOG_NAME_REGEX = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/; +const BASE = '../../site/content/blog'; + +/** @returns {import('./types').BlogData} */ +export function get_blog_data(base = BASE) { + /** @type {import('./types').BlogData} */ + const blog_posts = []; + + for (const file of fs.readdirSync(base).reverse()) { + if (!BLOG_NAME_REGEX.test(file)) continue; + + const { date, date_formatted, slug } = get_date_and_slug(file); + const { metadata, body } = extract_frontmatter(fs.readFileSync(`${base}/${file}`, 'utf-8')); + + blog_posts.push({ + date, + date_formatted, + content: body, + description: metadata.description, + draft: metadata.draft === 'true', + slug, + title: metadata.title, + author: { + name: metadata.author, + url: metadata.authorURL + } + }); + } + + return blog_posts; +} + +/** @param {import('./types').BlogData} blog_data */ +export function get_blog_list(blog_data) { + return blog_data.map(({ slug, date, title, description, draft }) => ({ + slug, + date, + title, + description, + draft + })); +} + +/** @param {string} filename */ +function get_date_and_slug(filename) { + const match = BLOG_NAME_REGEX.exec(filename); + if (!match) throw new Error(`Invalid filename for blog: '${filename}'`); + + const [, date, slug] = match; + const [y, m, d] = date.split('-'); + const date_formatted = `${months[+m - 1]} ${+d} ${y}`; + + return { date, date_formatted, slug }; +} + +const months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '); diff --git a/sites/svelte.dev/src/lib/server/blog/index.js b/sites/svelte.dev/src/lib/server/blog/index.js index 05d0eac90a..801878c785 100644 --- a/sites/svelte.dev/src/lib/server/blog/index.js +++ b/sites/svelte.dev/src/lib/server/blog/index.js @@ -1,75 +1,96 @@ -import fs from 'fs'; -import { extract_frontmatter } from '../markdown'; -import { transform } from './marked'; - -const base = '../../site/content/blog'; +// @ts-check +import { createShikiHighlighter } from 'shiki-twoslash'; +import { SHIKI_LANGUAGE_MAP, escape, transform } from '../markdown'; /** - * @returns {import('./types').BlogPostSummary[]} + * @param {import('./types').BlogData} blog_data + * @param {string} slug */ -export function get_index() { - return fs - .readdirSync(base) - .reverse() - .map((file) => { - if (!file.endsWith('.md')) return; +export async function get_processed_blog_post(blog_data, slug) { + const post = blog_data.find((post) => post.slug === slug); - const { date, slug } = get_date_and_slug(file); + if (!post) return null; - const content = fs.readFileSync(`${base}/${file}`, 'utf-8'); - const { metadata } = extract_frontmatter(content); + const highlighter = await createShikiHighlighter({ theme: 'css-variables' }); - return { - slug, - date, - title: metadata.title, - description: metadata.description, - draft: !!metadata.draft - }; - }); -} + return { + ...post, + content: transform(post.content, { + /** + * @param {string} html + */ + heading(html) { + const title = html + .replace(/<\/?code>/g, '') + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>'); -/** - * @param {string} slug - * @returns {import('./types').BlogPost} - */ -export function get_post(slug) { - for (const file of fs.readdirSync(`${base}`)) { - if (!file.endsWith('.md')) continue; - if (file.slice(11, -3) !== slug) continue; + return title; + }, + code: (source, language) => { + let html = ''; - const { date, date_formatted } = get_date_and_slug(file); + source = source + .replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => { + if (prefix && language !== 'diff') return match; - const content = fs.readFileSync(`${base}/${file}`, 'utf-8'); - const { metadata, body } = extract_frontmatter(content); + // for no good reason at all, marked replaces tabs with spaces + let tabs = ''; + for (let i = 0; i < spaces.length; i += 4) { + tabs += ' '; + } + return prefix + tabs; + }) + .replace(/\*\\\//g, '*/'); - return { - date, - date_formatted, - title: metadata.title, - description: metadata.description, - author: { - name: metadata.author, - url: metadata.authorURL - }, - draft: !!metadata.draft, - content: transform(body) - }; - } -} + if (language === 'diff') { + const lines = source.split('\n').map((content) => { + let type = null; + if (/^[\+\-]/.test(content)) { + type = content[0] === '+' ? 'inserted' : 'deleted'; + content = content.slice(1); + } -/** @param {string} filename */ -function get_date_and_slug(filename) { - const match = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/.exec(filename); - if (!match) throw new Error(`Invalid filename for blog: '${filename}'`); + return { + type, + content: escape(content) + }; + }); - const [, date, slug] = match; - const [y, m, d] = date.split('-'); - const date_formatted = `${months[+m - 1]} ${+d} ${y}`; + html = `
${lines
+						.map((line) => {
+							if (line.type) return `${line.content}\n`;
+							return line.content + '\n';
+						})
+						.join('')}
`; + } else { + html = highlighter.codeToHtml(source, { lang: SHIKI_LANGUAGE_MAP[language] }); - return { date, date_formatted, slug }; -} + html = html + .replace( + /^(\s+)([\s\S]+?)<\/span>\n/gm, + (match, intro_whitespace, content) => { + // we use some CSS trickery to make comments break onto multiple lines while preserving indentation + const lines = (intro_whitespace + content).split('\n'); + return lines + .map((line) => { + const match = /^(\s*)(.*)/.exec(line); + const indent = (match[1] ?? '').replace(/\t/g, ' ').length; -const months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '); + return `${ + line ?? '' + }`; + }) + .join(''); + } + ) + .replace(/\/\*…\*\//g, '…'); + } -function format_date(date) {} + return html; + }, + codespan: (text) => '' + text + '' + }) + }; +} diff --git a/sites/svelte.dev/src/lib/server/blog/marked.js b/sites/svelte.dev/src/lib/server/blog/marked.js deleted file mode 100644 index aad9fe1b85..0000000000 --- a/sites/svelte.dev/src/lib/server/blog/marked.js +++ /dev/null @@ -1,187 +0,0 @@ -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'; - -const escape_test = /[&<>"']/; -const escape_replace = /[&<>"']/g; -const escape_test_no_encode = /[<>"']|&(?!#?\w+;)/; -const escape_replace_no_encode = /[<>"']|&(?!#?\w+;)/g; -const escape_replacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' -}; -const get_escape_replacement = (ch) => escape_replacements[ch]; - -/** - * @param {string} html - * @param {boolean} [encode] - */ -export function escape(html, encode) { - if (encode) { - if (escape_test.test(html)) { - return html.replace(escape_replace, get_escape_replacement); - } - } else { - if (escape_test_no_encode.test(html)) { - return html.replace(escape_replace_no_encode, get_escape_replacement); - } - } - - return html; -} - -const prism_languages = { - bash: 'bash', - env: 'bash', - html: 'markup', - svelte: 'svelte', - js: 'javascript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '' -}; - -/** @type {Partial} */ -const default_renderer = { - code(code, infostring, escaped) { - const lang = (infostring || '').match(/\S*/)[0]; - - const prism_language = prism_languages[lang]; - - if (prism_language) { - const highlighted = PrismJS.highlight(code, PrismJS.languages[prism_language], lang); - return `
${highlighted}
`; - } - - return ( - '
' +
-			(escaped ? code : escape(code, true)) +
-			'
' - ); - }, - - blockquote(quote) { - return '
\n' + quote + '
\n'; - }, - - html(html) { - return html; - }, - - heading(text, level) { - return '' + text + '\n'; - }, - - hr() { - return '
\n'; - }, - - list(body, ordered, start) { - const type = ordered ? 'ol' : 'ul', - startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''; - return '<' + type + startatt + '>\n' + body + '\n'; - }, - - listitem(text) { - return '
  • ' + text + '
  • \n'; - }, - - checkbox(checked) { - return ' '; - }, - - paragraph(text) { - return '

    ' + text + '

    \n'; - }, - - table(header, body) { - if (body) body = '' + body + ''; - - return '\n' + '\n' + header + '\n' + body + '
    \n'; - }, - - tablerow(content) { - return '\n' + content + '\n'; - }, - - tablecell(content, flags) { - const type = flags.header ? 'th' : 'td'; - const tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>'; - return tag + content + '\n'; - }, - - // span level renderer - strong(text) { - return '' + text + ''; - }, - - em(text) { - return '' + text + ''; - }, - - codespan(text) { - return '' + text + ''; - }, - - br() { - return '
    '; - }, - - del(text) { - return '' + text + ''; - }, - - link(href, title, text) { - if (href === null) { - return text; - } - let out = ''; - return out; - }, - - image(href, title, text) { - if (href === null) { - return text; - } - - let out = '' + text + '} renderer - */ -export function transform(markdown, renderer = {}) { - marked.use({ - renderer: { - // we have to jump through these hoops because of marked's API design choices — - // options are global, and merged in confusing ways. You can't do e.g. - // `new Marked(options).parse(markdown)` - ...default_renderer, - ...renderer - } - }); - - return marked(markdown); -} diff --git a/sites/svelte.dev/src/lib/server/blog/types.d.ts b/sites/svelte.dev/src/lib/server/blog/types.d.ts index f64366def6..8f5bf1ca30 100644 --- a/sites/svelte.dev/src/lib/server/blog/types.d.ts +++ b/sites/svelte.dev/src/lib/server/blog/types.d.ts @@ -3,6 +3,7 @@ export interface BlogPost { description: string; date: string; date_formatted: string; + slug: string; author: { name: string; url?: string; @@ -11,6 +12,8 @@ export interface BlogPost { content: string; } +export type BlogData = BlogPost[]; + export interface BlogPostSummary { slug: string; title: string; diff --git a/sites/svelte.dev/src/lib/server/docs/index.js b/sites/svelte.dev/src/lib/server/docs/index.js index b7ab0213c0..6450b381d6 100644 --- a/sites/svelte.dev/src/lib/server/docs/index.js +++ b/sites/svelte.dev/src/lib/server/docs/index.js @@ -1,18 +1,16 @@ import * as fs from 'fs'; import * as path from 'path'; -import { renderCodeToHTML, runTwoSlash, createShikiHighlighter } from 'shiki-twoslash'; -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 { escape, extract_frontmatter, transform } from '../markdown'; +// import 'prism-svelte'; +// import 'prismjs/components/prism-bash.js'; +// import 'prismjs/components/prism-diff.js'; +// import 'prismjs/components/prism-typescript.js'; +import { createShikiHighlighter } from 'shiki-twoslash'; +import { SHIKI_LANGUAGE_MAP, extract_frontmatter, transform } from '../markdown'; // import { render, replace_placeholders } from './render.js'; // import { parse_route_id } from '../../../../../../packages/kit/src/utils/routing.js'; -import ts, { ScriptTarget } from 'typescript'; -import MagicString from 'magic-string'; -import { fileURLToPath } from 'url'; import { createHash } from 'crypto'; +import MagicString from 'magic-string'; +import ts from 'typescript'; /** @param {string} title */ export function slugify(title) { @@ -52,19 +50,6 @@ export const normalizeSlugify = (str) => { const base = '../../site/content/docs/'; -const languages = { - bash: 'bash', - env: 'bash', - html: 'html', - svelte: 'svelte', - sv: 'svelte', - js: 'javascript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '' -}; - /** * @param {string} file */ @@ -124,7 +109,7 @@ export async function read_file(file) { } // TODO: Replace later - html = highlighter.codeToHtml(source, { lang: languages[language] }); + html = highlighter.codeToHtml(source, { lang: SHIKI_LANGUAGE_MAP[language] }); // if (language === 'dts') { // // @ts-ignore diff --git a/sites/svelte.dev/src/lib/server/faq/index.js b/sites/svelte.dev/src/lib/server/faq/index.js index 7a1d4e97ca..84d57f51ca 100644 --- a/sites/svelte.dev/src/lib/server/faq/index.js +++ b/sites/svelte.dev/src/lib/server/faq/index.js @@ -1,19 +1,6 @@ // @ts-check import { createShikiHighlighter } from 'shiki-twoslash'; -import { transform } from '../markdown'; - -const languages = { - bash: 'bash', - env: 'bash', - html: 'svelte', - svelte: 'svelte', - sv: 'svelte', - js: 'javascript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '', -}; +import { SHIKI_LANGUAGE_MAP, transform } from '../markdown'; /** * @param {import('./types').FAQData} faq_data @@ -55,7 +42,7 @@ export async function get_parsed_faq(faq_data) { }) .replace(/\*\\\//g, '*/'); - html = highlighter.codeToHtml(source, { lang: languages[language] }); + html = highlighter.codeToHtml(source, { lang: SHIKI_LANGUAGE_MAP[language] }); html = html .replace( @@ -79,8 +66,8 @@ export async function get_parsed_faq(faq_data) { return html; }, - codespan: (text) => '' + text + '', - }), + codespan: (text) => '' + text + '' + }) }; }) ); diff --git a/sites/svelte.dev/src/lib/server/markdown/index.js b/sites/svelte.dev/src/lib/server/markdown/index.js index 337ea43292..5615bc3d46 100644 --- a/sites/svelte.dev/src/lib/server/markdown/index.js +++ b/sites/svelte.dev/src/lib/server/markdown/index.js @@ -17,6 +17,19 @@ const escapeReplacements = { */ const getEscapeReplacement = (ch) => escapeReplacements[ch]; +export const SHIKI_LANGUAGE_MAP = { + bash: 'bash', + env: 'bash', + html: 'svelte', + svelte: 'svelte', + sv: 'svelte', + js: 'javascript', + css: 'css', + diff: 'diff', + ts: 'typescript', + '': '' +}; + /** * @param {string} html * @param {boolean} encode diff --git a/sites/svelte.dev/src/lib/server/tutorial/index.js b/sites/svelte.dev/src/lib/server/tutorial/index.js index c8bc441d55..3c9d83371e 100644 --- a/sites/svelte.dev/src/lib/server/tutorial/index.js +++ b/sites/svelte.dev/src/lib/server/tutorial/index.js @@ -1,18 +1,5 @@ import { createShikiHighlighter } from 'shiki-twoslash'; -import { transform } from '../markdown'; - -const languages = { - bash: 'bash', - env: 'bash', - html: 'svelte', - svelte: 'svelte', - sv: 'svelte', - js: 'javascript', - css: 'css', - diff: 'diff', - ts: 'typescript', - '': '' -}; +import { SHIKI_LANGUAGE_MAP, transform } from '../markdown'; /** * @param {import('./types').TutorialData} tutorial_data @@ -58,7 +45,7 @@ export async function get_parsed_tutorial(tutorial_data, slug) { }) .replace(/\*\\\//g, '*/'); - html = highlighter.codeToHtml(source, { lang: languages[language] }); + html = highlighter.codeToHtml(source, { lang: SHIKI_LANGUAGE_MAP[language] }); html = html .replace( diff --git a/sites/svelte.dev/src/routes/blog/+page.server.js b/sites/svelte.dev/src/routes/blog/+page.server.js index b3c7e8fbca..5317b05daa 100644 --- a/sites/svelte.dev/src/routes/blog/+page.server.js +++ b/sites/svelte.dev/src/routes/blog/+page.server.js @@ -1,9 +1,9 @@ -import { get_index } from '$lib/server/blog'; +import { get_blog_data, get_blog_list } from '$lib/server/blog/get-blog-data'; export const prerender = true; export async function load() { return { - posts: get_index() + posts: get_blog_list(get_blog_data()) }; } diff --git a/sites/svelte.dev/src/routes/blog/[slug]/+page.server.js b/sites/svelte.dev/src/routes/blog/[slug]/+page.server.js index 05557040f2..5ed1b1b425 100644 --- a/sites/svelte.dev/src/routes/blog/[slug]/+page.server.js +++ b/sites/svelte.dev/src/routes/blog/[slug]/+page.server.js @@ -1,10 +1,11 @@ -import { get_post } from '$lib/server/blog/index.js'; +import { get_processed_blog_post } from '$lib/server/blog'; +import { get_blog_data } from '$lib/server/blog/get-blog-data'; import { error } from '@sveltejs/kit'; export const prerender = true; export async function load({ params }) { - const post = get_post(params.slug); + const post = get_processed_blog_post(get_blog_data(), params.slug); if (!post) { throw error(404); diff --git a/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte b/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte index 51bba4054d..3860149f30 100644 --- a/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte +++ b/sites/svelte.dev/src/routes/blog/[slug]/+page.svelte @@ -18,7 +18,7 @@ -
    +

    {data.post.title}

    {data.post.description}