Multi-page docs

pull/8253/head
Puru Vijay 4 years ago
parent e5ab5b65ce
commit 3c2af2aff5

@ -22,6 +22,8 @@
"@sveltejs/kit": "^1.3.10",
"@sveltejs/site-kit": "^3.2.1",
"@sveltejs/vite-plugin-svelte": "^2.0.2",
"@types/marked": "^4.0.8",
"@types/prismjs": "^1.26.0",
"degit": "^2.8.4",
"dotenv": "^16.0.3",
"jimp": "^0.16.2",
@ -34,6 +36,8 @@
"satori": "^0.1.2",
"satori-html": "^0.3.2",
"shelljs": "^0.8.5",
"shiki": "^0.14.0",
"shiki-twoslash": "^3.1.0",
"svelte": "^3.55.1",
"svelte-check": "^3.0.3",
"typescript": "^4.9.5",
@ -1340,6 +1344,12 @@
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ=="
},
"node_modules/@types/marked": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz",
"integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==",
"dev": true
},
"node_modules/@types/node": {
"version": "16.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
@ -1351,6 +1361,12 @@
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.4.tgz",
"integrity": "sha512-L5eZmzw89eXBKkiqVBcJfU1QGx9y+wurRIEgt0cuLH0hwNtVUxtx+6cu0R2STwWj468sjXyBYPYDtGclUd1kjQ=="
},
"node_modules/@types/prismjs": {
"version": "1.26.0",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
"integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==",
"dev": true
},
"node_modules/@types/pug": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
@ -1366,6 +1382,35 @@
"@types/node": "*"
}
},
"node_modules/@typescript/twoslash": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript/twoslash/-/twoslash-3.1.0.tgz",
"integrity": "sha512-kTwMUQ8xtAZaC4wb2XuLkPqFVBj2dNBueMQ89NWEuw87k2nLBbuafeG5cob/QEr6YduxIdTVUjix0MtC7mPlmg==",
"dev": true,
"dependencies": {
"@typescript/vfs": "1.3.5",
"debug": "^4.1.1",
"lz-string": "^1.4.4"
}
},
"node_modules/@typescript/twoslash/node_modules/@typescript/vfs": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.3.5.tgz",
"integrity": "sha512-pI8Saqjupf9MfLw7w2+og+fmb0fZS0J6vsKXXrp4/PDXEFvntgzXmChCXC/KefZZS0YGS6AT8e0hGAJcTsdJlg==",
"dev": true,
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/@typescript/vfs": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.3.4.tgz",
"integrity": "sha512-RbyJiaAGQPIcAGWFa3jAXSuAexU4BFiDRF1g3hy7LmRqfNpYlTQWGXjcrOaVZjJ8YkkpuwG0FcsYvtWQpd9igQ==",
"dev": true,
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@ -1377,6 +1422,12 @@
"node": ">=0.4.0"
}
},
"node_modules/ansi-sequence-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz",
"integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==",
"dev": true
},
"node_modules/any-base": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
@ -2369,6 +2420,12 @@
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
"dev": true
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@ -2418,6 +2475,15 @@
"node": ">=10"
}
},
"node_modules/lz-string": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
"integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==",
"dev": true,
"bin": {
"lz-string": "bin/bin.js"
}
},
"node_modules/magic-string": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
@ -3204,6 +3270,47 @@
"node": ">=4"
}
},
"node_modules/shiki": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.0.tgz",
"integrity": "sha512-fb9Fg1Yx/ElVJcTqPQIEOSfn7mSZlrT1W3CkymY08lL2Jsi+t7jPcZzKO1lCsQwlSDuyNhHvolnyA2OI4EgJNg==",
"dev": true,
"dependencies": {
"ansi-sequence-parser": "^1.1.0",
"jsonc-parser": "^3.2.0",
"vscode-oniguruma": "^1.7.0",
"vscode-textmate": "^8.0.0"
}
},
"node_modules/shiki-twoslash": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/shiki-twoslash/-/shiki-twoslash-3.1.0.tgz",
"integrity": "sha512-uDqrTutOIZzyHbo103GsK7Vvc10saK1XCCivnOQ1NHJzgp3FBilEpftGeVzVSMOJs+JyhI7whkvhXV7kXQ5zCg==",
"dev": true,
"dependencies": {
"@typescript/twoslash": "3.1.0",
"@typescript/vfs": "1.3.4",
"shiki": "0.10.1",
"typescript": ">3"
}
},
"node_modules/shiki-twoslash/node_modules/shiki": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz",
"integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==",
"dev": true,
"dependencies": {
"jsonc-parser": "^3.0.0",
"vscode-oniguruma": "^1.6.1",
"vscode-textmate": "5.2.0"
}
},
"node_modules/shiki-twoslash/node_modules/vscode-textmate": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
"dev": true
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@ -3721,6 +3828,18 @@
}
}
},
"node_modules/vscode-oniguruma": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
"integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==",
"dev": true
},
"node_modules/vscode-textmate": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
"dev": true
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
@ -4696,6 +4815,12 @@
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ=="
},
"@types/marked": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz",
"integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==",
"dev": true
},
"@types/node": {
"version": "16.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
@ -4707,6 +4832,12 @@
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.4.tgz",
"integrity": "sha512-L5eZmzw89eXBKkiqVBcJfU1QGx9y+wurRIEgt0cuLH0hwNtVUxtx+6cu0R2STwWj468sjXyBYPYDtGclUd1kjQ=="
},
"@types/prismjs": {
"version": "1.26.0",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
"integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==",
"dev": true
},
"@types/pug": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
@ -4722,11 +4853,48 @@
"@types/node": "*"
}
},
"@typescript/twoslash": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript/twoslash/-/twoslash-3.1.0.tgz",
"integrity": "sha512-kTwMUQ8xtAZaC4wb2XuLkPqFVBj2dNBueMQ89NWEuw87k2nLBbuafeG5cob/QEr6YduxIdTVUjix0MtC7mPlmg==",
"dev": true,
"requires": {
"@typescript/vfs": "1.3.5",
"debug": "^4.1.1",
"lz-string": "^1.4.4"
},
"dependencies": {
"@typescript/vfs": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.3.5.tgz",
"integrity": "sha512-pI8Saqjupf9MfLw7w2+og+fmb0fZS0J6vsKXXrp4/PDXEFvntgzXmChCXC/KefZZS0YGS6AT8e0hGAJcTsdJlg==",
"dev": true,
"requires": {
"debug": "^4.1.1"
}
}
}
},
"@typescript/vfs": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.3.4.tgz",
"integrity": "sha512-RbyJiaAGQPIcAGWFa3jAXSuAexU4BFiDRF1g3hy7LmRqfNpYlTQWGXjcrOaVZjJ8YkkpuwG0FcsYvtWQpd9igQ==",
"dev": true,
"requires": {
"debug": "^4.1.1"
}
},
"acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
},
"ansi-sequence-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz",
"integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==",
"dev": true
},
"any-base": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
@ -5489,6 +5657,12 @@
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
"dev": true
},
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@ -5528,6 +5702,12 @@
"yallist": "^4.0.0"
}
},
"lz-string": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
"integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==",
"dev": true
},
"magic-string": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
@ -6076,6 +6256,49 @@
"rechoir": "^0.6.2"
}
},
"shiki": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.0.tgz",
"integrity": "sha512-fb9Fg1Yx/ElVJcTqPQIEOSfn7mSZlrT1W3CkymY08lL2Jsi+t7jPcZzKO1lCsQwlSDuyNhHvolnyA2OI4EgJNg==",
"dev": true,
"requires": {
"ansi-sequence-parser": "^1.1.0",
"jsonc-parser": "^3.2.0",
"vscode-oniguruma": "^1.7.0",
"vscode-textmate": "^8.0.0"
}
},
"shiki-twoslash": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/shiki-twoslash/-/shiki-twoslash-3.1.0.tgz",
"integrity": "sha512-uDqrTutOIZzyHbo103GsK7Vvc10saK1XCCivnOQ1NHJzgp3FBilEpftGeVzVSMOJs+JyhI7whkvhXV7kXQ5zCg==",
"dev": true,
"requires": {
"@typescript/twoslash": "3.1.0",
"@typescript/vfs": "1.3.4",
"shiki": "0.10.1",
"typescript": ">3"
},
"dependencies": {
"shiki": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz",
"integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==",
"dev": true,
"requires": {
"jsonc-parser": "^3.0.0",
"vscode-oniguruma": "^1.6.1",
"vscode-textmate": "5.2.0"
}
},
"vscode-textmate": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
"dev": true
}
}
},
"simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@ -6400,6 +6623,18 @@
"dev": true,
"requires": {}
},
"vscode-oniguruma": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
"integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==",
"dev": true
},
"vscode-textmate": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
"dev": true
},
"web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",

