Merge remote-tracking branch 'origin/sites' into feat/update-content

pull/8289/head
Puru Vijay 3 years ago
commit 2e2bacbba7

@ -9,3 +9,4 @@
/src/routes/_components/Supporters/contributors.js /src/routes/_components/Supporters/contributors.js
/src/routes/_components/Supporters/donors.jpg /src/routes/_components/Supporters/donors.jpg
/src/routes/_components/Supporters/donors.js /src/routes/_components/Supporters/donors.js
.vercel

@ -21,7 +21,7 @@
"devDependencies": { "devDependencies": {
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.4.1",
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.14.0", "@sveltejs/kit": "^1.12.0",
"@sveltejs/site-kit": "^3.3.6", "@sveltejs/site-kit": "^3.3.6",
"@sveltejs/vite-plugin-svelte": "^2.0.3", "@sveltejs/vite-plugin-svelte": "^2.0.3",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",
@ -1217,9 +1217,9 @@
} }
}, },
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "1.14.0", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.12.0.tgz",
"integrity": "sha512-4e/cZT0z4IppEkqNvMrurGz6VE1gScukFU7XqwTL/yrGJGXHqS9D7RvsOcE1hASsgrMu6w/fKTIhxT5oN0K1Jw==", "integrity": "sha512-hhOtaL3jS7p4A3O34m8RlM+K5OSyrEyFUIh4iqsv6e8BDvupzNSxGa7J9+Gfjb+Z1yZCxjvxJ8Flb2Cj0g8cLg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
@ -4900,9 +4900,9 @@
} }
}, },
"@sveltejs/kit": { "@sveltejs/kit": {
"version": "1.14.0", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.12.0.tgz",
"integrity": "sha512-4e/cZT0z4IppEkqNvMrurGz6VE1gScukFU7XqwTL/yrGJGXHqS9D7RvsOcE1hASsgrMu6w/fKTIhxT5oN0K1Jw==", "integrity": "sha512-hhOtaL3jS7p4A3O34m8RlM+K5OSyrEyFUIh4iqsv6e8BDvupzNSxGa7J9+Gfjb+Z1yZCxjvxJ8Flb2Cj0g8cLg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@sveltejs/vite-plugin-svelte": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^2.0.0",

@ -29,7 +29,7 @@
"devDependencies": { "devDependencies": {
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.4.1",
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.14.0", "@sveltejs/kit": "^1.12.0",
"@sveltejs/site-kit": "^3.3.6", "@sveltejs/site-kit": "^3.3.6",
"@sveltejs/vite-plugin-svelte": "^2.0.3", "@sveltejs/vite-plugin-svelte": "^2.0.3",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",

@ -42,7 +42,7 @@
return { return {
name: file.slice(0, dot), name: file.slice(0, dot),
type: file.slice(dot + 1), type: file.slice(dot + 1),
source source,
}; };
}) })
.filter((x) => x.type === 'svelte' || x.type === 'js') .filter((x) => x.type === 'svelte' || x.type === 'js')
@ -64,7 +64,7 @@
const components = process_example(data.files); const components = process_example(data.files);
repl.set({ repl.set({
components components,
}); });
} }
}); });

@ -20,8 +20,8 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-top: 1px solid var(--second); border-top: 1px solid var(--sk-theme-2);
background-color: white; background-color: var(--sk-back-4);
} }
button { button {
@ -29,15 +29,15 @@
width: 4em; width: 4em;
height: 1em; height: 1em;
padding: 0.3em 0.4em; padding: 0.3em 0.4em;
border-radius: var(--border-r); border-radius: var(--sk-border-radius);
line-height: 1em; line-height: 1em;
box-sizing: content-box; box-sizing: content-box;
color: #888; color: var(--sk-text-3);
border: 1px solid var(--back-light); border: 1px solid var(--sk-back-3);
} }
.selected { .selected {
background-color: var(--prime); background-color: var(--sk-theme-1);
color: white; color: white;
} }
</style> </style>

