@twango/twiki 0.0.0-stage → 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.github/actions/product-cache/action.yml +32 -0
  2. package/.github/actions/product-cache/index.mjs +450 -0
  3. package/LICENSE +21 -0
  4. package/README.md +146 -2
  5. package/bun.lock +1701 -0
  6. package/cli/runtime.test.ts +420 -0
  7. package/cli/runtime.ts +385 -0
  8. package/cli/twiki.ts +305 -0
  9. package/package.json +138 -4
  10. package/site/_headers +7 -0
  11. package/site/assets/fonts/OFL.txt +93 -0
  12. package/site/assets/fonts/OverusedGrotesk-SemiBold.ttf +0 -0
  13. package/site/assets/social-background.svg +38 -0
  14. package/site/bun.lock +1701 -0
  15. package/site/database/index.ts +7 -0
  16. package/site/database/schema.ts +95 -0
  17. package/site/drizzle.config.ts +7 -0
  18. package/site/migrations/0001_auth.sql +14 -0
  19. package/site/migrations/0002_site_access.sql +5 -0
  20. package/site/migrations/0003_github_username.sql +1 -0
  21. package/site/migrations/meta/0002_snapshot.json +432 -0
  22. package/site/migrations/meta/0003_snapshot.json +439 -0
  23. package/site/migrations/meta/_journal.json +27 -0
  24. package/site/package.json +120 -0
  25. package/site/src/app.d.ts +14 -0
  26. package/site/src/app.html +11 -0
  27. package/site/src/hooks.server.ts +12 -0
  28. package/site/src/lib/analytics-config.json +1 -0
  29. package/site/src/lib/analytics.ts +40 -0
  30. package/site/src/lib/auth-config.json +3 -0
  31. package/site/src/lib/components/SEO.svelte +113 -0
  32. package/site/src/lib/components/doc/AccessLink.svelte +27 -0
  33. package/site/src/lib/components/doc/Backlinks.svelte +59 -0
  34. package/site/src/lib/components/doc/Breadcrumbs.svelte +29 -0
  35. package/site/src/lib/components/doc/ContentMeta.svelte +61 -0
  36. package/site/src/lib/components/doc/DocPager.svelte +91 -0
  37. package/site/src/lib/components/doc/DocShell.svelte +115 -0
  38. package/site/src/lib/components/doc/LicenseNotice.svelte +26 -0
  39. package/site/src/lib/components/doc/LockedContent.svelte +29 -0
  40. package/site/src/lib/components/doc/NavTree.svelte +158 -0
  41. package/site/src/lib/components/doc/NoteActions.svelte +139 -0
  42. package/site/src/lib/components/doc/PublicationBadge.svelte +49 -0
  43. package/site/src/lib/components/doc/ReaderModeToggle.svelte +19 -0
  44. package/site/src/lib/components/doc/Sidebar.svelte +351 -0
  45. package/site/src/lib/components/doc/Toc.svelte +339 -0
  46. package/site/src/lib/components/doc/enhancements.ts +146 -0
  47. package/site/src/lib/components/doc/heading-tracker.svelte.ts +39 -0
  48. package/site/src/lib/components/doc/image-preview.ts +114 -0
  49. package/site/src/lib/components/doc/reader-mode.svelte.ts +40 -0
  50. package/site/src/lib/components/embeds/mermaid.ts +353 -0
  51. package/site/src/lib/components/files/FileCode.svelte +81 -0
  52. package/site/src/lib/components/files/FileExplorer.svelte +74 -0
  53. package/site/src/lib/components/files/FileHistory.svelte +170 -0
  54. package/site/src/lib/components/files/FileIcon.svelte +28 -0
  55. package/site/src/lib/components/files/FileRmd.svelte +209 -0
  56. package/site/src/lib/components/files/FileTabs.svelte +180 -0
  57. package/site/src/lib/components/files/FileTree.svelte +67 -0
  58. package/site/src/lib/components/files/blame-gutter.ts +45 -0
  59. package/site/src/lib/components/files/code-view.ts +98 -0
  60. package/site/src/lib/components/files/file-workspace.svelte.ts +100 -0
  61. package/site/src/lib/components/graph/GraphPanel.svelte +275 -0
  62. package/site/src/lib/components/graph/GraphView.svelte +100 -0
  63. package/site/src/lib/components/graph/graph-data.ts +137 -0
  64. package/site/src/lib/components/graph/graph-model.ts +103 -0
  65. package/site/src/lib/components/graph/index.ts +3 -0
  66. package/site/src/lib/components/graph/render-graph.ts +626 -0
  67. package/site/src/lib/components/popover/index.ts +1 -0
  68. package/site/src/lib/components/popover/link-popovers.ts +233 -0
  69. package/site/src/lib/components/popover/popover.css +82 -0
  70. package/site/src/lib/components/search/SearchButton.svelte +57 -0
  71. package/site/src/lib/components/search/SearchPalette.svelte +189 -0
  72. package/site/src/lib/components/search/index.ts +3 -0
  73. package/site/src/lib/components/search/pagefind-client.svelte.ts +129 -0
  74. package/site/src/lib/components/search/pagefind-query.ts +22 -0
  75. package/site/src/lib/components/search/pagefind-url.ts +12 -0
  76. package/site/src/lib/components/search/search-state.svelte.ts +22 -0
  77. package/site/src/lib/components/ui/IconButton.svelte +30 -0
  78. package/site/src/lib/components/ui/ThemeToggle.svelte +22 -0
  79. package/site/src/lib/components/ui/index.ts +1 -0
  80. package/site/src/lib/config.ts +9 -0
  81. package/site/src/lib/content-license.ts +7 -0
  82. package/site/src/lib/file-history.ts +37 -0
  83. package/site/src/lib/file-icons.ts +27 -0
  84. package/site/src/lib/file-tabs.ts +72 -0
  85. package/site/src/lib/files.ts +73 -0
  86. package/site/src/lib/github-user.ts +70 -0
  87. package/site/src/lib/metadata.ts +166 -0
  88. package/site/src/lib/notebook-nav.ts +63 -0
  89. package/site/src/lib/publication.ts +1 -0
  90. package/site/src/lib/rmd.ts +156 -0
  91. package/site/src/lib/server/access-admin.ts +33 -0
  92. package/site/src/lib/server/content-model.ts +253 -0
  93. package/site/src/lib/server/content-provider.ts +266 -0
  94. package/site/src/lib/server/content.ts +277 -0
  95. package/site/src/lib/server/runtime-content.ts +34 -0
  96. package/site/src/lib/site-config.json +9 -0
  97. package/site/src/lib/social-image.ts +13 -0
  98. package/site/src/lib/styles/article.css +607 -0
  99. package/site/src/lib/styles/foundation.css +138 -0
  100. package/site/src/lib/styles/print.css +115 -0
  101. package/site/src/lib/styles/shell.css +182 -0
  102. package/site/src/lib/text-exports.ts +5 -0
  103. package/site/src/lib/types.ts +140 -0
  104. package/site/src/lib/utils.ts +6 -0
  105. package/site/src/routes/+error.svelte +16 -0
  106. package/site/src/routes/+layout.server.ts +10 -0
  107. package/site/src/routes/+layout.svelte +72 -0
  108. package/site/src/routes/+layout.ts +3 -0
  109. package/site/src/routes/404/+page.svelte +13 -0
  110. package/site/src/routes/[...slug]/+page.server.ts +24 -0
  111. package/site/src/routes/[...slug]/+page.svelte +180 -0
  112. package/site/src/routes/[course]/files/[...file]/+page.svelte +373 -0
  113. package/site/src/routes/[course]/files/[...file]/+page.ts +38 -0
  114. package/site/src/routes/admin/access/+page.server.ts +53 -0
  115. package/site/src/routes/admin/access/+page.svelte +118 -0
  116. package/site/src/routes/graph.json/+server.ts +7 -0
  117. package/site/src/routes/index.xml/+server.ts +7 -0
  118. package/site/src/routes/layout.css +10 -0
  119. package/site/src/routes/robots.txt/+server.ts +9 -0
  120. package/site/src/routes/sitemap.xml/+server.ts +7 -0
  121. package/site/src/routes/tags/+page.server.ts +5 -0
  122. package/site/src/routes/tags/+page.svelte +38 -0
  123. package/site/src/routes/tags/[...tag]/+page.server.ts +22 -0
  124. package/site/src/routes/tags/[...tag]/+page.svelte +52 -0
  125. package/site/static/favicon.png +0 -0
  126. package/site/static/fonts/JetBrainsMono-Italic-VF.woff2 +0 -0
  127. package/site/static/fonts/JetBrainsMono-VF.woff2 +0 -0
  128. package/site/static/fonts/OverusedGrotesk-VF.woff2 +0 -0
  129. package/site/static/fonts/OverusedGroteskItalic-VF.woff2 +0 -0
  130. package/site/static/fonts/Virgil.woff2 +0 -0
  131. package/site/svelte.config.js +71 -0
  132. package/site/tooling/LICENSE-quartz +25 -0
  133. package/site/tooling/adapter.js +55 -0
  134. package/site/tooling/assets.ts +323 -0
  135. package/site/tooling/benchmark.ts +191 -0
  136. package/site/tooling/build.ts +407 -0
  137. package/site/tooling/cache.ts +133 -0
  138. package/site/tooling/check-diagrams.ts +164 -0
  139. package/site/tooling/check-links.ts +86 -0
  140. package/site/tooling/check-targets.ts +139 -0
  141. package/site/tooling/compiler.ts +1386 -0
  142. package/site/tooling/deploy.ts +112 -0
  143. package/site/tooling/fingerprint.ts +81 -0
  144. package/site/tooling/lib/app-version.js +92 -0
  145. package/site/tooling/lib/application-asset-urls.ts +27 -0
  146. package/site/tooling/lib/application-cache.ts +269 -0
  147. package/site/tooling/lib/asset-hash-cache.ts +116 -0
  148. package/site/tooling/lib/autotag.ts +99 -0
  149. package/site/tooling/lib/build-assembly.ts +148 -0
  150. package/site/tooling/lib/build-workspace.ts +386 -0
  151. package/site/tooling/lib/cache-selection.ts +70 -0
  152. package/site/tooling/lib/cloudflare-prewarm.js +40 -0
  153. package/site/tooling/lib/comments.ts +32 -0
  154. package/site/tooling/lib/content-cache.ts +244 -0
  155. package/site/tooling/lib/content-urls.ts +50 -0
  156. package/site/tooling/lib/course-cache.ts +298 -0
  157. package/site/tooling/lib/course-files.ts +281 -0
  158. package/site/tooling/lib/currency.ts +33 -0
  159. package/site/tooling/lib/deployment-config.ts +222 -0
  160. package/site/tooling/lib/deployment-manifest.ts +274 -0
  161. package/site/tooling/lib/deployment-prepare.ts +325 -0
  162. package/site/tooling/lib/deployment-upload.ts +293 -0
  163. package/site/tooling/lib/dev-git-state.ts +130 -0
  164. package/site/tooling/lib/dev-history.ts +86 -0
  165. package/site/tooling/lib/dev-social-images.ts +38 -0
  166. package/site/tooling/lib/dev-state.ts +107 -0
  167. package/site/tooling/lib/edition-builds.ts +184 -0
  168. package/site/tooling/lib/edition-paths.js +29 -0
  169. package/site/tooling/lib/empty-tag-route.js +23 -0
  170. package/site/tooling/lib/file-history.ts +201 -0
  171. package/site/tooling/lib/file-icons.ts +93 -0
  172. package/site/tooling/lib/file-policy.ts +38 -0
  173. package/site/tooling/lib/file-search.js +13 -0
  174. package/site/tooling/lib/git-revisions.ts +41 -0
  175. package/site/tooling/lib/history-pool.ts +74 -0
  176. package/site/tooling/lib/history-worker.ts +22 -0
  177. package/site/tooling/lib/knit-rmd.R +38 -0
  178. package/site/tooling/lib/lastmod.ts +201 -0
  179. package/site/tooling/lib/markdown-files.ts +51 -0
  180. package/site/tooling/lib/ofm.ts +634 -0
  181. package/site/tooling/lib/output.ts +60 -0
  182. package/site/tooling/lib/product-cache.ts +1006 -0
  183. package/site/tooling/lib/project-config.ts +288 -0
  184. package/site/tooling/lib/public-assets.ts +38 -0
  185. package/site/tooling/lib/public-links.ts +56 -0
  186. package/site/tooling/lib/publishing.ts +156 -0
  187. package/site/tooling/lib/r-runtime.ts +257 -0
  188. package/site/tooling/lib/rebuild-queue.ts +25 -0
  189. package/site/tooling/lib/rmd-preflight.ts +166 -0
  190. package/site/tooling/lib/rmd-preview-cache.ts +93 -0
  191. package/site/tooling/lib/rmd-previews.ts +129 -0
  192. package/site/tooling/lib/runtime-data-modules.ts +30 -0
  193. package/site/tooling/lib/runtime-packaging.ts +321 -0
  194. package/site/tooling/lib/search-cache.js +165 -0
  195. package/site/tooling/lib/search-inputs.js +129 -0
  196. package/site/tooling/lib/slug.ts +377 -0
  197. package/site/tooling/lib/social-images.ts +210 -0
  198. package/site/tooling/lib/text-exports.ts +51 -0
  199. package/site/tooling/lib/visit-elements.ts +17 -0
  200. package/site/tooling/live-test.ts +138 -0
  201. package/site/tooling/pipeline.ts +103 -0
  202. package/site/tooling/prepare.ts +3 -0
  203. package/site/tooling/publish.ts +28 -0
  204. package/site/tooling/r-cache.ts +45 -0
  205. package/site/tooling/r-render-profile.json +7 -0
  206. package/site/tooling/search.js +94 -0
  207. package/site/tooling/verify-deployment.ts +109 -0
  208. package/site/tooling/vite-content.ts +240 -0
  209. package/site/tooling/wrangler-paths.js +25 -0
  210. package/site/tooling/wrangler.ts +19 -0
  211. package/site/tsconfig.json +16 -0
  212. package/site/vite.config.ts +40 -0
  213. package/site/worker/access.ts +57 -0
  214. package/site/worker/analytics.ts +100 -0
  215. package/site/worker/auth-routes.ts +78 -0
  216. package/site/worker/auth.ts +78 -0
  217. package/site/worker/content-runtime.ts +226 -0
  218. package/site/worker/framing.ts +4 -0
  219. package/site/worker/gate.ts +147 -0
  220. package/site/worker/index.ts +9 -0
  221. package/site/worker/login.ts +62 -0
  222. package/site/worker/publication.ts +44 -0
  223. package/site/worker/runtime.ts +104 -0
  224. package/site/worker/tsconfig.json +13 -0
  225. package/site/wrangler.jsonc +37 -0
