Skip to content

Commit fc1bd40

Browse files
committed
wip: new docs
1 parent 3d75b50 commit fc1bd40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2638
-122
lines changed

docs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
"@sveltejs/package": "catalog:",
2222
"@sveltejs/vite-plugin-svelte": "catalog:",
2323
"@types/flexsearch": "^0.7.6",
24+
"@types/jsdom": "^21.1.7",
2425
"@types/katex": "^0.16.0",
2526
"autoprefixer": "^10.4.19",
27+
"jsom": "^1.0.0",
2628
"sass": "^1.69.5",
2729
"shiki": "^1.4.0",
2830
"svelte": "catalog:",
@@ -46,6 +48,7 @@
4648
"cmdk-sv": "^0.0.18",
4749
"flexsearch": "0.7.21",
4850
"iconify-icon": "^2.0.0",
51+
"jsdom": "^26.0.0",
4952
"katex": "^0.16.10",
5053
"tailwind-merge": "^2.0.0"
5154
}

docs/src/app.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ declare global {
77
// interface PageData {}
88
// interface Platform {}
99
}
10+
11+
interface ImportMeta {
12+
dirname: string;
13+
}
1014
}
1115

1216
export {};

docs/src/hooks.server.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { dev } from '$app/environment';
2+
import { base } from '$app/paths';
3+
import {
4+
addPageToIndex,
5+
createNewIndex,
6+
loadIndexFromFile,
7+
writeIndexToFile,
8+
type IndexablePageFragment
9+
} from '$lib/search';
10+
import type { Handle } from '@sveltejs/kit';
11+
import { JSDOM } from 'jsdom';
12+
13+
async function saveHtml(html: string, pathname: string) {
14+
const dom = new JSDOM(html);
15+
const contentElement = dom.window.document.querySelector('.searchable-content');
16+
const titleElement = dom.window.document.querySelector('.title');
17+
if (!contentElement) {
18+
console.warn(`No content found for ${pathname}`);
19+
return;
20+
}
21+
if (!titleElement) {
22+
console.warn(`No title found for ${pathname}`);
23+
return;
24+
}
25+
26+
const searchableHtml = contentElement.innerHTML;
27+
const title = titleElement.textContent ?? '';
28+
29+
const fragment: IndexablePageFragment = {
30+
path: pathname,
31+
content: contentElement.textContent ?? '',
32+
html: searchableHtml,
33+
title: title
34+
};
35+
36+
const index = (await loadIndexFromFile()) ?? (await createNewIndex());
37+
await addPageToIndex(index, fragment);
38+
await writeIndexToFile(index);
39+
}
40+
41+
/**
42+
* Runs at build time, used to render all the pages into static HTML and then txt, to be
43+
* able to be searched by the search engine.
44+
*/
45+
export const handle: Handle = async ({ event, resolve }) => {
46+
const response = await resolve(event);
47+
48+
const pathname = dev ? event.url.pathname : event.url.pathname.slice(base.length);
49+
50+
if (!event.isDataRequest) {
51+
if (!response.bodyUsed) {
52+
const html = await response.clone().text();
53+
await saveHtml(html, pathname);
54+
} else {
55+
console.warn(`Response body already used for ${pathname}`);
56+
}
57+
}
58+
59+
return response;
60+
};

docs/src/lib/components/code/index.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { codeToHtml } from 'shiki';
1+
import { math } from '@cartamd/plugin-math';
2+
import { Carta, isBundleLanguage } from 'carta-md';
23