@ -14,13 +14,13 @@ const escape_replacements = {
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
'"': '&quot;', '"': '&quot;',
"'": '&#39;' "'": '&#39;',
}; };
const get_escape_replacement = (ch) => escape_replacements[ch]; const get_escape_replacement = (ch) => escape_replacements[ch];
/** /**
* @param {string} html * @param {string} html
* @param {boolean} encode * @param {boolean} [encode]
*/ */
export function escape(html, encode) { export function escape(html, encode) {
if (encode) { if (encode) {
@ -45,7 +45,7 @@ const prism_languages = {
css: 'css', css: 'css',
diff: 'diff', diff: 'diff',
ts: 'typescript', ts: 'typescript',
'': '' '': '',
}; };
/** @type {Partial<import('marked').Renderer>} */ /** @type {Partial<import('marked').Renderer>} */
@ -165,7 +165,7 @@ const default_renderer = {
text(text) { text(text) {
return text; return text;
} },
}; };
/** /**
@ -179,8 +179,8 @@ export function transform(markdown, renderer = {}) {
// options are global, and merged in confusing ways. You can't do e.g. // options are global, and merged in confusing ways. You can't do e.g.
// `new Marked(options).parse(markdown)` // `new Marked(options).parse(markdown)`
...default_renderer, ...default_renderer,
...renderer ...renderer,
} },
}); });
return marked(markdown); return marked(markdown);

@ -0,0 +1,72 @@
// @ts-check
import fs from 'node:fs';
const base = '../../site/content/examples/';
/**
* @returns {import('./types').ExamplesData}
*/
export function get_examples_data() {
const examples = [];
for (const subdir of fs.readdirSync(base)) {
// Exclude embeds
if (subdir.endsWith('99-embeds')) continue;
const section = {
title: '', // Initialise with empty
slug: subdir.split('-').slice(1).join('-'),
examples: [],
};
if (!(fs.statSync(`${base}/${subdir}`).isDirectory() || subdir.endsWith('meta.json'))) continue;
if (!subdir.endsWith('meta.json'))
section.title = JSON.parse(fs.readFileSync(`${base}/${subdir}/meta.json`, 'utf-8')).title;
for (const section_dir of fs.readdirSync(`${base}/${subdir}`)) {
const match = /\d{2}-(.+)/.exec(section_dir);
if (!match) continue;
const slug = match[1];
const example_base_dir = `${base}/${subdir}/${section_dir}`;
// Get title for
const example_title = JSON.parse(
fs.readFileSync(`${example_base_dir}/meta.json`, 'utf-8')
).title;
const files = [];
for (const file of fs
.readdirSync(example_base_dir)
.filter((file) => !file.endsWith('meta.json'))) {
files.push({
filename: file,
type: file.split('.').at(-1),
content: fs.readFileSync(`${example_base_dir}/${file}`, 'utf-8'),
});
}
section.examples.push({ title: example_title, slug, files });
}
examples.push(section);
}
return examples;
}
/**
* @param {import('./types').ExamplesData} examples_data
* @returns {import('./types').ExamplesList}
*/
export function get_examples_list(examples_data) {
return examples_data.map((section) => ({
title: section.title,
examples: section.examples.map((example) => ({
title: example.title,
slug: example.slug,
})),
}));
}

@ -0,0 +1,11 @@
/**
* @param {import('./types').ExamplesData} examples_data
* @param {string} slug
*/
export function get_example(examples_data, slug) {
const example = examples_data
.find((section) => section.examples.find((example) => example.slug === slug))
?.examples.find((example) => example.slug === slug);
return example;
}

@ -0,0 +1,25 @@
export type ExamplesData = {
title: string;
slug: string;
examples: {
title: string;
slug: string;
files: {
content: string;
type: 'svelte' | 'js';
filename: string;
}[];
}[];
}[];
export interface Example {
title: string;
slug: string;
}
export interface ExampleSection {
title: string;
examples: Example[];
}
export type ExamplesList = ExampleSection[];

@ -0,0 +1,81 @@
// @ts-check
import fs from 'node:fs';
import { extract_frontmatter } from '../markdown/index.js';
const base = '../../site/content/tutorial/';
/**
* @returns {import('./types').TutorialData}
*/
export function get_tutorial_data() {
const tutorials = [];
for (const subdir of fs.readdirSync(base)) {
const section = {
title: '', // Initialise with empty
slug: subdir.split('-').slice(1).join('-'),
tutorials: [],
};
if (!(fs.statSync(`${base}/${subdir}`).isDirectory() || subdir.endsWith('meta.json'))) continue;
if (!subdir.endsWith('meta.json'))
section.title = JSON.parse(fs.readFileSync(`${base}/${subdir}/meta.json`, 'utf-8')).title;
for (const section_dir of fs.readdirSync(`${base}/${subdir}`)) {
const match = /\d{2}-(.+)/.exec(section_dir);
if (!match) continue;
const slug = match[1];
const tutorial_base_dir = `${base}/${subdir}/${section_dir}`;
// Read the file, get frontmatter
const contents = fs.readFileSync(`${tutorial_base_dir}/text.md`, 'utf-8');
const { metadata, body } = extract_frontmatter(contents);
// Get the contents of the apps.
const completion_states_data = { initial: [], complete: [] };
for (const app_dir of fs.readdirSync(tutorial_base_dir)) {
if (!app_dir.startsWith('app-')) continue;
const app_dir_path = `${tutorial_base_dir}/${app_dir}`;
const app_contents = fs.readdirSync(app_dir_path, 'utf-8');
for (const file of app_contents) {
completion_states_data[app_dir === 'app-a' ? 'initial' : 'complete'].push({
name: file,
type: file.split('.').at(-1),
content: fs.readFileSync(`${app_dir_path}/${file}`, 'utf-8'),
});
}
}
section.tutorials.push({
title: metadata.title,
slug,
content: body,
dir: `${subdir}/${section_dir}`,
...completion_states_data,
});
}
tutorials.push(section);
}
return tutorials;
}
/**
* @param {import('./types').TutorialData} tutorial_data
* @returns {import('./types').TutorialsList}
*/
export function get_tutorial_list(tutorial_data) {
return tutorial_data.map((section) => ({
title: section.title,
tutorials: section.tutorials.map((tutorial) => ({
title: tutorial.title,
slug: tutorial.slug,
})),
}));
}

@ -0,0 +1,89 @@
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',
'': '',
};
/**
* @param {import('./types').TutorialData} tutorial_data
* @param {string} slug
*/
export async function get_parsed_tutorial(tutorial_data, slug) {
const tutorial = tutorial_data
.find(({ tutorials }) => tutorials.find((t) => t.slug === slug))
?.tutorials?.find((t) => t.slug === slug);
if (!tutorial) return null;
const body = tutorial.content;
const highlighter = await createShikiHighlighter({ theme: 'css-variables' });
const content = transform(body, {
/**
* @param {string} html
*/
heading(html) {
const title = html
.replace(/<\/?code>/g, '')
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
return title;
},
code: (source, language) => {
let html = '';
source = source
.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, '*/');
html = highlighter.codeToHtml(source, { lang: languages[language] });
html = html
.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, '…');
return html;
},
codespan: (text) => '<code>' + text + '</code>',
});
return { ...tutorial, content };
}