@ -30,6 +30,8 @@
"@sveltejs/kit": "^1.3.10",
"@sveltejs/site-kit": "^3.2.1",
"@sveltejs/vite-plugin-svelte": "^2.0.2",
"@types/marked": "^4.0.8",
"@types/prismjs": "^1.26.0",
"degit": "^2.8.4",
"dotenv": "^16.0.3",
"jimp": "^0.16.2",
@ -42,6 +44,8 @@
"satori": "^0.1.2",
"satori-html": "^0.3.2",
"shelljs": "^0.8.5",
"shiki": "^0.14.0",
"shiki-twoslash": "^3.1.0",
"svelte": "^3.55.1",
"svelte-check": "^3.0.3",
"typescript": "^4.9.5",

@ -1,4 +1,5 @@
import fs from 'fs';
import { extract_frontmatter } from '../markdown';
import { transform } from './marked';
/**
@ -21,7 +22,7 @@ export function get_index() {
date,
title: metadata.title,
description: metadata.description,
draft: !!metadata.draft
draft: !!metadata.draft,
};
});
}
@ -47,10 +48,10 @@ export function get_post(slug) {
description: metadata.description,
author: {
name: metadata.author,
url: metadata.authorURL
url: metadata.authorURL,
},
draft: !!metadata.draft,
content: transform(body)
content: transform(body),
};
}
}
@ -67,27 +68,6 @@ function get_date_and_slug(filename) {
return { date, date_formatted, slug };
}
/** @param {string} markdown */
function extract_frontmatter(markdown) {
const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown);
const frontmatter = match[1];
const body = markdown.slice(match[0].length);
/** @type {Record<string, string>} */
const metadata = {};
frontmatter.split('\n').forEach((pair) => {
const i = pair.indexOf(':');
metadata[pair.slice(0, i).trim()] = strip_quotes(pair.slice(i + 1).trim());
});
return { metadata, body };
}
function strip_quotes(str) {
if (str[0] === '"' && str[str.length - 1] === '"') return str.slice(1, -1);
return str;
}
const months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ');
function format_date(date) {}

