Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sites/svelte.dev/.prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"singleQuote": true,
"printWidth": 100,
"useTabs": true
"useTabs": true,
"trailingComma": "es5"
}
4 changes: 2 additions & 2 deletions sites/svelte.dev/src/lib/components/ReplWidget.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -64,7 +64,7 @@
const components = process_example(data.files);

repl.set({
components
components,
});
}
});
Expand Down
12 changes: 6 additions & 6 deletions sites/svelte.dev/src/lib/components/ScreenToggle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@
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 {
margin: 0 0.15em;
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>
12 changes: 6 additions & 6 deletions sites/svelte.dev/src/lib/server/blog/marked.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -45,7 +45,7 @@ const prism_languages = {
css: 'css',
diff: 'diff',
ts: 'typescript',
'': ''
'': '',
};

/** @type {Partial<import('marked').Renderer>} */
Expand Down Expand Up @@ -165,7 +165,7 @@ const default_renderer = {

text(text) {
return text;
}
},
};

/**
Expand All @@ -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);
Expand Down
72 changes: 72 additions & 0 deletions sites/svelte.dev/src/lib/server/examples/get-examples.js
Original file line number Diff line number Diff line change
@@ -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,
})),
}));
}
11 changes: 11 additions & 0 deletions sites/svelte.dev/src/lib/server/examples/index.js
Original file line number Diff line number Diff line change
@@ -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;
}
25 changes: 25 additions & 0 deletions sites/svelte.dev/src/lib/server/examples/types.d.ts
Original file line number Diff line number Diff line change
@@ -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[];
81 changes: 81 additions & 0 deletions sites/svelte.dev/src/lib/server/tutorial/get-tutorial-data.js
Original file line number Diff line number Diff line change
@@ -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,
})),
}));
}
89 changes: 89 additions & 0 deletions sites/svelte.dev/src/lib/server/tutorial/index.js
Original file line number Diff line number Diff line change
@@ -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 };
}
Loading