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/donors.jpg
/src/routes/_components/Supporters/donors.js
.vercel

File diff suppressed because it is too large Load Diff

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

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

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

@ -14,13 +14,13 @@ const escape_replacements = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
"'": '&#39;',
};
const get_escape_replacement = (ch) => escape_replacements[ch];
/**
* @param {string} html
* @param {boolean} encode
* @param {boolean} [encode]
*/
export function escape(html, encode) {
if (encode) {
@ -45,7 +45,7 @@ const prism_languages = {
css: 'css',
diff: 'diff',
ts: 'typescript',
'': ''
'': '',
};
/** @type {Partial<import('marked').Renderer>} */
@ -165,7 +165,7 @@ const default_renderer = {
text(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.
// `new Marked(options).parse(markdown)`
...default_renderer,
...renderer
}
...renderer,
},
});
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/';
/** @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;

@ -181,7 +181,7 @@
}
.content :global(code) {
padding: 0.4rem;
/* padding: 0.4rem; */
margin: 0 0.2rem;
top: -0.1rem;
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';
export const prerender = true;
/** @type {import('./$types').PageLoad} */
export function load() {
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 -->
<script>
import { getContext } from 'svelte';
// @ts-check
import { navigating } from '$app/stores';
import Repl from '@sveltejs/repl';
import ScreenToggle from '$lib/components/ScreenToggle.svelte';
import {
mapbox_setup, // see site/content/examples/15-context/00-context-api
svelteUrl,
} from '../../../config';
import TableOfContents from './_TableOfContents.svelte';
import Repl from '@sveltejs/repl';
import { mapbox_setup, svelteUrl } from '../../../config';
import TableOfContents from './TableOfContents.svelte';
/** @type {import('./$types').PageData} */
export let data;
/** @type {number} */
@ -19,10 +15,8 @@
/** @type {import('@sveltejs/repl').default} */
let repl;
const { sections } = getContext('examples');
const clone = (file) => ({
name: file.name.replace(/.\w+$/, ''),
name: file.filename.replace(/.\w+$/, ''),
type: file.type,
source: file.content,
});
@ -34,7 +28,7 @@
</script>
<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:description" content="Cybernetically enhanced web apps" />
@ -44,7 +38,11 @@
<h1 class="visually-hidden">Examples</h1>
<div class="examples-container" bind:clientWidth={width}>
<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}>
<Repl
bind:this={repl}

@ -15,7 +15,7 @@
<ul class="examples-toc">
{#each sections as section}
<li>
<span class="section-title">{section.name}</span>
<span class="section-title">{section.title}</span>
{#each section.examples as example}
<div class="row" class:active={example.slug === active_section} class:loading={isLoading}>
@ -31,7 +31,7 @@
src="/examples/thumbnails/{example.slug}.jpg"
/>
<span>{example.name}</span>
<span>{example.title}</span>
</a>
{#if example.slug === active_section}
<a bind:this={active_el} href="/repl/{example.slug}" class="repl-link">REPL</a>
@ -109,7 +109,7 @@
}
.thumbnail {
background-color: var(--sk-back-1);
background-color: #fff;
object-fit: contain;
width: 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';
export const prerender = true;
export function load() {
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>
import '@sveltejs/site-kit/styles/code.css';
import { browser } from '$app/environment';
import { getContext } from 'svelte';
import Repl from '@sveltejs/repl';
import ScreenToggle from '$lib/components/ScreenToggle.svelte';
import TableOfContents from './_TableOfContents.svelte';
import TableOfContents from './TableOfContents.svelte';
import { browser } from '$app/environment';
import { mapbox_setup, svelteUrl } from '../../../config.js';
import {
mapbox_setup, // needed for context API tutorial
svelteUrl,
} from '../../../config.js';
import '@sveltejs/site-kit/styles/code.css';
/** @type {import('./$types').PageData} */
export let data;
const { sections } = getContext('tutorial');
/** @type {import('@sveltejs/repl').default} */
let repl;
let prev;
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();
let width = browser ? window.innerWidth : 1000;
let offset = 0;
sections.forEach((section) => {
data.tutorials_list.forEach((section) => {
section.tutorials.forEach((chapter) => {
const obj = {
slug: chapter.slug,
@ -93,11 +94,11 @@
</script>
<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:description" content="{selected.section.name} / {selected.chapter.name}" />
<meta name="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.title} / {selected.chapter.title}" />
</svelte:head>
<svelte:window bind:innerWidth={width} />
@ -106,10 +107,10 @@
<div class="viewport offset-{offset}">
<div class="tutorial-text">
<div class="table-of-contents">
<TableOfContents {sections} slug={data.slug} {selected} />
<TableOfContents sections={data.tutorials_list} slug={data.slug} {selected} />
</div>
<div class="chapter-markup" bind:this={scrollable}>
<div class="chapter-markup content" bind:this={scrollable}>
{@html data.tutorial.content}
<div class="controls">
@ -152,6 +153,16 @@
{/if}
</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>
.tutorial-outer {
position: relative;
@ -265,13 +276,27 @@
top: -0.1em;
}
.chapter-markup :global(:where(pre.language-markup)) {
background-color: var(--sk-code-bg);
color: var(--sk-code-base);
.chapter-markup :global(code) {
/* padding: 0.4rem; */
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;
padding: 1rem;
margin: 0 0 1rem;
font-size: 14px;
}
.controls {

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

Loading…
Cancel
Save