diff --git a/sites/svelte.dev/.env b/sites/svelte.dev/.env index ef80c3ff5d..6dfd293254 100644 --- a/sites/svelte.dev/.env +++ b/sites/svelte.dev/.env @@ -13,7 +13,5 @@ GITHUB_CLIENT_SECRET= SUPABASE_URL=https://kpaaohfbmxvespqoqdzp.supabase.co SUPABASE_KEY= -PUBLIC_API_BASE="https://api.svelte.dev" - # client-side VITE_MAPBOX_ACCESS_TOKEN= diff --git a/sites/svelte.dev/src/lib/server/faq/get-faq.js b/sites/svelte.dev/src/lib/server/faq/get-faq.js new file mode 100644 index 0000000000..c7e074830e --- /dev/null +++ b/sites/svelte.dev/src/lib/server/faq/get-faq.js @@ -0,0 +1,24 @@ +// @ts-check +import fs from 'node:fs'; +import { extract_frontmatter } from '../markdown'; + +const base = '../../site/content/faq'; + +/** + * @returns {import('./types').FAQData} + */ +export function get_faq_data() { + const faqs = []; + + for (const file of fs.readdirSync(base)) { + const { metadata, body } = extract_frontmatter(fs.readFileSync(`${base}/${file}`, 'utf-8')); + + faqs.push({ + title: metadata.question, // Initialise with empty + slug: file.split('-').slice(1).join('-').replace('.md', ''), + content: body, + }); + } + + return faqs; +} diff --git a/sites/svelte.dev/src/lib/server/faq/index.js b/sites/svelte.dev/src/lib/server/faq/index.js new file mode 100644 index 0000000000..7a1d4e97ca --- /dev/null +++ b/sites/svelte.dev/src/lib/server/faq/index.js @@ -0,0 +1,87 @@ +// @ts-check +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').FAQData} faq_data + */ +export async function get_parsed_faq(faq_data) { + const highlighter = await createShikiHighlighter({ theme: 'css-variables' }); + + return Promise.all( + faq_data.map(async ({ content, slug, title }) => { + return { + title, + slug, + content: transform(content, { + /** + * @param {string} html + */ + heading(html) { + const title = html + .replace(/<\/?code>/g, '') + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/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+)([\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 `${ + line ?? '' + }`; + }) + .join(''); + } + ) + .replace(/\/\*…\*\//g, '…'); + + return html; + }, + codespan: (text) => '' + text + '', + }), + }; + }) + ); +} diff --git a/sites/svelte.dev/src/lib/server/faq/types.d.ts b/sites/svelte.dev/src/lib/server/faq/types.d.ts new file mode 100644 index 0000000000..dea3d12ee0 --- /dev/null +++ b/sites/svelte.dev/src/lib/server/faq/types.d.ts @@ -0,0 +1,5 @@ +export type FAQData = { + title: string; + slug: string; + content: string; +}[]; diff --git a/sites/svelte.dev/src/routes/faq/+page.js b/sites/svelte.dev/src/routes/faq/+page.js deleted file mode 100644 index 80327d51c4..0000000000 --- a/sites/svelte.dev/src/routes/faq/+page.js +++ /dev/null @@ -1,12 +0,0 @@ -import { PUBLIC_API_BASE } from '$env/static/public'; - -/** @type {import('./$types').PageLoad} */ -export async function load({ fetch, setHeaders }) { - const faqs = await fetch(`${PUBLIC_API_BASE}/docs/svelte/faq?content`).then((r) => r.json()); - - setHeaders({ - 'cache-control': 'public, max-age=60' - }); - - return { faqs }; -} diff --git a/sites/svelte.dev/src/routes/faq/+page.server.js b/sites/svelte.dev/src/routes/faq/+page.server.js new file mode 100644 index 0000000000..36ccd03b26 --- /dev/null +++ b/sites/svelte.dev/src/routes/faq/+page.server.js @@ -0,0 +1,8 @@ +import { get_parsed_faq } from '$lib/server/faq'; +import { get_faq_data } from '$lib/server/faq/get-faq'; + +export const prerender = true; + +export async function load() { + return { faqs: get_parsed_faq(get_faq_data()) }; +} diff --git a/sites/svelte.dev/src/routes/faq/+page.svelte b/sites/svelte.dev/src/routes/faq/+page.svelte index bb671f68fb..523cd9727d 100644 --- a/sites/svelte.dev/src/routes/faq/+page.svelte +++ b/sites/svelte.dev/src/routes/faq/+page.svelte @@ -1,7 +1,6 @@ @@ -85,11 +84,7 @@ @media (min-width: 768px) { .faqs :global(.anchor:focus), - .faqs :global(h2):hover :global(.anchor), - .faqs :global(h3):hover :global(.anchor), - .faqs :global(h4):hover :global(.anchor), - .faqs :global(h5):hover :global(.anchor), - .faqs :global(h6):hover :global(.anchor) { + .faqs :global(:where(h2, h3, h4, h5, h6)):hover :global(.anchor) { opacity: 1; }