mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-05-04 02:23:51 +01:00
feat(website): package documentation
This commit is contained in:
parent
4fad00f458
commit
939f59d99e
59 changed files with 1502 additions and 276 deletions
|
@ -61,7 +61,7 @@ const ADDITIONAL_FORBIDDEN_FILES: &[&str] = &["default.project.json"];
|
|||
struct DocEntryInfo {
|
||||
#[serde(default)]
|
||||
label: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, alias = "position")]
|
||||
sidebar_position: Option<usize>,
|
||||
#[serde(default)]
|
||||
collapsed: bool,
|
||||
|
|
Binary file not shown.
|
@ -12,47 +12,57 @@
|
|||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-vercel": "^5.4.5",
|
||||
"@sveltejs/kit": "^2.7.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.8",
|
||||
"@sveltejs/adapter-vercel": "^5.4.6",
|
||||
"@sveltejs/kit": "^2.7.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/gunzip-maybe": "^1.4.2",
|
||||
"@types/tar-stream": "^3.1.3",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.44.1",
|
||||
"eslint-plugin-svelte": "^2.46.0",
|
||||
"globals": "^15.11.0",
|
||||
"mdsvex": "^0.12.3",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"svelte": "^5.0.0-next.264",
|
||||
"svelte": "^5.1.4",
|
||||
"svelte-check": "^4.0.5",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.8.1",
|
||||
"vite": "^5.4.8"
|
||||
"typescript-eslint": "^8.12.2",
|
||||
"vite": "^5.4.10"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/nunito-sans": "^5.1.0",
|
||||
"@shikijs/rehype": "^1.22.0",
|
||||
"bits-ui": "^0.21.16",
|
||||
"@shikijs/rehype": "^1.22.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/unist": "^3.0.3",
|
||||
"bits-ui": "next",
|
||||
"date-fns": "^4.1.0",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"hast-util-heading": "^3.0.0",
|
||||
"hast-util-heading-rank": "^3.0.0",
|
||||
"hast-util-to-text": "^4.0.2",
|
||||
"lucide-svelte": "^0.446.0",
|
||||
"rehype-infer-description-meta": "^2.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gemoji": "^8.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"shiki": "^1.22.0",
|
||||
"shiki": "^1.22.2",
|
||||
"tar-stream": "^3.1.7",
|
||||
"unified": "^11.0.5"
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-map": "^4.0.0",
|
||||
"vfile": "^6.0.3"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@shikijs/rehype@1.22.0": "patches/@shikijs%2Frehype@1.22.0.patch"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
--color-card: 245 230 210;
|
||||
--color-card-hover: 240 225 205;
|
||||
--color-border: 200 180 160;
|
||||
--color-header: 250 234 215;
|
||||
|
||||
--color-body: 84 70 50;
|
||||
--color-heading: 70 55 35;
|
||||
|
@ -44,13 +45,14 @@
|
|||
--color-card: 28 22 17;
|
||||
--color-card-hover: 40 32 25;
|
||||
--color-border: 28 22 17;
|
||||
--color-header: 20 16 12;
|
||||
|
||||
--color-body: 198 167 140;
|
||||
--color-heading: 227 213 200;
|
||||
--color-light: 255 255 255;
|
||||
|
||||
--color-input-bg: 22 15 8;
|
||||
--color-input-border: 82 60 41;
|
||||
--color-input-bg: 20 13 8;
|
||||
--color-input-border: 78 60 40;
|
||||
--color-placeholder: 169 147 128;
|
||||
|
||||
--color-primary: 241 157 30;
|
||||
|
@ -61,7 +63,8 @@
|
|||
}
|
||||
|
||||
html {
|
||||
scroll-padding-top: theme(spacing.16);
|
||||
scroll-padding-top: theme(spacing.24);
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -78,3 +81,12 @@ body {
|
|||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
|
9
website/src/lib/components/Logomark.svelte
Normal file
9
website/src/lib/components/Logomark.svelte
Normal file
|
@ -0,0 +1,9 @@
|
|||
<svg {...$$restProps} viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>pesde</title>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M49.6025 0L92.9038 25V75L49.6025 100L6.30127 75V25L49.6025 0ZM14.3013 29.6188L49.6025 9.2376L84.9038 29.6188V70.3812L49.6025 90.7624L33.6148 81.5319V67.3848C34.5167 68.5071 35.6388 69.4215 36.981 70.1279C38.9701 71.148 41.0357 71.658 43.1779 71.658C46.442 71.658 49.1452 70.8929 51.2873 69.3629C53.4805 67.7818 55.1126 65.7672 56.1836 63.319C57.0915 61.3382 57.632 59.274 57.8054 57.1263C59.8723 57.7457 62.2157 58.0554 64.8356 58.0554C67.6918 58.0554 70.3695 57.6473 72.8686 56.8313C75.3678 55.9642 77.4079 54.8167 78.989 53.3886L75.7758 47.8038C74.5517 48.9258 72.9961 49.8439 71.109 50.5579C69.2219 51.221 67.2073 51.5525 65.0652 51.5525C61.3929 51.5525 58.6643 50.6854 56.8792 48.9513C56.7195 48.7962 56.567 48.6365 56.4217 48.472C55.6102 47.5539 55.0211 46.4896 54.6546 45.2791L54.6443 45.2452L54.669 45.2791H79.2185V41.9894C79.2185 39.0313 78.5555 36.3536 77.2294 33.9565C75.9543 31.5593 74.0927 29.6467 71.6445 28.2186C69.2474 26.7395 66.3657 26 62.9995 26C59.6843 26 56.8027 26.7395 54.3545 28.2186C51.9064 29.6467 50.0193 31.5593 48.6932 33.9565C47.6743 35.7983 47.0469 37.8057 46.8108 39.9788C45.6888 39.728 44.4778 39.6026 43.1779 39.6026C41.0357 39.6026 38.9701 40.1127 36.981 41.1327C35.3162 41.9651 33.9902 43.1549 33.0028 44.7023V40.3677H20.6855V46.2585H25.8113V77.0266L14.3013 70.3812V29.6188ZM55.1961 36.0986C54.6528 37.1015 54.3321 38.1216 54.234 39.1588H71.7976C71.7976 38.0367 71.4405 36.9401 70.7265 35.8691C70.0634 34.747 69.0689 33.8035 67.7428 33.0384C66.4677 32.2734 64.8867 31.8908 62.9995 31.8908C61.1124 31.8908 59.5058 32.2989 58.1798 33.1149C56.9047 33.88 55.9101 34.8745 55.1961 36.0986ZM49.6451 51.5692C49.3076 50.6641 48.8381 49.871 48.2367 49.1898C48.0885 49.0219 47.9323 48.8609 47.7681 48.7067C46.085 47.0746 44.0449 46.2585 41.6478 46.2585C40.1177 46.2585 38.6131 46.5645 37.134 47.1766C35.8594 47.6773 34.6863 48.5438 33.6148 49.7759V61.47C34.6863 62.6664 35.8594 63.5378 37.134 64.084C38.6131 64.6961 40.1177 65.0021 41.6478 65.0021C44.0449 65.0021 46.085 64.1861 47.7681 62.554C49.4512 60.9219 50.2928 58.6012 50.2928 55.5921C50.2928 54.0679 50.0769 52.727 49.6451 51.5692Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
74
website/src/lib/components/Select.svelte
Normal file
74
website/src/lib/components/Select.svelte
Normal file
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts">
|
||||
import { Select, type SelectSingleRootProps, type WithoutChildren } from "bits-ui"
|
||||
import { Check, ChevronsUpDown } from "lucide-svelte"
|
||||
import type { Snippet } from "svelte"
|
||||
|
||||
type Props = Omit<WithoutChildren<SelectSingleRootProps>, "type"> & {
|
||||
placeholder?: string
|
||||
items: { value: string; label: string; disabled?: boolean }[]
|
||||
contentProps?: WithoutChildren<Select.ContentProps>
|
||||
contentClass?: string
|
||||
triggerClass?: string
|
||||
trigger?: Snippet<[Record<string, unknown>, string]>
|
||||
sameWidth?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
value = $bindable(""),
|
||||
items,
|
||||
trigger,
|
||||
contentProps,
|
||||
contentClass = "",
|
||||
triggerClass = "",
|
||||
placeholder,
|
||||
sameWidth = true,
|
||||
id,
|
||||
open = $bindable(false),
|
||||
...restProps
|
||||
}: Props = $props()
|
||||
|
||||
const selectedLabel = $derived(items.find((item) => item.value === value)?.label)
|
||||
const triggerLabel = $derived(selectedLabel ?? placeholder ?? "")
|
||||
</script>
|
||||
|
||||
<Select.Root type="single" bind:value bind:open {...restProps}>
|
||||
<Select.Trigger {id}>
|
||||
{#snippet child({ props })}
|
||||
{#if trigger}
|
||||
{@render trigger(props, triggerLabel)}
|
||||
{:else}
|
||||
<button
|
||||
{...props}
|
||||
class={`border-input-border bg-input-bg ring-primary-bg/20 focus:border-primary relative flex h-11 w-full items-center rounded border px-4 outline-none ring-0 transition focus:ring-4 data-[disabled]:opacity-50 ${triggerClass}`}
|
||||
>
|
||||
{triggerLabel}
|
||||
<ChevronsUpDown class="ml-auto size-5" />
|
||||
</button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Select.Trigger>
|
||||
<Select.Portal>
|
||||
<Select.Content
|
||||
sideOffset={8}
|
||||
collisionPadding={8}
|
||||
{...contentProps}
|
||||
class={`bg-card border-input-border z-50 max-h-[var(--bits-floating-available-height)] origin-top overflow-y-auto rounded-lg border p-1 shadow-lg ${sameWidth ? "w-[var(--bits-select-anchor-width)]" : ""} ${contentClass}`}
|
||||
>
|
||||
{#each items as { value, label, disabled } (value)}
|
||||
<Select.Item
|
||||
{value}
|
||||
{label}
|
||||
{disabled}
|
||||
class="data-[highlighted]:bg-card-hover flex h-10 flex-shrink-0 items-center truncate rounded-sm px-3"
|
||||
>
|
||||
{#snippet children({ selected })}
|
||||
{label}
|
||||
{#if selected}
|
||||
<Check class="ml-auto size-4" />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
116
website/src/lib/markdown.ts
Normal file
116
website/src/lib/markdown.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import rehypeShikiFromHighlighter from "@shikijs/rehype/core"
|
||||
import type { Nodes } from "hast"
|
||||
import { heading } from "hast-util-heading"
|
||||
import { headingRank } from "hast-util-heading-rank"
|
||||
import { toText } from "hast-util-to-text"
|
||||
import rehypeInferDescriptionMeta from "rehype-infer-description-meta"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import rehypeSanitize from "rehype-sanitize"
|
||||
import rehypeSlug from "rehype-slug"
|
||||
import rehypeStringify from "rehype-stringify"
|
||||
import remarkFrontmatter from "remark-frontmatter"
|
||||
import remarkGemoji from "remark-gemoji"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkRehype from "remark-rehype"
|
||||
import { createCssVariablesTheme, createHighlighter } from "shiki"
|
||||
import { unified } from "unified"
|
||||
import type { Node } from "unist"
|
||||
import { map } from "unist-util-map"
|
||||
|
||||
const highlighter = createHighlighter({
|
||||
themes: [],
|
||||
langs: [],
|
||||
})
|
||||
|
||||
export const markdown = (async () => {
|
||||
return unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkFrontmatter)
|
||||
.use(remarkGfm)
|
||||
.use(remarkGemoji)
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeShikiFromHighlighter, await highlighter, {
|
||||
lazy: true,
|
||||
theme: createCssVariablesTheme({
|
||||
name: "css-variables",
|
||||
variablePrefix: "--shiki-",
|
||||
variableDefaults: {},
|
||||
fontStyle: true,
|
||||
}),
|
||||
fallbackLanguage: "text",
|
||||
})
|
||||
.use(rehypeStringify)
|
||||
.freeze()
|
||||
})()
|
||||
|
||||
export type TocItem = {
|
||||
id: string
|
||||
title: string
|
||||
level: number
|
||||
}
|
||||
|
||||
export const docsMarkdown = (async () => {
|
||||
return unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkFrontmatter)
|
||||
.use(remarkGfm)
|
||||
.use(remarkGemoji)
|
||||
.use(remarkRehype, { allowDangerousHtml: true, clobberPrefix: "" })
|
||||
.use(rehypeSlug)
|
||||
.use(() => (node, file) => {
|
||||
const toc: TocItem[] = []
|
||||
file.data.toc = toc
|
||||
|
||||
return map(node as Nodes, (node) => {
|
||||
if (node.type === "element" && node.tagName === "a") {
|
||||
const fullUrl = new URL(node.properties.href as string, `file://${file.path}`)
|
||||
|
||||
let href = node.properties.href as string
|
||||
if (fullUrl.protocol === "file:") {
|
||||
href = file.data.basePath + fullUrl.pathname.replace(/\.mdx?$/, "") + fullUrl.hash
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
properties: {
|
||||
...node.properties,
|
||||
href,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (heading(node)) {
|
||||
const rank = headingRank(node)
|
||||
if (rank && typeof node.properties.id === "string" && rank >= 2 && rank <= 3) {
|
||||
toc.push({
|
||||
id: node.properties.id,
|
||||
title: toText(node),
|
||||
level: rank,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
}) as Node
|
||||
})
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeShikiFromHighlighter, await highlighter, {
|
||||
lazy: true,
|
||||
theme: createCssVariablesTheme({
|
||||
name: "css-variables",
|
||||
variablePrefix: "--shiki-",
|
||||
variableDefaults: {},
|
||||
fontStyle: true,
|
||||
}),
|
||||
fallbackLanguage: "text",
|
||||
})
|
||||
.use(rehypeInferDescriptionMeta, {
|
||||
selector: "p",
|
||||
})
|
||||
.use(rehypeStringify)
|
||||
.freeze()
|
||||
})()
|
|
@ -19,6 +19,7 @@ export type PackageResponse = {
|
|||
authors?: string[]
|
||||
repository?: string
|
||||
dependencies: Record<string, DependencyEntry>
|
||||
docs?: DocEntry[]
|
||||
}
|
||||
|
||||
export type TargetInfo = {
|
||||
|
@ -31,19 +32,38 @@ export type TargetKind = "roblox" | "roblox_server" | "lune" | "luau"
|
|||
|
||||
export type DependencyEntry = [DependencyInfo, DependencyKind]
|
||||
|
||||
export type DependencyInfo = {
|
||||
index: string
|
||||
name: string
|
||||
target?: string
|
||||
version: string
|
||||
} | {
|
||||
index: string,
|
||||
wally: string,
|
||||
version: string,
|
||||
}
|
||||
export type DependencyInfo =
|
||||
| {
|
||||
index: string
|
||||
name: string
|
||||
target?: string
|
||||
version: string
|
||||
}
|
||||
| {
|
||||
index: string
|
||||
wally: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export type DependencyKind = "standard" | "peer" | "dev"
|
||||
|
||||
export type DocEntry = DocEntryCategory | DocEntryPage
|
||||
|
||||
export type DocEntryBase = {
|
||||
label: string
|
||||
position: number
|
||||
}
|
||||
|
||||
export type DocEntryCategory = DocEntryBase & {
|
||||
items?: DocEntry[]
|
||||
collapsed?: boolean
|
||||
}
|
||||
|
||||
export type DocEntryPage = DocEntryBase & {
|
||||
name: string
|
||||
hash: string
|
||||
}
|
||||
|
||||
export const TARGET_KIND_DISPLAY_NAMES: Record<TargetKind, string> = {
|
||||
roblox: "Roblox",
|
||||
roblox_server: "Roblox (server)",
|
||||
|
|
8
website/src/routes/(app)/+error.svelte
Normal file
8
website/src/routes/(app)/+error.svelte
Normal file
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
import { page } from "$app/stores"
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||
<h1 class="mb-1 text-4xl font-bold text-heading">{$page.status}</h1>
|
||||
<p class="text-lg">{$page.error?.message}</p>
|
||||
</div>
|
14
website/src/routes/(app)/+layout.svelte
Normal file
14
website/src/routes/(app)/+layout.svelte
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
import Footer from "./Footer.svelte"
|
||||
import Header from "./Header.svelte"
|
||||
|
||||
const { children } = $props()
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<main class="min-h-screen">
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<Footer />
|
77
website/src/routes/(app)/Hamburger.svelte
Normal file
77
website/src/routes/(app)/Hamburger.svelte
Normal file
|
@ -0,0 +1,77 @@
|
|||
<script lang="ts">
|
||||
import { navigating } from "$app/stores"
|
||||
import GitHub from "$lib/components/GitHub.svelte"
|
||||
import Logo from "$lib/components/Logo.svelte"
|
||||
import { Dialog } from "bits-ui"
|
||||
import { Menu, X } from "lucide-svelte"
|
||||
import { fade, fly } from "svelte/transition"
|
||||
import Search from "./Search.svelte"
|
||||
|
||||
let dialogOpen = $state(false)
|
||||
|
||||
$effect(() => {
|
||||
if ($navigating) {
|
||||
dialogOpen = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Trigger>
|
||||
<span class="sr-only">open menu</span>
|
||||
<Menu aria-hidden="true" />
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content forceMount>
|
||||
{#snippet child({ props, open })}
|
||||
{#if open}
|
||||
<div {...props} class="fixed inset-0 top-0 z-50 flex flex-col">
|
||||
<Dialog.Title class="sr-only">Menu</Dialog.Title>
|
||||
<div transition:fade={{ duration: 200 }} class="bg-header">
|
||||
<div class="relative z-50 flex h-14 flex-shrink-0 items-center justify-between px-4">
|
||||
<a href="/">
|
||||
<Logo class="text-primary h-7" />
|
||||
</a>
|
||||
<Dialog.Close>
|
||||
<span class="sr-only">close menu</span>
|
||||
<X aria-hidden="true" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<div class="px-4 py-1">
|
||||
<Search />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-header flex flex-grow flex-col overflow-hidden"
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<nav
|
||||
class="flex h-full flex-col px-4 pt-2"
|
||||
transition:fly={{ y: "-2%", duration: 200 }}
|
||||
>
|
||||
<div class="flex flex-grow flex-col border-y py-3">
|
||||
{#snippet item(href: string, text: string)}
|
||||
<a {href} class="hover:bg-card/80 flex h-10 items-center rounded px-3">{text}</a
|
||||
>
|
||||
{/snippet}
|
||||
|
||||
{@render item("https://docs.pesde.daimond113.com/", "Documentation")}
|
||||
{@render item("https://docs.pesde.daimond113.com/registry/policies", "Policies")}
|
||||
</div>
|
||||
<div class="flex items-center py-5">
|
||||
<a
|
||||
href="https://github.com/daimond113/pesde"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<GitHub class="size-6" />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
81
website/src/routes/(app)/Header.svelte
Normal file
81
website/src/routes/(app)/Header.svelte
Normal file
|
@ -0,0 +1,81 @@
|
|||
<script lang="ts">
|
||||
import GitHub from "$lib/components/GitHub.svelte"
|
||||
import Logo from "$lib/components/Logo.svelte"
|
||||
import type { Action } from "svelte/action"
|
||||
import Hamburger from "./Hamburger.svelte"
|
||||
import Search from "./Search.svelte"
|
||||
|
||||
let hideSearch = $state(false)
|
||||
let headerHidden = $state(false)
|
||||
|
||||
const handleScroll = () => {
|
||||
hideSearch = window.scrollY > 0
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
handleScroll()
|
||||
})
|
||||
|
||||
const headerIntersection: Action = (node) => {
|
||||
$effect(() => {
|
||||
const callback: IntersectionObserverCallback = (entries) => {
|
||||
for (const entry of entries) {
|
||||
headerHidden = !entry.isIntersecting
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(callback, {
|
||||
threshold: 0,
|
||||
rootMargin: "-57px 0px 0px 0px",
|
||||
})
|
||||
|
||||
observer.observe(node)
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:scroll={handleScroll} />
|
||||
|
||||
<header
|
||||
use:headerIntersection
|
||||
data-hidden={headerHidden ? true : null}
|
||||
data-hide-search={hideSearch ? true : null}
|
||||
class="bg-header group border-b pt-14 sm:!bg-transparent"
|
||||
>
|
||||
<div
|
||||
class="bg-header fixed inset-x-0 top-0 z-50 bg-opacity-100 backdrop-blur-lg transition-[background] group-data-[hidden]:border-b group-data-[hidden]:bg-opacity-80 sm:!border-b sm:!bg-opacity-80"
|
||||
>
|
||||
<div class="mx-auto flex h-14 max-w-screen-lg items-center justify-between px-4">
|
||||
<a href="/">
|
||||
<Logo class="text-primary h-7" />
|
||||
</a>
|
||||
<div class="hidden w-full max-w-80 sm:flex">
|
||||
<Search />
|
||||
</div>
|
||||
<div class="[&_a:hover]:text-heading hidden items-center space-x-6 sm:flex [&_a]:transition">
|
||||
<nav class="flex items-center space-x-6 font-medium">
|
||||
<a href="https://docs.pesde.daimond113.com/">Docs</a>
|
||||
<a href="https://docs.pesde.daimond113.com/registry/policies">Policies</a>
|
||||
</nav>
|
||||
|
||||
<a href="https://github.com/daimond113/pesde" target="_blank" rel="noreferrer noopener">
|
||||
<GitHub class="size-6" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center sm:hidden">
|
||||
<Hamburger />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden px-4 pb-2 pt-1 sm:hidden">
|
||||
<div
|
||||
class="transition duration-300 group-data-[hide-search]:-translate-y-1 group-data-[hide-search]:opacity-0"
|
||||
>
|
||||
<Search />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
6
website/src/routes/(app)/[...404]/+page.ts
Normal file
6
website/src/routes/(app)/[...404]/+page.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { error } from "@sveltejs/kit"
|
||||
import type { PageLoad } from "./$types"
|
||||
|
||||
export const load: PageLoad = () => {
|
||||
error(404, "Not Found")
|
||||
}
|
|
@ -10,7 +10,9 @@
|
|||
const [scope, name] = $derived(data.pkg.name.split("/"))
|
||||
|
||||
let currentPkg = $state(data.pkg)
|
||||
let currentTarget = $state(data.pkg.targets[0])
|
||||
let currentTarget = $state(
|
||||
data.pkg.targets.find((target) => target.kind === $page.params.target) ?? data.pkg.targets[0],
|
||||
)
|
||||
|
||||
setContext("currentPkg", {
|
||||
get value() {
|
||||
|
@ -73,13 +75,16 @@
|
|||
<p class="mb-6 max-w-prose">{pkgDescription}</p>
|
||||
|
||||
<div class="mb-8 lg:hidden">
|
||||
<TargetSelector id="mobile-target-selector" />
|
||||
<TargetSelector />
|
||||
</div>
|
||||
|
||||
<nav class="flex w-full border-b-2">
|
||||
<nav class="flex w-full flex-col sm:flex-row sm:border-b-2">
|
||||
<Tab tab={readme}>Readme</Tab>
|
||||
<Tab tab={`${pkgVersion}/${currentTarget.kind}/dependencies`}>Dependencies</Tab>
|
||||
<Tab tab="versions">Versions</Tab>
|
||||
{#if currentPkg.docs && currentPkg.docs.length > 0}
|
||||
<Tab tab={`${pkgVersion}/${currentTarget.kind}/docs`}>Documentation</Tab>
|
||||
{/if}
|
||||
</nav>
|
||||
|
||||
{@render children()}
|
|
@ -22,7 +22,9 @@
|
|||
const active = $derived(activeTab === tab)
|
||||
|
||||
const linkClass = $derived(
|
||||
`font-semibold px-5 h-10 inline-flex -mb-0.5 items-center rounded-t border-b-2 transition ${active ? "text-primary border-b-primary bg-primary-bg/20" : "hover:bg-border/30"}`,
|
||||
"font-semibold px-5 inline-flex items-center transition rounded-r h-12 border-l-2 " +
|
||||
"sm:rounded-r-none sm:rounded-t sm:border-b-2 sm:border-l-0 sm:h-10 sm:-mb-0.5 " +
|
||||
(active ? "text-primary border-primary bg-primary-bg/20" : "hover:bg-border/30"),
|
||||
)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation"
|
||||
import { page } from "$app/stores"
|
||||
import Select from "$lib/components/Select.svelte"
|
||||
import { TARGET_KIND_DISPLAY_NAMES, type TargetInfo, type TargetKind } from "$lib/registry-api"
|
||||
import { Label, useId } from "bits-ui"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const currentTarget = getContext<{ value: TargetInfo }>("currentTarget")
|
||||
|
||||
const basePath = $derived.by(() => {
|
||||
const { scope, name } = $page.params
|
||||
if ("target" in $page.params) {
|
||||
const { version } = $page.params
|
||||
return `/packages/${scope}/${name}/${version}`
|
||||
}
|
||||
return `/packages/${scope}/${name}/latest`
|
||||
})
|
||||
|
||||
const items = ($page.data.pkg.targets as TargetInfo[]).map((target) => {
|
||||
return {
|
||||
value: target.kind,
|
||||
label: TARGET_KIND_DISPLAY_NAMES[target.kind as TargetKind],
|
||||
}
|
||||
})
|
||||
|
||||
const id = useId()
|
||||
|
||||
let disabled = $state(false)
|
||||
let open = $state(false)
|
||||
</script>
|
||||
|
||||
<div class="text-heading mb-1 text-lg font-semibold">
|
||||
<Label.Root for={id} onclick={() => (open = true)}>Target</Label.Root>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
{items}
|
||||
{disabled}
|
||||
{id}
|
||||
bind:open
|
||||
name="target-selector"
|
||||
allowDeselect={false}
|
||||
value={currentTarget.value.kind}
|
||||
triggerClass="mb-6"
|
||||
onValueChange={(selected) => {
|
||||
disabled = true
|
||||
goto(`${basePath}/${selected}`).finally(() => {
|
||||
disabled = false
|
||||
})
|
||||
}}
|
||||
/>
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
fetchRegistryJson,
|
||||
RegistryHttpError,
|
||||
type PackageVersionResponse,
|
||||
type PackageVersionsResponse,
|
||||
} from "$lib/registry-api"
|
||||
import { error, redirect } from "@sveltejs/kit"
|
||||
import type { LayoutLoad } from "./$types"
|
||||
|
||||
type FetchPackageOptions = {
|
||||
scope: string
|
||||
name: string
|
||||
version: string
|
||||
target: string
|
||||
}
|
||||
|
||||
const fetchPackageAndVersions = async (fetcher: typeof fetch, options: FetchPackageOptions) => {
|
||||
const { scope, name, version, target } = options
|
||||
|
||||
try {
|
||||
const [pkg, versions] = await Promise.all([
|
||||
fetchRegistryJson<PackageVersionResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version}/${target}`,
|
||||
fetcher,
|
||||
),
|
||||
|
||||
fetchRegistryJson<PackageVersionsResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}`,
|
||||
fetcher,
|
||||
),
|
||||
])
|
||||
|
||||
versions.reverse()
|
||||
return { pkg, versions }
|
||||
} catch (e) {
|
||||
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||
error(404, "This package does not exist.")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export const load: LayoutLoad = async ({ params, url, fetch }) => {
|
||||
const { scope, name, version, target } = params
|
||||
|
||||
if (version !== undefined && target === undefined) {
|
||||
error(404, "Not Found")
|
||||
}
|
||||
|
||||
if (version === undefined || target === undefined || version === "latest" || target === "any") {
|
||||
const pkg = await fetchRegistryJson<PackageVersionResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version ?? "latest"}/${target ?? "any"}`,
|
||||
fetch,
|
||||
)
|
||||
|
||||
const path = url.pathname.split("/").slice(6).join("/")
|
||||
|
||||
return redirect(303, `/packages/${scope}/${name}/${pkg.version}/${pkg.targets[0].kind}/${path}`)
|
||||
}
|
||||
|
||||
const { pkg, versions } = await fetchPackageAndVersions(fetch, { scope, name, version, target })
|
||||
|
||||
return {
|
||||
pkg,
|
||||
versions: versions.map((v) => v.version),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/stores"
|
||||
import Logo from "$lib/components/Logo.svelte"
|
||||
import Logomark from "$lib/components/Logomark.svelte"
|
||||
import type { TocItem } from "$lib/markdown"
|
||||
import { ChevronsUpDown } from "lucide-svelte"
|
||||
import type { Action } from "svelte/action"
|
||||
import Hamburger from "./Hamburger.svelte"
|
||||
import TocMobile from "./MobileNavbar.svelte"
|
||||
import Sidebar from "./Sidebar.svelte"
|
||||
import Tab from "./Tab.svelte"
|
||||
import TargetSelector from "./TargetSelector.svelte"
|
||||
import Toc from "./Toc.svelte"
|
||||
import TocObserver from "./TocObserver.svelte"
|
||||
import VersionSelector from "./VersionSelector.svelte"
|
||||
|
||||
const { children, data } = $props()
|
||||
const [scope, name] = data.pkg.name.split("/")
|
||||
|
||||
let hideNavigation = $state(false)
|
||||
let headerHidden = $state(false)
|
||||
|
||||
const handleScroll = () => {
|
||||
hideNavigation = window.scrollY > 0
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
handleScroll()
|
||||
})
|
||||
|
||||
const headerIntersection: Action = (node) => {
|
||||
$effect(() => {
|
||||
const callback: IntersectionObserverCallback = (entries) => {
|
||||
for (const entry of entries) {
|
||||
headerHidden = !entry.isIntersecting
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(callback, {
|
||||
threshold: 0,
|
||||
rootMargin: "-57px 0px 0px 0px",
|
||||
})
|
||||
|
||||
observer.observe(node)
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toc: TocItem[] = $page.error
|
||||
? [
|
||||
{
|
||||
id: "_top",
|
||||
title: "Overview",
|
||||
level: 2,
|
||||
},
|
||||
]
|
||||
: ($page.data.toc ?? [])
|
||||
</script>
|
||||
|
||||
<svelte:window on:scroll={handleScroll} />
|
||||
|
||||
<TocObserver toc={$page.data.toc ?? []} />
|
||||
|
||||
<div class="min-h-screen">
|
||||
<header
|
||||
class="bg-header group w-full border-b pt-14"
|
||||
use:headerIntersection
|
||||
data-hide-navigation={hideNavigation ? true : null}
|
||||
data-hidden={headerHidden ? true : null}
|
||||
>
|
||||
<div
|
||||
class="bg-header fixed top-0 z-10 w-full backdrop-blur-lg transition-[background] group-data-[hidden]:border-b xl:group-data-[hidden]:bg-opacity-80"
|
||||
>
|
||||
<div class="mx-auto flex h-14 max-w-screen-2xl items-center px-4">
|
||||
{#snippet separator()}
|
||||
<span class="text-body/60 px-2 text-xl">/</span>
|
||||
{/snippet}
|
||||
|
||||
<span class="flex min-w-0 items-center">
|
||||
<a
|
||||
class="flex min-w-0 items-center"
|
||||
href={`/packages/${scope}/${name}/${$page.params.version ?? "latest"}/${$page.params.target ?? "any"}`}
|
||||
>
|
||||
<span class="text-primary mr-2">
|
||||
<Logomark class="h-7" />
|
||||
</span>
|
||||
<span class="min-w-0 truncate">{scope}</span>
|
||||
{@render separator()}
|
||||
<span class="text-heading min-w-0 truncate font-medium">{name}</span>
|
||||
</a>
|
||||
<span class="hidden items-center lg:flex">
|
||||
{#snippet trigger(props: Record<string, unknown>, label: string)}
|
||||
<button
|
||||
{...props}
|
||||
class="flex items-center transition-opacity data-[disabled]:opacity-50"
|
||||
>
|
||||
{label}
|
||||
<ChevronsUpDown class="ml-1 size-4" />
|
||||
</button>
|
||||
{/snippet}
|
||||
|
||||
{@render separator()}
|
||||
<VersionSelector {trigger} sameWidth={false} />
|
||||
{@render separator()}
|
||||
<TargetSelector {trigger} sameWidth={false} />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="ml-auto flex items-center lg:hidden">
|
||||
<Hamburger />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-data-[hidden]:invisible">
|
||||
<div class="-mb-px overflow-hidden pb-px">
|
||||
<nav
|
||||
class="transition duration-300 group-data-[hide-navigation]:-translate-y-1 group-data-[hide-navigation]:opacity-0"
|
||||
>
|
||||
<div class="mx-auto flex max-w-screen-2xl px-4">
|
||||
<Tab tab="docs" active={$page.data.activeTab == "docs"}>Docs</Tab>
|
||||
<Tab tab="reference" active={$page.data.activeTab == "reference"}>Reference</Tab>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<TocMobile {toc} />
|
||||
|
||||
<div class="mx-auto flex max-w-screen-2xl">
|
||||
<Sidebar items={$page.data.sidebar ?? []} />
|
||||
<main class="mx-auto w-full max-w-screen-md px-4 md:px-6">
|
||||
<div id="_top"></div>
|
||||
{@render children()}
|
||||
</main>
|
||||
<Toc {toc} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="mx-auto max-w-screen-2xl px-4">
|
||||
<div class="border-t py-16">
|
||||
<p class="text-center font-semibold">
|
||||
Documentation powered by<br />
|
||||
<span class="mt-2 inline-block">
|
||||
<a href="/"><Logo class="inline h-8" /></a>
|
||||
<span class="mx-2 text-lg font-semibold">+</span>
|
||||
<a href="https://eryn.io/moonwave">
|
||||
<svg
|
||||
class="inline h-6"
|
||||
viewBox="0 0 76 36"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Moonwave</title>
|
||||
<path
|
||||
d="M0.212789 31.8083C-0.187211 31.6083 0.012789 31.0083 0.512789 31.1083C6.01279 32.7083 15.9128 33.8083 21.4128 24.5083C28.8128 12.1083 31.8128 -5.59168 58.0128 1.70832C58.5128 1.80832 58.5128 2.50832 58.1128 2.60832C54.5128 4.10832 45.3128 9.20832 49.9128 19.5083C49.9128 19.5083 53.3128 8.50832 66.0128 11.8083C66.3128 11.9083 66.3128 12.3083 66.0128 12.5083C63.5128 13.6083 56.5128 17.6083 59.1128 25.8083C61.4128 33.0083 71.8128 32.5083 75.1128 32.1083C75.6128 32.0083 75.7128 32.6083 75.1128 32.9083C72.2128 34.3083 56.7128 40.1083 50.3128 26.9083C50.3128 26.9083 41.9128 32.5083 37.5128 21.1083C37.3128 21.2083 25.7128 45.0083 0.212789 31.8083Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
|
@ -0,0 +1,101 @@
|
|||
<script lang="ts">
|
||||
import { navigating, page } from "$app/stores"
|
||||
import Logomark from "$lib/components/Logomark.svelte"
|
||||
import { Dialog, Label, useId } from "bits-ui"
|
||||
import { Menu, X } from "lucide-svelte"
|
||||
import { fade, fly } from "svelte/transition"
|
||||
import SidebarItem from "./SidebarItem.svelte"
|
||||
import TargetSelector from "./TargetSelector.svelte"
|
||||
import VersionSelector from "./VersionSelector.svelte"
|
||||
|
||||
let dialogOpen = $state(false)
|
||||
const [scope, name] = $page.data.pkg.name.split("/")
|
||||
|
||||
$effect(() => {
|
||||
if ($navigating) {
|
||||
dialogOpen = false
|
||||
}
|
||||
})
|
||||
|
||||
let versionOpen = $state(false)
|
||||
let targetOpen = $state(false)
|
||||
|
||||
const versionId = useId()
|
||||
const targetId = useId()
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Trigger>
|
||||
<span class="sr-only">open menu</span>
|
||||
<Menu aria-hidden="true" />
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content forceMount>
|
||||
{#snippet child({ props, open })}
|
||||
{#if open}
|
||||
<div {...props} class="fixed inset-0 top-0 z-50 flex flex-col">
|
||||
<Dialog.Title class="sr-only">Menu</Dialog.Title>
|
||||
<div transition:fade={{ duration: 200 }} class="bg-header">
|
||||
<div
|
||||
class="relative z-50 flex h-14 flex-shrink-0 items-center justify-between border-b px-4"
|
||||
>
|
||||
<a
|
||||
class="flex items-center truncate"
|
||||
href={`/packages/${scope}/${name}/${$page.params.version ?? "latest"}/${$page.params.target ?? "any"}`}
|
||||
>
|
||||
{#snippet separator()}
|
||||
<span class="text-body/60 px-2 text-xl">/</span>
|
||||
{/snippet}
|
||||
|
||||
<span class="text-primary mr-2">
|
||||
<Logomark class="h-7" />
|
||||
</span>
|
||||
<span class="truncate">{scope}</span>
|
||||
{@render separator()}
|
||||
<span class="text-heading truncate font-medium">{name}</span>
|
||||
</a>
|
||||
<Dialog.Close>
|
||||
<span class="sr-only">close menu</span>
|
||||
<X aria-hidden="true" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-header flex flex-grow flex-col overflow-hidden"
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<nav
|
||||
class="flex h-full flex-col overflow-y-auto p-4"
|
||||
transition:fly={{ y: "-2%", duration: 200 }}
|
||||
>
|
||||
<div
|
||||
class="mb-4 flex flex-col space-y-4 border-b pb-4 sm:flex-row sm:space-x-4 sm:space-y-0"
|
||||
>
|
||||
<div class="w-full">
|
||||
<div class="text-heading mb-1 text-sm font-semibold">
|
||||
<Label.Root for={versionId}>Version</Label.Root>
|
||||
</div>
|
||||
<VersionSelector id={versionId} bind:open={versionOpen} />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="text-heading mb-1 text-sm font-semibold">
|
||||
<Label.Root for={targetId}>Target</Label.Root>
|
||||
</div>
|
||||
<TargetSelector id={targetId} bind:open={targetOpen} />
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
{#each $page.data.sidebar ?? [] as item}
|
||||
<li>
|
||||
<SidebarItem {item} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import type { TocItem } from "$lib/markdown"
|
||||
import { Popover } from "bits-ui"
|
||||
import { ChevronRight } from "lucide-svelte"
|
||||
import { scale } from "svelte/transition"
|
||||
import TocList, { tocScroll } from "./TocList.svelte"
|
||||
import { activeHeaderId } from "./TocObserver.svelte"
|
||||
|
||||
type Props = {
|
||||
toc: TocItem[]
|
||||
}
|
||||
|
||||
const { toc }: Props = $props()
|
||||
|
||||
let activeHeaderTitle = $derived(toc.find((item) => item.id === activeHeaderId.value)?.title)
|
||||
let popoverOpen = $state(false)
|
||||
</script>
|
||||
|
||||
<nav class="bg-header/80 sticky top-14 border-b backdrop-blur-lg lg:ml-72 xl:hidden">
|
||||
<div class="mx-auto flex h-12 max-w-screen-md items-center px-4 md:px-6">
|
||||
<Popover.Root bind:open={popoverOpen}>
|
||||
<Popover.Trigger
|
||||
class="bg-background/80 flex h-8 flex-shrink-0 items-center rounded border px-4 text-sm font-semibold {popoverOpen
|
||||
? 'border-primary'
|
||||
: 'hover:border-body/50'}"
|
||||
>
|
||||
On this page
|
||||
<ChevronRight class="ml-0.5 size-4" />
|
||||
</Popover.Trigger>
|
||||
<Popover.Content side="bottom" align="start" sideOffset={4} collisionPadding={8}>
|
||||
{#snippet child({ props })}
|
||||
<div
|
||||
class="bg-card border-input-border max-h-[var(--bits-popover-content-available-height)] w-72 max-w-[var(--bits-popover-content-available-width)] origin-top-left overflow-y-auto rounded border p-4 pl-2 pr-6 shadow-lg"
|
||||
use:tocScroll
|
||||
transition:scale={{ start: 0.95, duration: 200 }}
|
||||
{...props}
|
||||
>
|
||||
<TocList mobile {toc} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
{#if activeHeaderTitle}
|
||||
<span class="text-heading ml-2 truncate text-sm">{activeHeaderTitle}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
|
@ -0,0 +1,36 @@
|
|||
<script module lang="ts">
|
||||
export type SidebarItem =
|
||||
| {
|
||||
label: string
|
||||
href: string
|
||||
}
|
||||
| {
|
||||
label: string
|
||||
children: SidebarItem[]
|
||||
collapsed: boolean
|
||||
}
|
||||
|
||||
// this comment is here to fix the syntax highlighting
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import SidebarItemComponent from "./SidebarItem.svelte"
|
||||
|
||||
type Props = {
|
||||
items: SidebarItem[]
|
||||
}
|
||||
|
||||
const { items }: Props = $props()
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class="sticky top-14 -mt-12 hidden h-[calc(100vh-theme(spacing.14))] w-72 flex-shrink-0 flex-col overflow-y-auto border-r p-4 lg:flex xl:mt-0"
|
||||
>
|
||||
<ul>
|
||||
{#each items as item}
|
||||
<li>
|
||||
<SidebarItemComponent {item} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
|
@ -0,0 +1,45 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/stores"
|
||||
import { ChevronDownIcon } from "lucide-svelte"
|
||||
import type { SidebarItem } from "./Sidebar.svelte"
|
||||
import Self from "./SidebarItem.svelte"
|
||||
|
||||
type Props = {
|
||||
item: SidebarItem
|
||||
}
|
||||
|
||||
const { item }: Props = $props()
|
||||
|
||||
let open = $state("collapsed" in item ? !item.collapsed : true)
|
||||
|
||||
let active = $derived.by(() => {
|
||||
if ("href" in item) {
|
||||
const fullUrl = new URL(item.href, $page.url)
|
||||
return fullUrl.pathname === $page.url.pathname
|
||||
}
|
||||
return false
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if "href" in item}
|
||||
<a
|
||||
href={item.href}
|
||||
class={`-mx-2 block rounded px-2 py-1.5 text-sm transition ${active ? "bg-primary-bg/20 text-primary font-semibold" : "hover:text-heading"}`}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
{:else}
|
||||
<details class="group flex flex-col py-0.5" bind:open>
|
||||
<summary class="text-heading flex list-none items-center py-1 font-bold">
|
||||
<span>{item.label} </span>
|
||||
<ChevronDownIcon class="ml-auto size-5 transition group-[:not([open])]:-rotate-90" />
|
||||
</summary>
|
||||
<ul class="mb-1 border-l pl-4">
|
||||
{#each item.children as child}
|
||||
<li>
|
||||
<Self item={child} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</details>
|
||||
{/if}
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/stores"
|
||||
import type { Snippet } from "svelte"
|
||||
|
||||
type Props = {
|
||||
tab: string
|
||||
children: Snippet
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const { tab, children, active = false }: Props = $props()
|
||||
|
||||
const basePath = $derived.by(() => {
|
||||
const { scope, name, version, target } = $page.params
|
||||
return `/packages/${scope}/${name}/${version ?? "latest"}/${target ?? "any"}`
|
||||
})
|
||||
|
||||
const href = $derived(`${basePath}/${tab}`)
|
||||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
class={`-mb-px flex h-9 w-full items-center justify-center border-b-2 px-4 font-semibold transition sm:w-auto ${active ? "border-primary text-heading" : "hover:border-border hover:text-heading border-transparent"}`}
|
||||
>
|
||||
{@render children()}
|
||||
</a>
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation"
|
||||
import { page } from "$app/stores"
|
||||
import Select from "$lib/components/Select.svelte"
|
||||
import { TARGET_KIND_DISPLAY_NAMES, type TargetInfo } from "$lib/registry-api"
|
||||
import type { Snippet } from "svelte"
|
||||
|
||||
let disabled = $state(false)
|
||||
|
||||
type Props = {
|
||||
trigger?: Snippet<[Record<string, unknown>, string]>
|
||||
sameWidth?: boolean
|
||||
open?: boolean
|
||||
id?: string
|
||||
}
|
||||
|
||||
let { trigger, sameWidth = true, open = $bindable(false), id }: Props = $props()
|
||||
|
||||
const basePath = $derived.by(() => {
|
||||
const { scope, name } = $page.params
|
||||
return `/packages/${scope}/${name}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<Select
|
||||
items={$page.data.pkg.targets.map((target: TargetInfo) => ({
|
||||
value: target.kind,
|
||||
label: TARGET_KIND_DISPLAY_NAMES[target.kind],
|
||||
}))}
|
||||
value={$page.params.target ?? $page.data.pkg.targets[0].kind}
|
||||
contentClass={sameWidth ? "" : "w-32"}
|
||||
onValueChange={(target) => {
|
||||
disabled = true
|
||||
|
||||
const path = $page.data.activeTab === "docs" ? "docs/intro" : "reference"
|
||||
|
||||
goto(`${basePath}/${$page.data.pkg.version}/${target}/${path}`).finally(() => {
|
||||
disabled = false
|
||||
})
|
||||
}}
|
||||
bind:open
|
||||
{disabled}
|
||||
{sameWidth}
|
||||
{trigger}
|
||||
{id}
|
||||
/>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { TocItem } from "$lib/markdown"
|
||||
import TocList, { tocScroll } from "./TocList.svelte"
|
||||
|
||||
type Props = {
|
||||
toc: TocItem[]
|
||||
}
|
||||
|
||||
const { toc }: Props = $props()
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="hide-scrollbar sticky top-14 hidden h-[calc(100vh-theme(spacing.14))] w-72 flex-shrink-0 overflow-y-auto py-16 text-lg xl:block"
|
||||
use:tocScroll
|
||||
>
|
||||
<nav class="border-l pr-4 text-sm">
|
||||
<h2 class="text-heading mb-1 pl-4 text-base font-semibold">On this page</h2>
|
||||
<TocList {toc} />
|
||||
</nav>
|
||||
</aside>
|
|
@ -0,0 +1,45 @@
|
|||
<script module lang="ts">
|
||||
import type { Action } from "svelte/action"
|
||||
|
||||
export const tocScroll: Action<HTMLElement> = (node) => {
|
||||
$effect(() => {
|
||||
const link = node.querySelector(`[data-item-id="${activeHeaderId.value}"]`)
|
||||
|
||||
if (link && link instanceof HTMLElement) {
|
||||
setTimeout(() => {
|
||||
node.scrollTo({
|
||||
top: link.offsetTop + link.clientHeight - node.clientHeight / 2,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}, 20)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { TocItem } from "$lib/markdown"
|
||||
import { activeHeaderId } from "./TocObserver.svelte"
|
||||
|
||||
type Props = {
|
||||
toc: TocItem[]
|
||||
mobile?: boolean
|
||||
}
|
||||
|
||||
const { toc, mobile = false }: Props = $props()
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
{#each toc as item}
|
||||
{@const active = item.id === activeHeaderId.value}
|
||||
<li data-item-id={item.id}>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
class={`-ml-px block truncate pl-[calc(var(--level)*theme(spacing.4))] transition ${active ? "text-primary" : "hover:text-heading"} ${mobile ? "py-2" : "py-1"}`}
|
||||
style:--level={item.level - 1}
|
||||
>
|
||||
{item.title}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
|
@ -0,0 +1,80 @@
|
|||
<script module lang="ts">
|
||||
let _activeHeaderId = $state("")
|
||||
|
||||
export const activeHeaderId = {
|
||||
get value() {
|
||||
return _activeHeaderId
|
||||
},
|
||||
|
||||
set value(value) {
|
||||
_activeHeaderId = value
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { TocItem } from "$lib/markdown"
|
||||
|
||||
type Props = {
|
||||
toc: TocItem[]
|
||||
}
|
||||
|
||||
const { toc }: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
toc
|
||||
|
||||
const isHeading = (el: Element): boolean => {
|
||||
if (el.id === "_top") return true
|
||||
if (el instanceof HTMLHeadingElement) {
|
||||
const level = el.tagName[1]
|
||||
if (level) {
|
||||
const int = parseInt(level, 10)
|
||||
if (int >= 2 && int <= 3) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const getNearestHeading = (el: Element | null): Element | null => {
|
||||
if (!el) return null
|
||||
|
||||
while (el) {
|
||||
if (isHeading(el)) return el
|
||||
el = el.previousElementSibling
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const callback: IntersectionObserverCallback = (entries) => {
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) continue
|
||||
|
||||
const heading = getNearestHeading(entry.target)
|
||||
if (!heading) continue
|
||||
|
||||
const item = toc.find((item) => {
|
||||
if (item.id === "_top" && heading.id === "_top") return true
|
||||
return `user-content-${item.id}` === heading.id
|
||||
})
|
||||
if (item) {
|
||||
activeHeaderId.value = item.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(callback, {
|
||||
rootMargin: "-56px 0px -80% 0px",
|
||||
})
|
||||
|
||||
const targets = document.querySelectorAll("main > *")
|
||||
for (const target of targets) {
|
||||
observer.observe(target)
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation"
|
||||
import { page } from "$app/stores"
|
||||
import Select from "$lib/components/Select.svelte"
|
||||
import { fetchRegistryJson, type PackageVersionResponse } from "$lib/registry-api"
|
||||
import type { Snippet } from "svelte"
|
||||
|
||||
let disabled = $state(false)
|
||||
|
||||
type Props = {
|
||||
trigger?: Snippet<[Record<string, unknown>, string]>
|
||||
sameWidth?: boolean
|
||||
open?: boolean
|
||||
id?: string
|
||||
}
|
||||
|
||||
let { trigger, sameWidth = true, open = $bindable(false), id }: Props = $props()
|
||||
|
||||
const basePath = $derived.by(() => {
|
||||
const { scope, name } = $page.params
|
||||
return `/packages/${scope}/${name}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<Select
|
||||
items={$page.data.versions.map((v: string) => ({ value: v, label: v }))}
|
||||
value={$page.data.pkg.version}
|
||||
contentClass={sameWidth ? "" : "w-32"}
|
||||
onValueChange={(version) => {
|
||||
disabled = true
|
||||
|
||||
const path = $page.data.activeTab === "docs" ? "docs/intro" : "reference"
|
||||
|
||||
fetchRegistryJson<PackageVersionResponse>(
|
||||
`packages/${encodeURIComponent($page.data.pkg.name)}/${version}/any`,
|
||||
fetch,
|
||||
)
|
||||
.then((pkg) => goto(`${basePath}/${version}/${pkg.targets[0].kind}/${path}`))
|
||||
.finally(() => {
|
||||
disabled = false
|
||||
})
|
||||
}}
|
||||
bind:open
|
||||
{disabled}
|
||||
{sameWidth}
|
||||
{trigger}
|
||||
{id}
|
||||
/>
|
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
import { page } from "$app/stores"
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||
<h1 class="text-heading mb-1 text-4xl font-bold">{$page.status}</h1>
|
||||
<p class="text-lg">{$page.error?.message}</p>
|
||||
</div>
|
|
@ -0,0 +1,33 @@
|
|||
import type { DocEntry } from "$lib/registry-api"
|
||||
import type { SidebarItem } from "../Sidebar.svelte"
|
||||
import type { LayoutLoad } from "./$types"
|
||||
|
||||
export const load: LayoutLoad = async ({ params, parent }) => {
|
||||
const parentData = await parent()
|
||||
|
||||
const { scope, name, version, target } = params
|
||||
const basePath = `/packages/${scope}/${name}/${version ?? "latest"}/${target ?? "any"}`
|
||||
|
||||
function docEntryToSidebarItem(entry: DocEntry): SidebarItem {
|
||||
if ("name" in entry) {
|
||||
return {
|
||||
label: entry.label,
|
||||
href: `${basePath}/docs/${entry.name}`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: entry.label,
|
||||
children: entry.items?.map(docEntryToSidebarItem) ?? [],
|
||||
collapsed: entry.collapsed ?? false,
|
||||
}
|
||||
}
|
||||
|
||||
const sidebar = parentData.pkg.docs?.map(docEntryToSidebarItem) ?? []
|
||||
|
||||
return {
|
||||
activeTab: "docs",
|
||||
doc: params.doc,
|
||||
sidebar,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { redirect } from "@sveltejs/kit"
|
||||
import type { PageLoad } from "./$types"
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
redirect(301, "docs/intro")
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts">
|
||||
const { data } = $props()
|
||||
</script>
|
||||
|
||||
<main class="prose max-w-none py-16">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html data.html}
|
||||
</main>
|
|
@ -0,0 +1,62 @@
|
|||
import { docsMarkdown, type TocItem } from "$lib/markdown"
|
||||
import { fetchRegistry, RegistryHttpError, type DocEntry } from "$lib/registry-api"
|
||||
import { error } from "@sveltejs/kit"
|
||||
import { VFile } from "vfile"
|
||||
import type { PageLoad } from "./$types"
|
||||
|
||||
const findDocTitle = (docs: DocEntry[], name: string): string | undefined => {
|
||||
for (const doc of docs) {
|
||||
if ("name" in doc && doc.name === name) return doc.label
|
||||
if ("items" in doc && doc.items) {
|
||||
const title = findDocTitle(doc.items, name)
|
||||
if (title) return title
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const load: PageLoad = async ({ params, parent, fetch }) => {
|
||||
try {
|
||||
const page = await fetchRegistry(
|
||||
`packages/${encodeURIComponent(`${params.scope}/${params.name}`)}/${params.version ?? "latest"}/${params.target ?? "any"}?doc=${encodeURIComponent(params.doc)}`,
|
||||
fetch,
|
||||
).then((r) => r.text())
|
||||
|
||||
const inputFile = new VFile({
|
||||
path: `/docs/${params.doc}`,
|
||||
value: page,
|
||||
data: {
|
||||
basePath: `/packages/${params.scope}/${params.name}/${params.version ?? "latest"}/${params.target ?? "any"}`,
|
||||
},
|
||||
})
|
||||
|
||||
const file = await (await docsMarkdown).process(inputFile)
|
||||
|
||||
const html = file.value
|
||||
|
||||
const parentData = await parent()
|
||||
const docTitle = findDocTitle(parentData.pkg.docs ?? [], params.doc) ?? params.doc
|
||||
|
||||
return {
|
||||
html,
|
||||
toc: [
|
||||
{
|
||||
id: "_top",
|
||||
title: "Overview",
|
||||
level: 2,
|
||||
},
|
||||
...(file.data.toc as TocItem[]),
|
||||
],
|
||||
meta: {
|
||||
siteName: `${parentData.pkg.name} - pesde`,
|
||||
title: docTitle,
|
||||
description: (file.data.meta as { description: string }).description,
|
||||
},
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||
error(404, "Page not found")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import type { LayoutLoad } from "./$types"
|
||||
|
||||
export const load: LayoutLoad = async ({ params, parent }) => {
|
||||
const { scope, name, version, target } = params
|
||||
const basePath = `/packages/${scope}/${name}/${version ?? "latest"}/${target ?? "any"}`
|
||||
|
||||
const parentData = await parent()
|
||||
return {
|
||||
activeTab: "reference",
|
||||
sidebar: [
|
||||
{
|
||||
label: "Reference",
|
||||
href: `${basePath}/reference`,
|
||||
},
|
||||
],
|
||||
toc: [
|
||||
{
|
||||
id: "_top",
|
||||
title: "Overview",
|
||||
level: 2,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
siteName: `${parentData.pkg.name} - pesde`,
|
||||
title: "Reference",
|
||||
description: `API reference for ${parentData.pkg.name}`,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<script>
|
||||
import { Boxes } from "lucide-svelte"
|
||||
</script>
|
||||
|
||||
<main class="mx-auto max-w-screen-2xl px-4 py-48 text-center">
|
||||
<Boxes aria-hidden="true" class="mx-auto mb-8 size-16" />
|
||||
|
||||
<h1 class="text-heading mb-1 text-xl font-bold">Reference coming soon!</h1>
|
||||
<p>The reference will contain API documentation for packages.</p>
|
||||
</main>
|
|
@ -59,7 +59,7 @@
|
|||
<Command command={installCommand} class="mb-4" />
|
||||
|
||||
<div class="hidden lg:block">
|
||||
<TargetSelector id="target-selector-sidebar" />
|
||||
<TargetSelector />
|
||||
</div>
|
||||
|
||||
{#if data.pkg.license !== undefined}
|
|
@ -0,0 +1,51 @@
|
|||
import { markdown } from "$lib/markdown"
|
||||
import { fetchRegistry, RegistryHttpError } from "$lib/registry-api"
|
||||
import type { PageLoad } from "./$types"
|
||||
|
||||
const fetchReadme = async (
|
||||
fetcher: typeof fetch,
|
||||
name: string,
|
||||
version: string,
|
||||
target: string,
|
||||
) => {
|
||||
try {
|
||||
const res = await fetchRegistry(
|
||||
`packages/${encodeURIComponent(name)}/${version}/${target}`,
|
||||
fetcher,
|
||||
{
|
||||
headers: {
|
||||
Accept: "text/plain",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return res.text()
|
||||
} catch (e) {
|
||||
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||
return "*No README provided*"
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export const load: PageLoad = async ({ parent, fetch }) => {
|
||||
const { pkg } = await parent()
|
||||
const { name, version, targets } = pkg
|
||||
|
||||
const readmeText = await fetchReadme(fetch, name, version, targets[0].kind)
|
||||
|
||||
const file = await (await markdown)
|
||||
.process(readmeText)
|
||||
|
||||
const readmeHtml = file.value
|
||||
|
||||
return {
|
||||
readmeHtml,
|
||||
pkg,
|
||||
|
||||
meta: {
|
||||
title: `${name} - ${version}`,
|
||||
description: pkg.description,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -3,6 +3,6 @@
|
|||
</script>
|
||||
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||
<h1 class="mb-1 text-4xl font-bold text-heading">{$page.status}</h1>
|
||||
<h1 class="text-heading mb-1 text-4xl font-bold">{$page.status}</h1>
|
||||
<p class="text-lg">{$page.error?.message}</p>
|
||||
</div>
|
||||
|
|
|
@ -1,26 +1,64 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores"
|
||||
import Header from "./Header.svelte"
|
||||
|
||||
import "@fontsource-variable/nunito-sans"
|
||||
import "../app.css"
|
||||
import Footer from "./Footer.svelte"
|
||||
|
||||
const { children } = $props()
|
||||
|
||||
const siteName = $derived($page.data.meta?.siteName ?? "pesde")
|
||||
const title = $derived($page.data.meta?.title)
|
||||
const description = $derived(
|
||||
$page.data.meta?.description ??
|
||||
"A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune.",
|
||||
)
|
||||
|
||||
let themeColor = $state("#F19D1E")
|
||||
$effect(() => {
|
||||
const query = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
|
||||
const updateColor = (dark: boolean) => {
|
||||
themeColor = dark ? "#14100C" : "#FAEAD7"
|
||||
}
|
||||
|
||||
const listener = (e: MediaQueryListEvent) => {
|
||||
updateColor(e.matches)
|
||||
}
|
||||
|
||||
query.addEventListener("change", listener)
|
||||
updateColor(query.matches)
|
||||
|
||||
return () => query.removeEventListener("change", listener)
|
||||
})
|
||||
|
||||
function hashChange() {
|
||||
let hash
|
||||
|
||||
try {
|
||||
hash = decodeURIComponent(location.hash.slice(1)).toLowerCase()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
const id = "user-content-" + hash
|
||||
const target = document.getElementById(id)
|
||||
|
||||
if (target) {
|
||||
target.scrollIntoView()
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
hashChange()
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{title ? `${title} - pesde` : "pesde"}</title>
|
||||
<title>{title ? `${title} - ${siteName}` : siteName}</title>
|
||||
<meta name="description" content={description} />
|
||||
<meta name="theme-color" content="#F19D1E" />
|
||||
<meta name="theme-color" content={themeColor} />
|
||||
|
||||
<meta property="og:site_name" content="pesde" />
|
||||
<meta property="og:site_name" content={siteName} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={title ?? "Manage your packages for Luau"} />
|
||||
<meta property="og:description" content={description} />
|
||||
|
@ -35,10 +73,6 @@
|
|||
<link rel="manifest" href="/site.webmanifest" />
|
||||
</svelte:head>
|
||||
|
||||
<Header />
|
||||
<svelte:window onhashchange={hashChange} />
|
||||
|
||||
<main class="min-h-screen">
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
{@render children()}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { navigating } from "$app/stores"
|
||||
import GitHub from "$lib/components/GitHub.svelte"
|
||||
import Logo from "$lib/components/Logo.svelte"
|
||||
import { Dialog } from "bits-ui"
|
||||
import { Menu, X } from "lucide-svelte"
|
||||
import { fade, fly } from "svelte/transition"
|
||||
import Search from "./Search.svelte"
|
||||
|
||||
let dialogOpen = $state(false)
|
||||
|
||||
$effect(() => {
|
||||
if ($navigating) {
|
||||
dialogOpen = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Trigger>
|
||||
<span class="sr-only">open menu</span>
|
||||
<Menu aria-hidden="true" />
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content class="fixed inset-0 top-0 z-50 flex flex-col">
|
||||
<Dialog.Title class="sr-only">Menu</Dialog.Title>
|
||||
<div transition:fade={{ duration: 200 }} class="bg-background">
|
||||
<div class="relative z-50 flex h-14 flex-shrink-0 items-center justify-between px-4">
|
||||
<a href="/">
|
||||
<Logo class="text-primary h-7" />
|
||||
</a>
|
||||
<Dialog.Close>
|
||||
<span class="sr-only">close menu</span>
|
||||
<X aria-hidden="true" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<div class="px-4 py-1">
|
||||
<Search />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-background flex flex-grow flex-col overflow-hidden"
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<nav class="flex h-full flex-col px-4 pt-2" transition:fly={{ y: "-2%", duration: 200 }}>
|
||||
<div class="flex flex-grow flex-col border-y py-3">
|
||||
{#snippet item(href: string, text: string)}
|
||||
<a {href} class="hover:bg-card/50 flex h-10 items-center rounded px-3">{text}</a>
|
||||
{/snippet}
|
||||
|
||||
{@render item("https://docs.pesde.daimond113.com/", "Documentation")}
|
||||
{@render item("https://docs.pesde.daimond113.com/registry/policies", "Policies")}
|
||||
</div>
|
||||
<div class="flex items-center py-5">
|
||||
<a href="https://github.com/daimond113/pesde" target="_blank" rel="noreferrer noopener">
|
||||
<GitHub class="size-6" />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
|
@ -1,35 +0,0 @@
|
|||
<script>
|
||||
import GitHub from "$lib/components/GitHub.svelte"
|
||||
import Logo from "$lib/components/Logo.svelte"
|
||||
import Hamburger from "./Hamburger.svelte"
|
||||
import Search from "./Search.svelte"
|
||||
</script>
|
||||
|
||||
<header class="pt-14 sm:pt-16">
|
||||
<div class="bg-background/80 fixed inset-x-0 top-0 z-50 backdrop-blur">
|
||||
<div class="mx-auto flex h-14 max-w-screen-lg items-center justify-between px-4 sm:h-16">
|
||||
<a href="/">
|
||||
<Logo class="text-primary h-7 sm:h-9" />
|
||||
</a>
|
||||
<div class="hidden w-full max-w-80 sm:flex">
|
||||
<Search />
|
||||
</div>
|
||||
<div class="[&_a:hover]:text-heading hidden items-center space-x-6 sm:flex [&_a]:transition">
|
||||
<nav class="flex items-center space-x-6 font-medium">
|
||||
<a href="https://docs.pesde.daimond113.com/">Docs</a>
|
||||
<a href="https://docs.pesde.daimond113.com/registry/policies">Policies</a>
|
||||
</nav>
|
||||
|
||||
<a href="https://github.com/daimond113/pesde" target="_blank" rel="noreferrer noopener">
|
||||
<GitHub class="size-6" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center sm:hidden">
|
||||
<Hamburger />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-1 sm:hidden">
|
||||
<Search />
|
||||
</div>
|
||||
</header>
|
|
@ -1,51 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation"
|
||||
import { page } from "$app/stores"
|
||||
import { TARGET_KIND_DISPLAY_NAMES, type TargetInfo, type TargetKind } from "$lib/registry-api"
|
||||
import { ChevronDownIcon } from "lucide-svelte"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { id }: { id: string } = $props()
|
||||
|
||||
const currentTarget = getContext<{ value: TargetInfo }>("currentTarget")
|
||||
|
||||
const basePath = $derived.by(() => {
|
||||
const { scope, name } = $page.params
|
||||
if ("target" in $page.params) {
|
||||
const { version } = $page.params
|
||||
return `/packages/${scope}/${name}/${version}`
|
||||
}
|
||||
return `/packages/${scope}/${name}/latest`
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="text-heading mb-1 text-lg font-semibold">
|
||||
<label for={id}>Target</label>
|
||||
</div>
|
||||
<div
|
||||
class="border-input-border bg-input-bg ring-primary-bg/20 focus-within:border-primary relative mb-6 flex h-11 w-full items-center rounded border ring-0 transition focus-within:ring-4 has-[:disabled]:opacity-50"
|
||||
>
|
||||
<select
|
||||
class="absolute inset-0 appearance-none bg-transparent px-4 outline-none"
|
||||
{id}
|
||||
onchange={(e) => {
|
||||
const select = e.currentTarget
|
||||
|
||||
select.disabled = true
|
||||
goto(`${basePath}/${e.currentTarget.value}`).finally(() => {
|
||||
select.disabled = false
|
||||
})
|
||||
}}
|
||||
>
|
||||
{#each $page.data.pkg.targets as target}
|
||||
<option
|
||||
value={target.kind}
|
||||
class="bg-card"
|
||||
selected={target.kind === currentTarget.value.kind}
|
||||
>
|
||||
{TARGET_KIND_DISPLAY_NAMES[target.kind as TargetKind]}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<ChevronDownIcon aria-hidden="true" class="pointer-events-none absolute right-4 h-5 w-5" />
|
||||
</div>
|
|
@ -1,82 +0,0 @@
|
|||
import { fetchRegistry, RegistryHttpError } from "$lib/registry-api"
|
||||
import rehypeShikiFromHighlighter from "@shikijs/rehype/core"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import rehypeSanitize from "rehype-sanitize"
|
||||
import rehypeStringify from "rehype-stringify"
|
||||
import remarkGemoji from "remark-gemoji"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkRehype from "remark-rehype"
|
||||
import { createCssVariablesTheme, createHighlighter } from "shiki"
|
||||
import { unified } from "unified"
|
||||
import type { PageLoad } from "./$types"
|
||||
|
||||
const fetchReadme = async (
|
||||
fetcher: typeof fetch,
|
||||
name: string,
|
||||
version: string,
|
||||
target: string,
|
||||
) => {
|
||||
try {
|
||||
const res = await fetchRegistry(
|
||||
`packages/${encodeURIComponent(name)}/${version}/${target}`,
|
||||
fetcher,
|
||||
{
|
||||
headers: {
|
||||
Accept: "text/plain",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return res.text()
|
||||
} catch (e) {
|
||||
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||
return "*No README provided*"
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export const load: PageLoad = async ({ parent, fetch }) => {
|
||||
const { pkg } = await parent()
|
||||
const { name, version, targets } = pkg
|
||||
|
||||
const readmeText = await fetchReadme(fetch, name, version, targets[0].kind)
|
||||
|
||||
const highlighter = await createHighlighter({
|
||||
themes: [],
|
||||
langs: [],
|
||||
})
|
||||
|
||||
const file = await unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkGemoji)
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeShikiFromHighlighter, highlighter, {
|
||||
lazy: true,
|
||||
theme: createCssVariablesTheme({
|
||||
name: "css-variables",
|
||||
variablePrefix: "--shiki-",
|
||||
variableDefaults: {},
|
||||
fontStyle: true,
|
||||
}),
|
||||
fallbackLanguage: "text",
|
||||
})
|
||||
.use(rehypeStringify)
|
||||
.process(readmeText)
|
||||
|
||||
const readmeHtml = file.value
|
||||
|
||||
return {
|
||||
readmeHtml,
|
||||
pkg,
|
||||
|
||||
meta: {
|
||||
title: `${name} - ${version}`,
|
||||
description: pkg.description,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ export default {
|
|||
hover: "rgb(var(--color-card-hover) / <alpha-value>)",
|
||||
},
|
||||
border: "rgb(var(--color-border) / <alpha-value>)",
|
||||
header: "rgb(var(--color-header) / <alpha-value>)",
|
||||
|
||||
body: "rgb(var(--color-body) / <alpha-value>)",
|
||||
heading: "rgb(var(--color-heading) / <alpha-value>)",
|
||||
|
|
Loading…
Add table
Reference in a new issue