{data.post.title}
{data.post.description}
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${highlighted}' +
- (escaped ? code : escape(code, true)) +
- '\n' + quote + '\n'; - }, - - html(html) { - return html; - }, - - heading(text, level) { - return '
' + text + '
\n'; - }, - - table(header, body) { - if (body) body = '' + body + ''; - - return '' + text + '';
- },
-
- br() {
- return '' + 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.description}