@@ -0,0 +1,118 @@
1
+ <script lang="ts">
2
+ import { site } from '$lib/config';
3
+ import { enhance } from '$app/forms';
4
+ import type { SubmitFunction } from '@sveltejs/kit';
5
+ import { lookupGithubUser } from '$lib/github-user';
6
+ import ShieldCheck from '@lucide/svelte/icons/shield-check';
7
+ import UserPlus from '@lucide/svelte/icons/user-plus';
8
+ import type { PageProps } from './$types';
9
+
10
+ let { data, form }: PageProps = $props();
11
+ let adding = $state(false);
12
+ let lookupError = $state('');
13
+
14
+ const addAccess: SubmitFunction = async ({ formData, cancel }) => {
15
+ if (adding) return cancel();
16
+ adding = true;
17
+ lookupError = '';
18
+ form = null;
19
+ const result = await lookupGithubUser(formData.get('username'));
20
+ if ('error' in result) {
21
+ lookupError = result.error;
22
+ adding = false;
23
+ return cancel();
24
+ }
25
+ formData.set('githubId', result.user.id);
26
+ formData.set('githubLogin', result.user.login);
27
+ return async ({ update }) => {
28
+ try {
29
+ await update();
30
+ } finally {
31
+ adding = false;
32
+ }
33
+ };
34
+ };
35
+ </script>
36
+
37
+ <svelte:head><title>Access · {site.title}</title></svelte:head>
38
+
39
+ <section class="mx-auto w-full max-w-xl">
40
+ <header class="mb-6">
41
+ <h1 class="mb-2 text-3xl font-semibold tracking-tight">Access</h1>
42
+ <p class="text-sm text-muted">
43
+ Approved people can read all notes. Only you can manage access.
44
+ </p>
45
+ </header>
46
+
47
+ <form method="POST" action="?/add" use:enhance={addAccess} class="mb-6">
48
+ <label for="github-username" class="mb-2 block text-sm font-medium">GitHub username</label>
49
+ <div class="flex gap-2">
50
+ <input
51
+ id="github-username"
52
+ name="username"
53
+ disabled={adding}
54
+ required
55
+ maxlength="40"
56
+ autocomplete="off"
57
+ autocapitalize="none"
58
+ spellcheck="false"
59
+ placeholder="username"
60
+ class="min-w-0 flex-1 rounded-lg border border-border bg-bg px-3 py-2 text-sm outline-none focus:border-accent focus:ring-1 focus:ring-accent"
61
+ />
62
+ <button
63
+ disabled={adding}
64
+ class="flex shrink-0 items-center gap-2 rounded-lg bg-text px-3 py-2 text-sm font-medium text-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
65
+ ><UserPlus size={16} />{adding ? 'Adding…' : 'Add access'}</button
66
+ >
67
+ </div>
68
+ </form>
69
+
70
+ <noscript><p>Enable JavaScript to look up GitHub accounts and add access.</p></noscript>
71
+ {#if lookupError || form?.error}<p role="alert" class="mb-4 text-sm text-accent">
72
+ {lookupError || form?.error}
73
+ </p>{/if}
74
+ {#if form?.message}<p role="status" class="mb-4 text-sm text-muted">{form.message}</p>{/if}
75
+
76
+ <ul
77
+ class="m-0 list-none divide-y divide-border border-y border-border p-0"
78
+ aria-label="People with access"
79
+ >
80
+ <li class="flex items-center justify-between gap-3 py-4">
81
+ <div>
82
+ <p class="font-medium">You</p>
83
+ <p class="text-xs text-muted">GitHub ID {data.ownerId}</p>
84
+ </div>
85
+ <span class="flex items-center gap-1.5 text-xs text-muted"
86
+ ><ShieldCheck size={14} />Owner</span
87
+ >
88
+ </li>
89
+ {#each data.grants as grant (grant.githubId)}
90
+ <li class="flex items-center justify-between gap-3 py-4">
91
+ <div class="min-w-0">
92
+ <a
93
+ class="font-medium text-text hover:text-accent"
94
+ href={`https://github.com/${grant.githubLogin}`}
95
+ target="_blank"
96
+ rel="noreferrer">@{grant.githubLogin}</a
97
+ >
98
+ <p class="text-xs text-muted">GitHub ID {grant.githubId}</p>
99
+ </div>
100
+ <form
101
+ method="POST"
102
+ action="?/revoke"
103
+ use:enhance={({ cancel }) => {
104
+ if (!confirm(`Revoke @${grant.githubLogin}'s access and sign out their sessions?`))
105
+ cancel();
106
+ }}
107
+ >
108
+ <input type="hidden" name="githubId" value={grant.githubId} />
109
+ <button
110
+ class="rounded-md px-2 py-1 text-sm text-muted hover:bg-accent-soft hover:text-accent focus-visible:outline-2 focus-visible:outline-accent"
111
+ aria-label={`Revoke @${grant.githubLogin}`}>Revoke</button
112
+ >
113
+ </form>
114
+ </li>
115
+ {/each}
116
+ </ul>
117
+ {#if !data.grants.length}<p class="mt-4 text-sm text-muted">Only you have access.</p>{/if}
118
+ </section>
@@ -0,0 +1,7 @@
1
+ import type { RequestHandler } from './$types';
2
+ import { requestContent } from '$lib/server/runtime-content';
3
+ export const prerender = import.meta.env.VITE_STATIC_EXPORT === 'true';
4
+ export const GET: RequestHandler = async ({ platform }) =>
5
+ new Response(JSON.stringify((await requestContent(platform).manifest()).graph), {
6
+ headers: { 'Content-Type': 'application/json' }
7
+ });
@@ -0,0 +1,7 @@
1
+ import type { RequestHandler } from './$types';
2
+ import { requestContent } from '$lib/server/runtime-content';
3
+ export const prerender = import.meta.env.VITE_STATIC_EXPORT === 'true';
4
+ export const GET: RequestHandler = async ({ platform }) =>
5
+ new Response((await requestContent(platform).model()).rssXml(), {
6
+ headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' }
7
+ });
@@ -0,0 +1,10 @@
1
+ @import 'tailwindcss';
2
+ @source not '../../docs';
3
+ @source not '../../tests';
4
+ @source not '../../benchmarks';
5
+ @import '../lib/styles/foundation.css';
6
+ @import '../lib/styles/shell.css';
7
+ @import '../lib/styles/article.css';
8
+ @import '../lib/styles/print.css';
9
+ @plugin '@tailwindcss/typography';
10
+ @custom-variant dark (&:where(:root.dark, :root.dark *));
@@ -0,0 +1,9 @@
1
+ import { site } from '$lib/config';
2
+
3
+ export const prerender = import.meta.env.VITE_STATIC_EXPORT === 'true';
4
+
5
+ export function GET() {
6
+ return new Response(`User-agent: *\nAllow: /\nSitemap: ${site.url}/sitemap.xml\n`, {
7
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' }
8
+ });
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { RequestHandler } from './$types';
2
+ import { requestContent } from '$lib/server/runtime-content';
3
+ export const prerender = import.meta.env.VITE_STATIC_EXPORT === 'true';
4
+ export const GET: RequestHandler = async ({ platform }) =>
5
+ new Response((await requestContent(platform).model()).sitemapXml(), {
6
+ headers: { 'Content-Type': 'application/xml; charset=utf-8' }
7
+ });
@@ -0,0 +1,5 @@
1
+ import type { PageServerLoad } from './$types';
2
+ import { requestContent } from '$lib/server/runtime-content';
3
+ export const load: PageServerLoad = async ({ platform }) => ({
4
+ tags: (await requestContent(platform).model()).tagIndex()
5
+ });
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import SEO from '$lib/components/SEO.svelte';
3
+ import Breadcrumbs from '$lib/components/doc/Breadcrumbs.svelte';
4
+ import type { PageData } from './$types';
5
+
6
+ interface Props {
7
+ data: PageData;
8
+ }
9
+
10
+ const { data }: Props = $props();
11
+ </script>
12
+
13
+ <SEO
14
+ title="Tag Index"
15
+ description="Browse topics across notes and reference material."
16
+ canonical="/tags"
17
+ />
18
+
19
+ <Breadcrumbs route="tags" current="Tag Index" />
20
+
21
+ <article class="prose dark:prose-invert max-w-none">
22
+ <h1>Tag Index</h1>
23
+ <p class="text-muted">Found {data.tags.length} total tags.</p>
24
+ </article>
25
+
26
+ <ul class="m-0 mt-4 flex list-none flex-wrap gap-2 p-0">
27
+ {#each data.tags as { tag, count } (tag)}
28
+ <li>
29
+ <a
30
+ class="inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1 text-sm text-text no-underline transition-colors hover:border-accent hover:text-accent"
31
+ href={`/tags/${tag}`}
32
+ >
33
+ <span>{tag}</span>
34
+ <span class="text-xs text-muted">{count}</span>
35
+ </a>
36
+ </li>
37
+ {/each}
38
+ </ul>
@@ -0,0 +1,22 @@
1
+ import { error } from '@sveltejs/kit';
2
+ import type { EntryGenerator, PageServerLoad } from './$types';
3
+ import { requestContent } from '$lib/server/runtime-content';
4
+ export const entries: EntryGenerator = async () => {
5
+ if (import.meta.env.VITE_STATIC_EXPORT !== 'true') return [];
6
+ return Object.keys((await requestContent(undefined).manifest()).tags).map((tag) => ({ tag }));
7
+ };
8
+ export const load: PageServerLoad = async ({ params, platform }) => {
9
+ const model = await requestContent(platform).model();
10
+ const pages = model.tagPages(params.tag);
11
+ if (!pages) error(404, 'Tag not found');
12
+ return {
13
+ tag: params.tag,
14
+ pages: pages.map((p) => ({
15
+ route: `/${model.displayRoute(p.slug)}`,
16
+ title: p.title,
17
+ description: p.description,
18
+ modified: p.dates.modified,
19
+ tags: p.tags
20
+ }))
21
+ };
22
+ };
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import SEO from '$lib/components/SEO.svelte';
3
+ import Breadcrumbs from '$lib/components/doc/Breadcrumbs.svelte';
4
+ import type { PageData } from './$types';
5
+
6
+ interface Props {
7
+ data: PageData;
8
+ }
9
+
10
+ const { data }: Props = $props();
11
+
12
+ function shortDate(iso: string): string {
13
+ if (!iso) return '';
14
+ return new Date(iso).toLocaleDateString('en-US', {
15
+ year: 'numeric',
16
+ month: 'short',
17
+ day: 'numeric'
18
+ });
19
+ }
20
+ </script>
21
+
22
+ <SEO
23
+ title={`Tag: ${data.tag}`}
24
+ description={`Browse course notes and reference material tagged ${data.tag}.`}
25
+ canonical={`/tags/${data.tag}`}
26
+ />
27
+
28
+ <Breadcrumbs route={`tags/${data.tag}`} current={`Tag: ${data.tag}`} />
29
+
30
+ <article class="prose dark:prose-invert max-w-none">
31
+ <h1>Tag: {data.tag}</h1>
32
+ <p class="text-muted">
33
+ {data.pages.length}
34
+ {data.pages.length === 1 ? 'item' : 'items'} with this tag.
35
+ </p>
36
+ </article>
37
+
38
+ <ul class="m-0 mt-4 list-none space-y-1 p-0">
39
+ {#each data.pages as page (page.route)}
40
+ <li>
41
+ <a class="block rounded-md px-3 py-2 no-underline hover:bg-surface" href={page.route}>
42
+ <span class="flex items-baseline justify-between gap-3">
43
+ <span class="min-w-0 truncate text-sm font-medium text-text">{page.title}</span>
44
+ <span class="shrink-0 text-xs text-muted">{shortDate(page.modified)}</span>
45
+ </span>
46
+ {#if page.description}
47
+ <span class="mt-0.5 line-clamp-1 block text-xs text-muted">{page.description}</span>
48
+ {/if}
49
+ </a>
50
+ </li>
51
+ {/each}
52
+ </ul>
Binary file
Binary file
@@ -0,0 +1,71 @@
1
+ import site from './src/lib/site-config.json' with { type: 'json' };
2
+ import { staticDirectory } from './tooling/lib/edition-paths.js';
3
+ import adapter from './tooling/adapter.js';
4
+ import { readFileSync } from 'node:fs';
5
+ import { applicationVersion } from './tooling/lib/app-version.js';
6
+ import { emptyTagRoute } from './tooling/lib/empty-tag-route.js';
7
+
8
+ const prerenderConcurrency = Number(process.env.WISCONSIN_PRERENDER_CONCURRENCY ?? 1);
9
+ if (!Number.isInteger(prerenderConcurrency) || prerenderConcurrency < 1)
10
+ throw new Error('WISCONSIN_PRERENDER_CONCURRENCY must be a positive integer');
11
+
12
+ /** @param {string} filename @param {string} value */
13
+ function expectedDiagnostic(filename, value) {
14
+ try {
15
+ return JSON.parse(readFileSync(`build/generated/${filename}.json`, 'utf8')).includes(value);
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ /** @type {import('@sveltejs/kit').Config} */
22
+ const config = {
23
+ compilerOptions: {
24
+ // Force runes mode everywhere except libraries.
25
+ runes: ({ filename }) => {
26
+ if (filename.split(/[/\\]/).includes('node_modules')) return undefined;
27
+ return true;
28
+ }
29
+ },
30
+ kit: {
31
+ paths: { relative: process.env.VITE_STATIC_EXPORT === 'true' },
32
+ outDir: 'build/.svelte-kit',
33
+ files: { assets: process.env.WISCONSIN_APP_STATIC_DIR ?? staticDirectory(process.cwd()) },
34
+ adapter: adapter(),
35
+ // Content is served separately. Rebuilding it should not change the
36
+ // application version, hydration IDs, and every prerendered HTML asset.
37
+ version: { name: applicationVersion(process.cwd()) },
38
+ prerender: {
39
+ concurrency: prerenderConcurrency,
40
+ origin: site.url,
41
+ handleUnseenRoutes: ({ routes, message }) => {
42
+ if (emptyTagRoute(process.cwd(), routes)) return;
43
+ if (
44
+ process.env.VITE_PUBLIC_EDITION === 'true' &&
45
+ routes.every((route) => ['/tags/[...tag]', '/[course]/files/[...file]'].includes(route))
46
+ )
47
+ return;
48
+ throw new Error(message);
49
+ },
50
+ handleHttpError: ({ status, path, referrer, message }) => {
51
+ if (status === 404 && path === '/login') return;
52
+ if (status === 404 && expectedDiagnostic('expected-404', path)) {
53
+ console.warn(`[prerender] unresolved content link: ${path} <- ${referrer}`);
54
+ return;
55
+ }
56
+ throw new Error(message);
57
+ },
58
+ handleMissingId: ({ path, id, referrers, message }) => {
59
+ if (expectedDiagnostic('expected-missing-id', `${path}#${id}`)) {
60
+ console.warn(
61
+ `[prerender] unresolved content anchor: ${path}#${id} <- ${referrers.join(', ')}`
62
+ );
63
+ return;
64
+ }
65
+ throw new Error(message);
66
+ }
67
+ }
68
+ }
69
+ };
70
+
71
+ export default config;
@@ -0,0 +1,25 @@
1
+ Portions of the content pipeline in this directory (slug.ts, ofm.ts,
2
+ lastmod.ts, autotag.ts and the link-resolution logic) are derived from
3
+ Quartz v4 (https://github.com/jackyzha0/quartz), used under the MIT License:
4
+
5
+ MIT License
6
+
7
+ Copyright (c) 2021 jackyzha0
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ import cloudflare from '@sveltejs/adapter-cloudflare';
2
+ import { readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { prepareWranglerPaths } from './wrangler-paths.js';
5
+ import { indexSearch } from './search.js';
6
+ import { prewarmCloudflareEmulator } from './lib/cloudflare-prewarm.js';
7
+
8
+ export default function () {
9
+ const directory = prepareWranglerPaths(path.resolve(import.meta.dirname, '..'));
10
+ process.env.WRANGLER_CACHE_DIR ??= path.join(directory, 'cache');
11
+ const adapter = cloudflare({
12
+ platformProxy: { persist: { path: path.join(directory, 'state/v3') } }
13
+ });
14
+ return {
15
+ ...adapter,
16
+ async emulate() {
17
+ return prewarmCloudflareEmulator((await adapter.emulate?.()) ?? {});
18
+ },
19
+ /** @param {import('@sveltejs/kit').Builder} builder */
20
+ async adapt(builder) {
21
+ const started = performance.now();
22
+ await adapter.adapt(builder);
23
+ console.log(`adapter: cloudflare in ${((performance.now() - started) / 1000).toFixed(2)}s`);
24
+ if (process.env.VITE_STATIC_EXPORT === 'true') {
25
+ await indexSearch(builder.getBuildDirectory('cloudflare'));
26
+ const shell = path.join(builder.getBuildDirectory('cloudflare'), '_file-browser.html');
27
+ const fallbackStarted = performance.now();
28
+ await builder.generateFallback(shell);
29
+ console.log(
30
+ `adapter: file browser shell in ${((performance.now() - fallbackStarted) / 1000).toFixed(2)}s`
31
+ );
32
+ writeFileSync(
33
+ shell,
34
+ readFileSync(shell, 'utf8').replace(
35
+ '</body>',
36
+ '<noscript><p>Enable JavaScript to use the file browser.</p></noscript></body>'
37
+ )
38
+ );
39
+ if (process.env.VITE_PUBLIC_EDITION === 'true') {
40
+ writeFileSync(
41
+ 'build/generated/public-routes.json',
42
+ JSON.stringify(
43
+ Object.fromEntries(
44
+ [...builder.prerendered.pages].map(([route, page]) => [route, page.file])
45
+ )
46
+ )
47
+ );
48
+ }
49
+ }
50
+ const directory = builder.getBuildDirectory('');
51
+ renameSync(`${directory}/worker.js`, `${directory}/svelte-worker.js`);
52
+ writeFileSync(`${directory}/worker.js`, "export { default } from '../../worker/index.ts';\n");
53
+ }
54
+ };
55
+ }