@ -0,0 +1,564 @@
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 { 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';
const base = '../../site/content/docs/';
const languages = {
bash: 'bash',
env: 'bash',
html: 'markup',
svelte: 'svelte',
js: 'javascript',
css: 'css',
diff: 'diff',
ts: 'typescript',
'': '',
};
/**
* @param {string} file
*/
export async function read_file(file) {
const match = /\d{2}-(.+)\.md/.exec(path.basename(file));
if (!match) return null;
// const markdown = replace_placeholders(fs.readFileSync(`${base}/${file}`, 'utf-8'));
const markdown = fs.readFileSync(`${base}/${file}`, 'utf-8');
const highlighter = await createShikiHighlighter({ theme: 'css-variables' });
const { metadata, body } = extract_frontmatter(markdown);
const { content, sections } = parse({
file,
body: generate_ts_from_js(body),
code: (source, language, current) => {
const hash = createHash('sha256');
hash.update(source + language + current);
const digest = hash.digest().toString('base64').replace(/\//g, '-');
// TODO: cache
// if (fs.existsSync(`${snippet_cache}/${digest}.html`)) {
// return fs.readFileSync(`${snippet_cache}/${digest}.html`, 'utf-8');
// }
/** @type {Record<string, string>} */
const options = {};
let html = '';
source = source
.replace(/^\/\/\/ (.+?): (.+)\n/gm, (_, key, value) => {
options[key] = value;
return '';
})
.replace(/^([\-\+])?((?: )+)/gm, (match, prefix = '', spaces) => {
if (prefix && language !== '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 += ' ';
}
return prefix + tabs;
})
.replace(/\*\\\//g, '*/');
let version_class = '';
if (language === 'generated-ts' || language === 'generated-svelte') {
language = language.replace('generated-', '');
version_class = 'ts-version';
} else if (language === 'original-js' || language === 'original-svelte') {
language = language.replace('original-', '');
version_class = 'js-version';
}
// if (language === 'dts') {
// // @ts-ignore
// html = renderCodeToHTML(source, 'ts', { twoslash: false }, {}, highlighter);
// } else if (language === 'js' || language === 'ts') {
// try {
// const injected = [];
// if (
// source.includes('$app/') ||
// source.includes('$service-worker') ||
// source.includes('@sveltejs/kit/')
// ) {
// injected.push(
// `// @filename: ambient-kit.d.ts`,
// `/// <reference types="@sveltejs/kit" />`
// );
// }
// if (source.includes('$env/')) {
// // TODO we're hardcoding static env vars that are used in code examples
// // in the types, which isn't... totally ideal, but will do for now
// injected.push(
// `declare module '$env/dynamic/private' { export const env: Record<string, string> }`,
// `declare module '$env/dynamic/public' { export const env: Record<string, string> }`,
// `declare module '$env/static/private' { export const API_KEY: string }`,
// `declare module '$env/static/public' { export const PUBLIC_BASE_URL: string }`
// );
// }
// if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) {
// const params = parse_route_id(options.file || `+page.${language}`)
// .params.map((param) => `${param.name}: string`)
// .join(', ');
// injected.push(
// `// @filename: $types.d.ts`,
// `import type * as Kit from '@sveltejs/kit';`,
// `export type PageLoad = Kit.Load<{${params}}>;`,
// `export type PageServerLoad = Kit.ServerLoad<{${params}}>;`,
// `export type LayoutLoad = Kit.Load<{${params}}>;`,
// `export type LayoutServerLoad = Kit.ServerLoad<{${params}}>;`,
// `export type RequestHandler = Kit.RequestHandler<{${params}}>;`,
// `export type Action = Kit.Action<{${params}}>;`,
// `export type Actions = Kit.Actions<{${params}}>;`
// );
// }
// // special case — we need to make allowances for code snippets coming
// // from e.g. ambient.d.ts
// if (file.endsWith('30-modules.md')) {
// injected.push('// @errors: 7006 7031');
// }
// // another special case
// if (source.includes('$lib/types')) {
// injected.push(`declare module '$lib/types' { export interface User {} }`);
// }
// if (injected.length) {
// const injected_str = injected.join('\n');
// if (source.includes('// @filename:')) {
// source = source.replace('// @filename:', `${injected_str}\n\n// @filename:`);
// } else {
// source = source.replace(
// /^(?!\/\/ @)/m,
// `${injected_str}\n\n// @filename: index.${language}\n// ---cut---\n`
// );
// }
// }
// const twoslash = runTwoSlash(source, language, {
// defaultCompilerOptions: {
// allowJs: true,
// checkJs: true,
// target: 'es2021',
// },
// });
// html = renderCodeToHTML(
// twoslash.code,
// 'ts',
// { twoslash: true },
// {},
// highlighter,
// twoslash
// );
// } catch (e) {
// console.error(`Error compiling snippet in ${file}`);
// console.error(e.code);
// throw e;
// }
// // we need to be able to inject the LSP attributes as HTML, not text, so we
// // turn &lt; into &amp;lt;
// html = html.replace(
// /<data-lsp lsp='([^']*)'([^>]*)>(\w+)<\/data-lsp>/g,
// (match, lsp, attrs, name) => {
// if (!lsp) return name;
// return `<data-lsp lsp='${lsp.replace(/&/g, '&amp;')}'${attrs}>${name}</data-lsp>`;
// }
// );
// // preserve blank lines in output (maybe there's a more correct way to do this?)
// html = html.replace(/<div class='line'><\/div>/g, '<div class="line"> </div>');
// } else 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);
// }
// return {
// type,
// content: escape(content),
// };
// });
// html = `<pre class="language-diff"><code>${lines
// .map((line) => {
// if (line.type) return `<span class="${line.type}">${line.content}\n</span>`;
// return line.content + '\n';
// })
// .join('')}</code></pre>`;
// } else {
// const plang = languages[language];
// const highlighted = plang
// ? PrismJS.highlight(source, PrismJS.languages[plang], language)
// : source.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
// html = `<pre class='language-${plang}'><code>${highlighted}</code></pre>`;
// }
if (options.file) {
html = `<div class="code-block"><span class="filename">${options.file}</span>${html}</div>`;
}
if (version_class) {
html = html.replace(/class=('|")/, `class=$1${version_class} `);
}
// type_regex.lastIndex = 0;
html = html
// .replace(type_regex, (match, prefix, name) => {
// if (options.link === 'false' || name === current) {
// // we don't want e.g. RequestHandler to link to RequestHandler
// return match;
// }
// const link = `<a href="${type_links.get(name)}">${name}</a>`;
// return `${prefix || ''}${link}`;
// })
.replace(
/^(\s+)<span class="token comment">([\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;
return `<span class="token comment wrapped" style="--indent: ${indent}ch">${
line ?? ''
}</span>`;
})
.join('');
}
)
.replace(/\/\*…\*\//g, '…');
// fs.writeFileSync(`${snippet_cache}/${digest}.html`, html);
return html;
},
codespan: (text) => {
return (
'<code>' +
text +
// text.replace(type_regex, (match, prefix, name) => {
// const link = `<a href="${type_links.get(name)}">${name}</a>`;
// return `${prefix || ''}${link}`;
// }) +
'</code>'
);
},
});
return {
file,
slug: match[1],
title: metadata.title,
content,
sections,
};
}
/** @param {string} title */
export function slugify(title) {
return title
.toLowerCase()
.replace(/&lt;/g, '')
.replace(/&gt;/g, '')
.replace(/[^a-z0-9-$]/g, '-')
.replace(/-{2,}/g, '-')
.replace(/^-/, '')
.replace(/-$/, '');
}
/**
* @param {{
* file: string;
* body: string;
* code: (source: string, language: string, current: string) => string;
* codespan: (source: string) => string;
* }} opts
*/
function parse({ file, body, code, codespan }) {
const headings = [];
/** @type {import('./types').Section[]} */
const sections = [];
/** @type {import('./types').Section} */
let section;
// this is a bit hacky, but it allows us to prevent type declarations
// from linking to themselves
let current = '';
/** @type {string} */
const content = transform(body, {
/**
* @param {string} html
* @param {number} level
*/
heading(html, level) {
const title = html
.replace(/<\/?code>/g, '')
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
current = title;
const normalized = slugify(title);
headings[level - 1] = normalized;
headings.length = level;
const slug = headings.filter(Boolean).join('-');
// TODO: All this will need to change when headings in documentation are restructured to be h2+ instead of h3+ as they are right now
if (level === 3) {
section = {
title,
slug,
sections: [],
};
sections.push(section);
} else if (level === 4 || level === 5) {
(section?.sections ?? sections).push({
title,
slug,
});
} else {
throw new Error(`Unexpected <h${level}> in ${file}`);
}
return `<h${level} id="${slug}">${html}<a href="#${slug}" class="permalink"><span class="visually-hidden">permalink</span></a></h${level}>`;
},
code: (source, language) => code(source, language, current),
codespan,
});
return {
sections,
content,
};
}
export function generate_ts_from_js(markdown) {
return markdown
.replaceAll(/```js\n([\s\S]+?)\n```/g, (match, code) => {
if (!code.includes('/// file:')) {
// No named file -> assume that the code is not meant to be shown in two versions
return match;
}
if (code.includes('/// file: svelte.config.js')) {
// svelte.config.js has no TS equivalent
return match;
}
const ts = convert_to_ts(code);
if (!ts) {
// No changes -> don't show TS version
return match;
}
return match.replace('js', 'original-js') + '\n```generated-ts\n' + ts + '\n```';
})
.replaceAll(/```svelte\n([\s\S]+?)\n```/g, (match, code) => {
if (!code.includes('/// file:')) {
// No named file -> assume that the code is not meant to be shown in two versions
return match;
}
// Assumption: no context="module" blocks
const script = code.match(/<script>([\s\S]+?)<\/script>/);
if (!script) return match;
const [outer, inner] = script;
const ts = convert_to_ts(inner, '\t', '\n');
if (!ts) {
// No changes -> don't show TS version
return match;
}
return (
match.replace('svelte', 'original-svelte') +
'\n```generated-svelte\n' +
code.replace(outer, `<script lang="ts">${ts}</script>`) +
'\n```'
);
});
}
/**
* Transforms a JS code block into a TS code block by turning JSDoc into type annotations.
* Due to pragmatism only the cases currently used in the docs are implemented.
* @param {string} js_code
* @param {string} [indent]
* @param {string} [offset]
* */
function convert_to_ts(js_code, indent = '', offset = '') {
js_code = js_code
.replaceAll('// @filename: index.js', '// @filename: index.ts')
.replace(/(\/\/\/ .+?\.)js/, '$1ts')
// *\/ appears in some JsDoc comments in d.ts files due to the JSDoc-in-JSDoc problem
.replace(/\*\\\//g, '*/');
const ast = ts.createSourceFile(
'filename.ts',
js_code,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS
);
const code = new MagicString(js_code);
const imports = new Map();
function walk(node) {
// @ts-ignore
if (node.jsDoc) {
// @ts-ignore
for (const comment of node.jsDoc) {
let modified = false;
for (const tag of comment.tags ?? []) {
if (ts.isJSDocTypeTag(tag)) {
const [name, generics] = get_type_info(tag);
if (ts.isFunctionDeclaration(node)) {
const is_export = node.modifiers?.some(
(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword
)
? 'export '
: '';
const is_async = node.modifiers?.some(
(modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword
);
code.overwrite(
node.getStart(),
node.name.getEnd(),
`${is_export ? 'export ' : ''}const ${node.name.getText()} = (${
is_async ? 'async ' : ''
}`
);
code.appendLeft(node.body.getStart(), '=> ');
const type = generics !== undefined ? `${name}${generics}` : name;
code.appendLeft(node.body.getEnd(), `) satisfies ${type};`);
modified = true;
} else if (
ts.isVariableStatement(node) &&
node.declarationList.declarations.length === 1
) {
const variable_statement = node.declarationList.declarations[0];
if (variable_statement.name.getText() === 'actions') {
code.appendLeft(variable_statement.getEnd(), ` satisfies ${name}`);
} else {
code.appendLeft(variable_statement.name.getEnd(), `: ${name}`);
}
modified = true;
} else {
throw new Error('Unhandled @type JsDoc->TS conversion: ' + js_code);
}
} else if (ts.isJSDocParameterTag(tag) && ts.isFunctionDeclaration(node)) {
if (node.parameters.length !== 1) {
throw new Error(
'Unhandled @type JsDoc->TS conversion; needs more params logic: ' + node.getText()
);
}
const [name] = get_type_info(tag);
code.appendLeft(node.parameters[0].getEnd(), `: ${name}`);
modified = true;
}
}
if (modified) {
code.overwrite(comment.getStart(), comment.getEnd(), '');
}
}
}
ts.forEachChild(node, walk);
}
walk(ast);
if (imports.size) {
const import_statements = Array.from(imports.entries())
.map(([from, names]) => {
return `${indent}import type { ${Array.from(names).join(', ')} } from '${from}';`;
})
.join('\n');
const idxOfLastImport = [...ast.statements]
.reverse()
.find((statement) => ts.isImportDeclaration(statement))
?.getEnd();
const insertion_point = Math.max(
idxOfLastImport ? idxOfLastImport + 1 : 0,
js_code.includes('---cut---')
? js_code.indexOf('\n', js_code.indexOf('---cut---')) + 1
: js_code.includes('/// file:')
? js_code.indexOf('\n', js_code.indexOf('/// file:')) + 1
: 0
);
code.appendLeft(insertion_point, offset + import_statements + '\n');
}
const transformed = code.toString();
return transformed === js_code ? undefined : transformed.replace(/\n\s*\n\s*\n/g, '\n\n');
/** @param {ts.JSDocTypeTag | ts.JSDocParameterTag} tag */
function get_type_info(tag) {
const type_text = tag.typeExpression.getText();
let name = type_text.slice(1, -1); // remove { }
const import_match = /import\('(.+?)'\)\.(\w+)(<{?[\n\* \w:;,]+}?>)?/.exec(type_text);
if (import_match) {
const [, from, _name, generics] = import_match;
name = _name;
const existing = imports.get(from);
if (existing) {
existing.add(name);
} else {
imports.set(from, new Set([name]));
}
if (generics !== undefined) {
return [
name,
generics
.replaceAll('*', '') // get rid of JSDoc asterisks
.replace(' }>', '}>'), // unindent closing brace
];
}
}
return [name];
}
}

@ -0,0 +1,13 @@
export interface Section {
title: string;
slug: string;
sections?: Section[];
}
export interface Type {
name: string;
comment: string;
snippet: string;
bullets: string[];
children: Type[];
}

@ -0,0 +1,191 @@
import { marked } from 'marked';
const escapeTest = /[&<>"']/;
const escapeReplace = /[&<>"']/g;
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
const escapeReplacements = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
};
/**
* @param {keyof typeof escapeReplacements} ch
*/
const getEscapeReplacement = (ch) => escapeReplacements[ch];
/**
* @param {string} html
* @param {boolean} encode
*/
export function escape(html, encode = false) {
if (encode) {
if (escapeTest.test(html)) {
return html.replace(escapeReplace, getEscapeReplacement);
}
} else {
if (escapeTestNoEncode.test(html)) {
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
}
}
return html;
}
/** @type {Partial<import('marked').Renderer>} */
const default_renderer = {
code(code, infostring, escaped) {
const lang = (infostring || '').match(/\S*/)[0];
code = code.replace(/\n$/, '') + '\n';
if (!lang) {
return '<pre><code>' + (escaped ? code : escape(code, true)) + '</code></pre>\n';
}
return (
'<pre><code class="language-' +
escape(lang, true) +
'">' +
(escaped ? code : escape(code, true)) +
'</code></pre>\n'
);
},
blockquote(quote) {
return '<blockquote>\n' + quote + '</blockquote>\n';
},
html(html) {
return html;
},
heading(text, level) {
return '<h' + level + '>' + text + '</h' + level + '>\n';
},
hr() {
return '<hr>\n';
},
list(body, ordered, start) {
const type = ordered ? 'ol' : 'ul',
startatt = ordered && start !== 1 ? ' start="' + start + '"' : '';
return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
},
listitem(text) {
return '<li>' + text + '</li>\n';
},
checkbox(checked) {
return '<input ' + (checked ? 'checked="" ' : '') + 'disabled="" type="checkbox"' + '' + '> ';
},
paragraph(text) {
return '<p>' + text + '</p>\n';
},
table(header, body) {
if (body) body = '<tbody>' + body + '</tbody>';
return '<table>\n' + '<thead>\n' + header + '</thead>\n' + body + '</table>\n';
},
tablerow(content) {
return '<tr>\n' + content + '</tr>\n';
},
tablecell(content, flags) {
const type = flags.header ? 'th' : 'td';
const tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>';
return tag + content + '</' + type + '>\n';
},
// span level renderer
strong(text) {
return '<strong>' + text + '</strong>';
},
em(text) {
return '<em>' + text + '</em>';
},
codespan(text) {
return '<code>' + text + '</code>';
},
br() {
return '<br>';
},
del(text) {
return '<del>' + text + '</del>';
},
link(href, title, text) {
if (href === null) {
return text;
}
let out = '<a href="' + escape(href) + '"';
if (title) {
out += ' title="' + title + '"';
}
out += '>' + text + '</a>';
return out;
},
image(href, title, text) {
if (href === null) {
return text;
}
let out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += '>';
return out;
},
text(text) {
return text;
},
};
/**
* @param {string} markdown
* @param {Partial<import('marked').Renderer>} 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);
}
/** @param {string} markdown */
export function extract_frontmatter(markdown) {
const match = /---\r?\n([\s\S]+?)\r?\n---/.exec(markdown);
const frontmatter = match[1];
const body = markdown.slice(match[0].length);
/** @type {Record<string, string>} */
const metadata = {};
frontmatter.split('\n').forEach((pair) => {
const i = pair.indexOf(':');
metadata[pair.slice(0, i).trim()] = pair.slice(i + 1).trim();
});
return { metadata, body };
}

@ -0,0 +1,23 @@
import { extract_frontmatter } from '$lib/server/markdown';
import fs from 'fs';
import { base } from '$app/paths';
export const prerender = true;
const base_dir = '../../site/content/docs/';
/** @type {import('./$types').LayoutServerLoad} */
export function load() {
const sections = fs.readdirSync(base_dir).map((file) => {
const { title } = extract_frontmatter(fs.readFileSync(`${base_dir}/${file}`, 'utf-8')).metadata;
return {
title,
path: `${base}/docs/${file.slice(3, -3)}`,
};
});
return {
sections,
};
}

@ -0,0 +1,334 @@
<script>
import Contents from './Contents.svelte';
/** @type {import('./$types').LayoutServerData}*/
export let data;
</script>
<div class="container">
<div class="page content">
<slot />
</div>
<div class="toc-container">
<Contents contents={data.sections} />
</div>
</div>
<style>
.container {
--sidebar-menu-width: 20rem;
--sidebar-width: var(--sidebar-menu-width);
--ts-toggle-height: 4.2rem;
}
.page {
--on-this-page-display: none;
padding: var(--sk-page-padding-top) var(--sk-page-padding-side);
}
.page :global(hr) {
display: none;
}
.content {
width: 100%;
margin: 0;
padding: var(--sk-page-padding-top) var(--sk-page-padding-side);
tab-size: 2;
-moz-tab-size: 2;
}
@media (min-width: 832px) {
/* can't use vars in @media :( */
.content {
padding-left: calc(var(--sidebar-width) + var(--sk-page-padding-side));
}
}
.content :global(h1) {
font-size: 3.2rem;
margin: 0 0 0.5em 0;
}
.content :global(h2) {
margin-top: 8rem;
padding: 2rem 1.6rem 4rem 0.2rem;
border-top: 2px solid #ddd;
line-height: 1;
font-size: var(--sk-text-m);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.content :global(section):first-of-type > :global(h2) {
margin-top: 0;
}
.content :global(h4) {
margin: 2em 0 1em 0;
}
.content :global(.offset-anchor) {
position: relative;
display: block;
top: calc(-1 * var(--sk-page-padding-top));
width: 0;
height: 0;
}
.content :global(.anchor) {
position: absolute;
display: block;
background: url(../icons/link.svg) 0 50% no-repeat;
background-size: 1em 1em;
width: 1.4em;
height: 1em;
left: -1.3em;
bottom: 0.3rem;
opacity: 0;
transition: opacity 0.2s;
user-select: none;
}
.content :global(h2) :global(.anchor) {
bottom: 4rem;
}
.content :global(h3) :global(.anchor) {
bottom: 1rem;
}
@media (min-width: 400px) {
.content :global(h1) {
font-size: 4.2rem;
}
}
@media (min-width: 768px) {
.content :global(h1) {
font-size: 5.4rem;
}
.content :global(.anchor:focus),
.content :global(h2):hover :global(.anchor),
.content :global(h3):hover :global(.anchor),
.content :global(h4):hover :global(.anchor),
.content :global(h5):hover :global(.anchor),
.content :global(h6):hover :global(.anchor) {
opacity: 1;
}
}
.content :global(h3),
.content :global(h3 > code) {
margin: 6.4rem 0 1rem 0;
padding: 0 0 1rem 0;
color: var(--sk-text-2);
max-width: var(--sk-line-max-width);
border-bottom: 1px solid #ddd;
background: transparent;
line-height: 1;
}
.content :global(h3):first-child {
border: none;
margin: 0;
}
/* avoid doubled border-top */
.content :global(h3 > code) {
border-radius: 0 0 0 0;
border: none;
font-size: inherit;
}
.content :global(h4),
.content :global(h4 > code) {
font-family: inherit;
font-weight: 600;
font-size: 2.4rem;
color: var(--sk-theme-2);
margin: 6.4rem 0 1.6rem 0;
padding-left: 0;
background: transparent;
line-height: 1;
padding-top: 0;
top: 0;
}
.content :global(h4::before) {
display: inline;
content: ' ';
block-size: var(--sk-nav-height);
margin-block-start: calc(-1 * var(--sk-nav-height));
}
.content :global(h4 > em) {
opacity: 0.7;
}
.content :global(h4 > .anchor) {
top: 0.05em;
}
.content :global(h5) {
font-size: 2.4rem;
margin: 2em 0 0.5em 0;
}
.content :global(code) {
padding: 0.4rem;
margin: 0 0.2rem;
top: -0.1rem;
background: var(--sk-back-4);
}
.content :global(pre) :global(code) {
padding: 0;
margin: 0;
top: 0;
background: transparent;
}
.content :global(pre) {
margin: 0 0 2rem 0;
width: 100%;
max-width: var(--sk-line-max-width);
padding: 1rem 1rem;
box-shadow: inset 1px 1px 6px hsla(205.7, 63.6%, 30.8%, 0.06);
}
.content :global(.icon) {
width: 2rem;
height: 2rem;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
.content :global(table) {
margin: 0 0 2em 0;
}
.content :global(section) :global(p) {
max-width: var(--sk-line-max-width);
margin: 1em 0;
}
.content :global(small) {
font-size: var(--sk-text-s);
float: right;
pointer-events: all;
color: var(--sk-theme-1);
cursor: pointer;
}
.content :global(blockquote) {
color: var(--sk-text-1);
background-color: rgba(255, 62, 0, 0.1);
border-left: 4px solid var(--sk-theme-1-variant);
padding: 1rem;
}
.content :global(blockquote) :global(:first-child) {
margin-top: 0;
}
.content :global(blockquote) :global(:last-child) {
margin-bottom: 0;
}
.content :global(blockquote) :global(code) {
background: var(--sk-code-bg);
}
.content :global(section) :global(a):hover {
text-decoration: underline;
}
.content :global(section) :global(a) :global(code) {
color: inherit;
background: rgba(255, 62, 0, 0.1) !important;
}
/* this replaces the offset-anchor hack, which we should remove from this CSS
once https://github.com/sveltejs/action-deploy-docs/issues/1 is closed */
.content :global(h2[id]),
.content :global(h3[id]) {
padding-top: 10rem;
margin-top: -2rem;
border-top: none;
}
.content :global(h2[id])::after {
content: '';
position: absolute;
width: 100%;
left: 0;
top: 8rem;
height: 2px;
background: #ddd;
}
.toc-container {
background: var(--sk-back-3);
}
/*
.ts-toggle {
width: 100%;
border-top: 1px solid var(--sk-back-4);
background-color: var(--sk-back-3);
} */
@media (min-width: 832px) {
.toc-container {
width: var(--sidebar-width);
height: calc(100vh - var(--sk-nav-height) - var(--ts-toggle-height));
position: fixed;
left: 0;
top: var(--sk-nav-height);
overflow-x: hidden;
overflow-y: auto;
}
.toc-container::before {
content: '';
position: fixed;
width: 0;
height: 100%;
top: 0;
left: calc(var(--sidebar-width) - 1px);
border-right: 1px solid var(--sk-back-5);
}
.page {
padding-left: calc(var(--sidebar-width) + var(--sk-page-padding-side));
}
/* .ts-toggle {
position: fixed;
width: var(--sidebar-width);
bottom: 0;
z-index: 1;
margin-right: 0;
border-right: 1px solid var(--sk-back-5);
} */
}
@media (min-width: 1200px) {
.container {
--sidebar-width: max(20rem, 18vw);
}
.page {
--on-this-page-display: block;
padding: var(--sk-page-padding-top) calc(var(--sidebar-width) + var(--sk-page-padding-side));
margin: 0 auto;
max-width: var(--sk-line-max-width);
box-sizing: content-box;
}
}
</style>

@ -0,0 +1,137 @@
<script>
import { page } from '$app/stores';
/** @type {{title: string, path: string}[]} */
export let contents;
</script>
<nav aria-label="Docs">
<ul class="sidebar">
{#each contents as { title, path }}
<li>
<a
data-sveltekit-preload-data
class="page"
class:active={path === $page.url.pathname}
href={path}
>
{title}
</a>
</li>
{/each}
</ul>
</nav>
<style>
nav {
top: 0;
left: 0;
color: var(--sk-text-3);
display: grid;
justify-content: center;
}
.sidebar {
padding: var(--sk-page-padding-top) 0 var(--sk-page-padding-top) 3.2rem;
font-family: var(--sk-font);
height: 100%;
bottom: auto;
width: 100%;
columns: 2;
margin: 0;
}
li {
display: block;
line-height: 1.2;
margin: 0;
margin-bottom: 1rem;
}
li:last-child {
margin-bottom: 0;
}
a {
position: relative;
transition: color 0.2s;
border-bottom: none;
padding: 0;
color: var(--sk-text-3);
user-select: none;
}
.page {
display: block;
font-size: 1.6rem;
font-family: var(--sk-font);
padding-bottom: 0.6em;
}
.active {
font-weight: 700;
color: var(--sk-text-1);
}
@media (min-width: 600px) {
.sidebar {
columns: 2;
padding-left: var(--sk-page-padding-side);
padding-right: var(--sk-page-padding-side);
}
}
@media (min-width: 700px) {
.sidebar {
/* columns: 3; */
}
}
@media (min-width: 832px) {
.sidebar {
columns: 1;
padding-left: 3.2rem;
padding-right: 0;
width: var(--sidebar-menu-width);
margin: 0 0 0 auto;
}
nav {
min-height: calc(100vh - var(--ts-toggle-height));
}
nav::after {
content: '';
position: fixed;
left: 0;
bottom: var(--ts-toggle-height);
width: calc(var(--sidebar-width) - 1px);
height: 2em;
pointer-events: none;
background: linear-gradient(
to bottom,
hsla(var(--sk-back-3-hsl), 0) 0%,
hsla(var(--sk-back-3-hsl), 0.7) 50%,
hsl(var(--sk-back-3-hsl)) 100%
);
background-repeat: no-repeat;
background-size: calc(100% - 3rem) 100%; /* cover text but not scrollbar */
}
.active::after {
--size: 1rem;
content: '';
position: absolute;
width: var(--size);
height: var(--size);
top: -0.1rem;
right: calc(-0.5 * var(--size));
background-color: var(--sk-back-1);
border-left: 1px solid var(--sk-back-5);
border-bottom: 1px solid var(--sk-back-5);
transform: translateY(0.2rem) rotate(45deg);
z-index: 2;
}
}
</style>

@ -0,0 +1,24 @@
import fs from 'fs';
// import { read_file } from '$lib/server/docs';
import { error } from '@sveltejs/kit';
import { read_file } from '$lib/server/docs';
export const prerender = true;
const base = '../../site/content/docs/';
/**
* ASSUMPTION FOR FUTURE: This assumes the directory structure of docs is flat. AKA, no nested folders
*/
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
for (const file of fs.readdirSync(`${base}`)) {
if (file.slice(3, -3) === params.slug) {
return {
page: await read_file(file),
};
}
}
throw error(404);
}

@ -0,0 +1,8 @@
<script>
/** @type {import('./$types').PageData}*/
export let data;
// $: console.log(data.page.content);
</script>
{@html data.page.content}
Loading…
Cancel
Save