34
export type CodeBlock = {
45
lang: string;
@@ -19,17 +20,41 @@ export type HighlightedCodeBlocks<T extends string = string> = {
1920
[key in T]: HighlightedCodeBlock;
2021
};
2122

23+
const carta = new Carta({
24+
sanitizer: false,
25+
theme: 'houston',
26+
extensions: [math()],
27+
shikiOptions: {
28+
themes: ['houston']
29+
}
30+
});
31+
2232
/**
2333
* Highlights the code blocks.
2434
* @param codeBlocks The code blocks to highlight
2535
* @returns The highlighted code blocks
2636
*/
27-
export async function highlightCodeBlocks<T extends string>(codeBlocks: RegisteredCodeBlocks<T>) {
37+
export async function highlightCodeBlocks<T extends string>(
38+
codeBlocks: RegisteredCodeBlocks<T>
39+
): Promise<HighlightedCodeBlocks<T>> {
2840
const highlightedCodeBlocks: HighlightedCodeBlocks<T> = {} as HighlightedCodeBlocks<T>;
2941

42+
const highlighter = await carta.highlighter();
43+
if (!highlighter) {
44+
throw new Error('Failed to get highlighter');
45+
}
46+
47+
const loadedLanguages = highlighter.getLoadedLanguages();
48+
3049
for (const key in codeBlocks) {
3150
const codeBlock = codeBlocks[key];
32-
const html = await codeToHtml(codeBlock.code, {
51+
52+
if (isBundleLanguage(codeBlock.lang) && !loadedLanguages.includes(codeBlock.lang)) {
53+
await highlighter.loadLanguage(codeBlock.lang);
54+
loadedLanguages.push(codeBlock.lang);
55+
}
56+
57+
const html = highlighter.codeToHtml(codeBlock.code, {
3358
lang: codeBlock.lang,
3459
theme: 'houston'
3560
});

docs/src/lib/components/header-tracker/HeaderTracker.svelte

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@
2525
2626
const [throttledHighlightHeader] = throttle(highlightHeader, 100);
2727
const debouncedHighlightHeader = debounce(highlightHeader, 100);
28+
29+
const getHeaderLevel = (header: HTMLElement) => {
30+
const tagName = header.tagName;
31+
return Number(tagName.split('')[1]);
32+
};
33+
34+
const headerIsVisibleAmongSiblings = (header: HTMLElement) => {
35+
const level = getHeaderLevel(header);
36+
const sameLevelHeaders = trackedHeaders.filter((h) => getHeaderLevel(h.element) === level);
37+
const index = sameLevelHeaders.findIndex((h) => h.element === header);
38+
39+
let selectedSameLevelHeaderIndex = 0;
40+
for (let i = sameLevelHeaders.length - 1; i >= 0; i--) {
41+
const rect = sameLevelHeaders[i].element.getBoundingClientRect();
42+
if (rect.top < Padding) {
43+
selectedSameLevelHeaderIndex = i;
44+
break;
45+
}
46+
}
47+
48+
return selectedSameLevelHeaderIndex === index;
49+
};
2850
</script>
2951

3052
<svelte:window
@@ -36,16 +58,26 @@
3658

3759
<div class="h-full space-y-3 {className}">
3860
{#each trackedHeaders as header, i}
39-
{@const margin = Number(header.element.tagName.split('')[1]) - 1}
61+
{@const headerLevel = getHeaderLevel(header.element)}
62+
{@const margin = headerLevel - 1}
63+
{@const parentHeader = trackedHeaders
64+
.filter((_, j) => j < i)
65+
.findLast((h) => {
66+
const parentHeaderLevel = getHeaderLevel(h.element);
67+
return parentHeaderLevel === headerLevel - 1;
68+
})}
4069
{#key selectedHeaderIndex}
41-
{#if header.element.children[0] instanceof HTMLAnchorElement && header.element.children[0].href}
42-
<a
43-
style="margin-left: {margin * 0.75}rem;"
44-
class="block text-sm {selectedHeaderIndex === i
45-
? 'font-medium text-sky-300'
46-
: 'text-neutral-400'}"
47-
href={header.element.children[0].href}>{header.element.innerText}</a
48-
>
70+
{#if headerLevel <= 2 || (parentHeader && headerIsVisibleAmongSiblings(parentHeader.element))}
71+
{#if header.element.children[0] instanceof HTMLAnchorElement && header.element.children[0].href}
72+
<a
73+
style="margin-left: {margin * 0.75}rem;"
74+
class="block text-sm transition {selectedHeaderIndex === i
75+
? 'font-medium text-sky-300'
76+
: 'text-neutral-400'}"
77+
href={header.element.children[0].href}
78+
>{header.element.innerText}
79+
</a>
80+
{/if}
4981
{/if}
5082
{/key}
5183
{/each}

docs/src/lib/components/header-tracker/headers.svelte.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ type TrackableHeader = {
1010
*/
1111
export const trackedHeaders: TrackableHeader[] = $state([]);
1212

13-
function toSlug(text: string) {
13+
/**
14+
* Converts a string to a slug
15+
* @param text The string to convert
16+
*/
17+
export function toSlug(text: string) {
1418
return text
1519
.toLowerCase()
1620
.replace(/\s+/g, '-')
@@ -23,14 +27,19 @@ function toSlug(text: string) {
2327
*/
2428
export const track: Action = (node) => {
2529
const text = node.textContent;
26-
const id = toSlug(text ?? '');
30+
const id = node.id;
31+
if (!id) {
32+
console.warn(`Header element with text "${text}" does not have an id`);
33+
return;
34+
}
35+
2736
const element = node as HTMLElement;
2837
element.id = id;
2938

3039
// Wrap the inner content in a Anchor tag
3140
const anchor = document.createElement('a');
3241
anchor.href = `#${id}`;
33-
anchor.textContent = text;
42+
anchor.innerHTML = element.innerHTML;
3443
element.innerHTML = '';
3544
element.appendChild(anchor);
3645

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
<script lang="ts">
2+
import { toSlug, track } from '../header-tracker/headers.svelte';
3+
24
interface Props {
5+
name: string;
36
npmLink: string;
47
githubLink: string;
5-
children?: import('svelte').Snippet;
68
}
79
8-
let { npmLink, githubLink, children }: Props = $props();
10+
let { npmLink, githubLink, name }: Props = $props();
911
</script>
1012

11-
<div class="plugin-link mb-2 mt-6 flex items-end space-x-3">
12-
{@render children?.()}
13+
<div class="mb-2 mt-6 flex items-end space-x-3">
14+
<h3 class="text-2xl font-medium text-neutral-200" use:track id={toSlug(name)}>
15+
<code>
16+
{name}
17+
</code>
18+
</h3>
1319

14-
<a href={githubLink} class="flex aspect-square">
20+
<a href={githubLink} aria-label="Github Link" class="flex aspect-square">
1521
<iconify-icon icon="mdi:github" class="text-3xl text-white hover:text-sky-300"></iconify-icon>
1622
</a>
17-
<a href={npmLink} class="flex aspect-square">
23+
<a href={npmLink} aria-label="NPM Link" class="flex aspect-square">
1824
<iconify-icon icon="gg:npm" class="text-3xl text-white hover:text-sky-300"></iconify-icon>
1925
</a>
2026
</div>
21-
22-
<style>
23-
:global(.markdown .plugin-link > h1, .markdown .plugin-link > h2, .markdown .plugin-link > h3) {
24-
margin-top: 0;
25-
margin-bottom: 0;
26-
}
27-
</style>

0 commit comments

Comments
 (0)