@ -0,0 +1,24 @@
export type TutorialData = {
title: string;
slug: string;
tutorials: {
title: string;
slug: string;
dir: string;
content: string;
initial: { name: string; type: string; content: string }[];
complete: { name: string; type: string; content: string }[];
}[];
}[];
export interface Tutorial {
title: string;
slug: string;
}
export interface TutorialSection {
title: string;
tutorials: Tutorial[];
}
export type TutorialsList = TutorialSection[];

@ -6,7 +6,6 @@ export const prerender = true;
const base_dir = '../../site/content/docs/'; const base_dir = '../../site/content/docs/';
/** @type {import('./$types').LayoutServerLoad} */
export function load() { export function load() {
const sections = fs.readdirSync(base_dir).map((file) => { const sections = fs.readdirSync(base_dir).map((file) => {
const { title } = extract_frontmatter(fs.readFileSync(`${base_dir}/${file}`, 'utf-8')).metadata; const { title } = extract_frontmatter(fs.readFileSync(`${base_dir}/${file}`, 'utf-8')).metadata;

@ -181,7 +181,7 @@
} }
.content :global(code) { .content :global(code) {
padding: 0.4rem; /* padding: 0.4rem; */
margin: 0 0.2rem; margin: 0 0.2rem;
top: -0.1rem; top: -0.1rem;
background: var(--sk-back-4); background: var(--sk-back-4);

@ -1,6 +0,0 @@
import { PUBLIC_API_BASE } from '$env/static/public';
export async function load({ fetch }) {
const examples = await fetch(`${PUBLIC_API_BASE}/docs/svelte/examples`).then((r) => r.json());
return { examples };
}

@ -1,10 +0,0 @@
<script>
import { setContext } from 'svelte';
/** @type {import('./$types').PageData} */
export let data;
setContext('examples', { sections: data.examples });
</script>
<slot />

@ -1,5 +1,7 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
export const prerender = true;
/** @type {import('./$types').PageLoad} */ /** @type {import('./$types').PageLoad} */
export function load() { export function load() {
throw redirect(301, 'examples/hello-world'); throw redirect(301, 'examples/hello-world');

@ -1,17 +0,0 @@
import { PUBLIC_API_BASE } from '$env/static/public';
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params, setHeaders }) {
const example = await fetch(`${PUBLIC_API_BASE}/docs/svelte/examples/${params.slug}`, {
credentials: 'omit'
});
setHeaders({
'cache-control': 'public, max-age=60'
});
return {
example: await example.json(),
slug: params.slug
};
}

@ -0,0 +1,17 @@
import { get_example } from '$lib/server/examples';
import { get_examples_data, get_examples_list } from '$lib/server/examples/get-examples';
export const prerender = true;
export async function load({ params }) {
const examples_data = get_examples_data();
const examples_list = get_examples_list(examples_data);
const example = get_example(examples_data, params.slug);
return {
examples_list,
example,
slug: params.slug,
};
}

@ -1,16 +1,12 @@
<!-- FIXME sometimes it adds a trailing slash when landing --> <!-- FIXME sometimes it adds a trailing slash when landing -->
<script> <script>
import { getContext } from 'svelte'; // @ts-check
import { navigating } from '$app/stores'; import { navigating } from '$app/stores';
import Repl from '@sveltejs/repl';
import ScreenToggle from '$lib/components/ScreenToggle.svelte'; import ScreenToggle from '$lib/components/ScreenToggle.svelte';
import { import Repl from '@sveltejs/repl';
mapbox_setup, // see site/content/examples/15-context/00-context-api import { mapbox_setup, svelteUrl } from '../../../config';
svelteUrl, import TableOfContents from './TableOfContents.svelte';
} from '../../../config';
import TableOfContents from './_TableOfContents.svelte';
/** @type {import('./$types').PageData} */
export let data; export let data;
/** @type {number} */ /** @type {number} */
@ -19,10 +15,8 @@
/** @type {import('@sveltejs/repl').default} */ /** @type {import('@sveltejs/repl').default} */
let repl; let repl;
const { sections } = getContext('examples');
const clone = (file) => ({ const clone = (file) => ({
name: file.name.replace(/.\w+$/, ''), name: file.filename.replace(/.\w+$/, ''),
type: file.type, type: file.type,
source: file.content, source: file.content,
}); });
@ -34,7 +28,7 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{data.example.name} {data.example.name ? '•' : ''} Svelte Examples</title> <title>{data.example.title} {data.example.title ? '•' : ''} Svelte Examples</title>
<meta name="twitter:title" content="Svelte examples" /> <meta name="twitter:title" content="Svelte examples" />
<meta name="twitter:description" content="Cybernetically enhanced web apps" /> <meta name="twitter:description" content="Cybernetically enhanced web apps" />
@ -44,7 +38,11 @@
<h1 class="visually-hidden">Examples</h1> <h1 class="visually-hidden">Examples</h1>
<div class="examples-container" bind:clientWidth={width}> <div class="examples-container" bind:clientWidth={width}>
<div class="viewport offset-{offset}"> <div class="viewport offset-{offset}">
<TableOfContents {sections} active_section={data.example.slug} isLoading={!!$navigating} /> <TableOfContents
sections={data.examples_list}
active_section={data.example.slug}
isLoading={!!$navigating}
/>
<div class="repl-container" class:loading={$navigating}> <div class="repl-container" class:loading={$navigating}>
<Repl <Repl
bind:this={repl} bind:this={repl}

@ -15,7 +15,7 @@
<ul class="examples-toc"> <ul class="examples-toc">
{#each sections as section} {#each sections as section}
<li> <li>
<span class="section-title">{section.name}</span> <span class="section-title">{section.title}</span>
{#each section.examples as example} {#each section.examples as example}
<div class="row" class:active={example.slug === active_section} class:loading={isLoading}> <div class="row" class:active={example.slug === active_section} class:loading={isLoading}>
@ -31,7 +31,7 @@
src="/examples/thumbnails/{example.slug}.jpg" src="/examples/thumbnails/{example.slug}.jpg"
/> />
<span>{example.name}</span> <span>{example.title}</span>
</a> </a>
{#if example.slug === active_section} {#if example.slug === active_section}
<a bind:this={active_el} href="/repl/{example.slug}" class="repl-link">REPL</a> <a bind:this={active_el} href="/repl/{example.slug}" class="repl-link">REPL</a>
@ -109,7 +109,7 @@
} }
.thumbnail { .thumbnail {
background-color: var(--sk-back-1); background-color: #fff;
object-fit: contain; object-fit: contain;
width: 5rem; width: 5rem;
height: 5rem; height: 5rem;

@ -1,6 +0,0 @@
import { PUBLIC_API_BASE } from '$env/static/public';
export async function load({ fetch }) {
const tutorials = await fetch(`${PUBLIC_API_BASE}/docs/svelte/tutorial`).then((r) => r.json());
return { tutorials };
}

@ -1,10 +0,0 @@
<script>
import { setContext } from 'svelte';
/** @type {import('./$types').PageData} */
export let data;
setContext('tutorial', { sections: data.tutorials });
</script>
<slot />

@ -1,5 +1,7 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
export const prerender = true;
export function load() { export function load() {
throw redirect(301, '/tutorial/basics'); throw redirect(301, '/tutorial/basics');
} }

@ -1,17 +0,0 @@
import { redirect } from '@sveltejs/kit';
import { PUBLIC_API_BASE } from '$env/static/public';
export async function load({ fetch, params, setHeaders }) {
// TODO: Use local data
const tutorial = await fetch(`${PUBLIC_API_BASE}/docs/svelte/tutorial/${params.slug}`);
if (!tutorial.ok) {
throw redirect(301, '/tutorial/basics');
}
setHeaders({
'cache-control': 'public, max-age=60'
});
return { tutorial: await tutorial.json(), slug: params.slug };
}

@ -0,0 +1,20 @@
import { get_parsed_tutorial } from '$lib/server/tutorial';
import { get_tutorial_data, get_tutorial_list } from '$lib/server/tutorial/get-tutorial-data';
import { error } from '@sveltejs/kit';
export const prerender = true;
export async function load({ params }) {
const tutorial_data = get_tutorial_data();
const tutorials_list = get_tutorial_list(tutorial_data);
const tutorial = await get_parsed_tutorial(tutorial_data, params.slug);
if (!tutorial) throw error(404);
return {
tutorials_list,
tutorial,
slug: params.slug,
};
}

@ -1,31 +1,32 @@
<script> <script>
import '@sveltejs/site-kit/styles/code.css';
import { browser } from '$app/environment';
import { getContext } from 'svelte';
import Repl from '@sveltejs/repl'; import Repl from '@sveltejs/repl';
import ScreenToggle from '$lib/components/ScreenToggle.svelte'; import ScreenToggle from '$lib/components/ScreenToggle.svelte';
import TableOfContents from './_TableOfContents.svelte'; import TableOfContents from './TableOfContents.svelte';
import { import { browser } from '$app/environment';
mapbox_setup, // needed for context API tutorial import { mapbox_setup, svelteUrl } from '../../../config.js';
svelteUrl,
} from '../../../config.js';
/** @type {import('./$types').PageData} */ import '@sveltejs/site-kit/styles/code.css';
export let data;
const { sections } = getContext('tutorial'); export let data;
/** @type {import('@sveltejs/repl').default} */
let repl; let repl;
let prev; let prev;
let scrollable; let scrollable;
/** @type {Map<string, {
* slug: string,
* section: import('$lib/server/tutorial/types').TutorialSection,
* chapter: import('$lib/server/tutorial/types').Tutorial,
* prev: { slug: string, section: import('$lib/server/tutorial/types').TutorialSection, chapter: import('$lib/server/tutorial/types').Tutorial }
* next?: { slug: string, section: import('$lib/server/tutorial/types').TutorialSection, chapter: import('$lib/server/tutorial/types').Tutorial }
* }>} */
const lookup = new Map(); const lookup = new Map();
let width = browser ? window.innerWidth : 1000; let width = browser ? window.innerWidth : 1000;
let offset = 0; let offset = 0;
sections.forEach((section) => { data.tutorials_list.forEach((section) => {
section.tutorials.forEach((chapter) => { section.tutorials.forEach((chapter) => {
const obj = { const obj = {
slug: chapter.slug, slug: chapter.slug,
@ -93,11 +94,11 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{selected.section.name} / {selected.chapter.name} • Svelte Tutorial</title> <title>{selected.section.title} / {selected.chapter.title} • Svelte Tutorial</title>
<meta name="twitter:title" content="Svelte tutorial" /> <meta name="twitter:title" content="Svelte tutorial" />
<meta name="twitter:description" content="{selected.section.name} / {selected.chapter.name}" /> <meta name="twitter:description" content="{selected.section.title} / {selected.chapter.title}" />
<meta name="Description" content="{selected.section.name} / {selected.chapter.name}" /> <meta name="Description" content="{selected.section.title} / {selected.chapter.title}" />
</svelte:head> </svelte:head>
<svelte:window bind:innerWidth={width} /> <svelte:window bind:innerWidth={width} />
@ -106,10 +107,10 @@
<div class="viewport offset-{offset}"> <div class="viewport offset-{offset}">
<div class="tutorial-text"> <div class="tutorial-text">
<div class="table-of-contents"> <div class="table-of-contents">
<TableOfContents {sections} slug={data.slug} {selected} /> <TableOfContents sections={data.tutorials_list} slug={data.slug} {selected} />
</div> </div>
<div class="chapter-markup" bind:this={scrollable}> <div class="chapter-markup content" bind:this={scrollable}>
{@html data.tutorial.content} {@html data.tutorial.content}
<div class="controls"> <div class="controls">
@ -152,6 +153,16 @@
{/if} {/if}
</div> </div>
<!-- HACK to prerender -->
<p style="display: none;">
{#each data.tutorials_list as { tutorials }}
{#each tutorials as { slug }}
<!-- svelte-ignore a11y-missing-content -->
<a href="/tutorial/{slug}" />
{/each}
{/each}
</p>
<style> <style>
.tutorial-outer { .tutorial-outer {
position: relative; position: relative;
@ -265,13 +276,27 @@
top: -0.1em; top: -0.1em;
} }
.chapter-markup :global(:where(pre.language-markup)) { .chapter-markup :global(code) {
background-color: var(--sk-code-bg); /* padding: 0.4rem; */
color: var(--sk-code-base); margin: 0 0.2rem;
top: -0.1rem;
background: var(--sk-back-4);
}
.chapter-markup :global(pre) :global(code) {
padding: 0;
margin: 0;
top: 0;
background: transparent;
}
.chapter-markup :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);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 1rem;
margin: 0 0 1rem;
font-size: 14px;
} }
.controls { .controls {

@ -2,6 +2,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { Icon } from '@sveltejs/site-kit/components'; import { Icon } from '@sveltejs/site-kit/components';
/** @type {import('$lib/server/tutorial/types').TutorialsList} */
export let sections; export let sections;
export let slug; export let slug;
export let selected; export let selected;
@ -13,7 +14,6 @@
<nav> <nav>
<a <a
rel="prefetch"
aria-label="Previous tutorial step" aria-label="Previous tutorial step"
class="no-underline" class="no-underline"
href="/tutorial/{(selected.prev || selected).slug}" href="/tutorial/{(selected.prev || selected).slug}"
@ -28,16 +28,16 @@
<span style="position: relative; top: -0.1em; margin: 0 0.5em 0 0" <span style="position: relative; top: -0.1em; margin: 0 0.5em 0 0"
><Icon name="menu" /></span ><Icon name="menu" /></span
> >
{selected.section.name} / {selected.section.title} /
</strong> </strong>
{selected.chapter.name} {selected.chapter.title}
</span> </span>
<select value={slug} on:change={navigate}> <select value={slug} on:change={navigate}>
{#each sections as section, i} {#each sections as section, i}
<optgroup label="{i + 1}. {section.name}"> <optgroup label="{i + 1}. {section.title}">
{#each section.tutorials as chapter, i} {#each section.tutorials as chapter, i}
<option value={chapter.slug}>{String.fromCharCode(i + 97)}. {chapter.name}</option> <option value={chapter.slug}>{String.fromCharCode(i + 97)}. {chapter.title}</option>
{/each} {/each}
</optgroup> </optgroup>
{/each} {/each}
@ -45,7 +45,6 @@
</div> </div>
<a <a
rel="prefetch"
aria-label="Next tutorial step" aria-label="Next tutorial step"
class="no-underline" class="no-underline"
href="/tutorial/{(selected.next || selected).slug}" href="/tutorial/{(selected.next || selected).slug}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -1,3 +1,4 @@
// @ts-check
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */

Loading…
Cancel
Save