From 1be26150787d2a1d87aa689d272a97cdbd433fa0 Mon Sep 17 00:00:00 2001 From: Trezy Date: Wed, 13 May 2026 15:53:19 -0500 Subject: [PATCH] docs: add a blog Signed-off-by: Trezy --- bun.lock | 10 +- .../releasing-happyview-2-into-the-wild.md | 63 ++++++++++++ packages/docs/package.json | 3 +- packages/docs/public/authors/trezy.webp | Bin 0 -> 1734 bytes packages/docs/source.config.ts | 17 ++- .../{ => (docs)}/[[...slug]]/not-found.tsx | 0 .../src/app/{ => (docs)}/[[...slug]]/page.tsx | 0 packages/docs/src/app/(docs)/layout.tsx | 18 ++++ packages/docs/src/app/blog/[slug]/page.tsx | 95 +++++++++++++++++ packages/docs/src/app/blog/layout.tsx | 5 + packages/docs/src/app/blog/page.tsx | 92 +++++++++++++++++ packages/docs/src/app/global.css | 97 +++++++++++++++++- packages/docs/src/app/layout.tsx | 49 ++------- packages/docs/src/lib/nav.tsx | 35 +++++++ packages/docs/src/lib/source.ts | 6 +- 15 files changed, 441 insertions(+), 49 deletions(-) create mode 100644 packages/docs/content/blog/releasing-happyview-2-into-the-wild.md create mode 100644 packages/docs/public/authors/trezy.webp rename packages/docs/src/app/{ => (docs)}/[[...slug]]/not-found.tsx (100%) rename packages/docs/src/app/{ => (docs)}/[[...slug]]/page.tsx (100%) create mode 100644 packages/docs/src/app/(docs)/layout.tsx create mode 100644 packages/docs/src/app/blog/[slug]/page.tsx create mode 100644 packages/docs/src/app/blog/layout.tsx create mode 100644 packages/docs/src/app/blog/page.tsx create mode 100644 packages/docs/src/lib/nav.tsx diff --git a/bun.lock b/bun.lock index e9d0483..183351d 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,9 @@ "packages/design-system": { "name": "@happyview/design-system", "version": "0.0.0", + "peerDependencies": { + "next": ">=15", + }, }, "packages/docs": { "name": "@happyview/docs", @@ -26,6 +29,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "tailwindcss": "^4.2.0", + "zod": "^4.4.3", }, "devDependencies": { "@types/node": "^22.0.0", @@ -1490,7 +1494,7 @@ "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], - "postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], + "postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], @@ -1884,6 +1888,8 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], + "bun-types/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -1920,8 +1926,6 @@ "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - "normalize-package-data/hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], diff --git a/packages/docs/content/blog/releasing-happyview-2-into-the-wild.md b/packages/docs/content/blog/releasing-happyview-2-into-the-wild.md new file mode 100644 index 0000000..2496f22 --- /dev/null +++ b/packages/docs/content/blog/releasing-happyview-2-into-the-wild.md @@ -0,0 +1,63 @@ +--- +title: "Releasing HappyView 2 Into the Wild" +description: "One binary, no companion services, and a lot fewer moving parts." +date: 2026-05-13 +author: + name: "Trezy" + avatar: "/authors/trezy.webp" +tags: + - announcements +--- + +I originally built HappyView because I needed it for my own [atproto](https://atproto.com) project, [Cartridge](https://cartridge.dev). v2 is still built for my needs, but now I can hand it to someone else without apologizing. + +## If you're new here + +HappyView is a general-purpose [AppView](https://atproto.com/guides/glossary#app-view) for atproto. Upload your [lexicons](https://atproto.com/guides/glossary#lexicon) and it gives you a fully functional [XRPC](https://atproto.com/guides/glossary#xrpc) API. It also provides a scripting interface that can be used to process records during ingestion, as well as customizing your XRPC handlers. + +## What's new + +HappyView v2 is a _massive_ upgrade. Bug fixes, new features, and an interface overhaul across the board. + +### Fewer moving parts + +HappyView v1 required setting up HappyView itself, a [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) server for ingestion, and an [AIP](https://github.com/graze-social/aip) server for handling auth. All three had to be deployed, configured, and kept in sync, which was a lot of moving parts. HappyView v2 completely absorbs Tap and AIP. Real-time indexing and backfill were fully owned by Tap in v1, but now run as background tasks inside v2. AIP used to handle auth, but now OAuth with DPoP is built natively into v2. + +There are no more companion services to stand up. + +### More databases + +SQLite is now the default so most deployments only require a single binary to run the entire AppView. Postgres is still fully supported for large-scale deployments. A single adapter translates between the two at runtime so your scripts work against either database without modification. That means a fresh HappyView deployment needs no external database, no connection string, and no compose file to run. + +### Authorize meeeeeee + +HappyView auth has its own system for managing DPoP-bound access tokens, which makes it easy for developers to use the existing atproto SDKs to integrate with HappyView. + +That's right: apps built against atproto don't need a separate auth integration to work with a HappyView instance. + +Three new npm packages ship alongside to allow existing atproto SDKs and [`@atproto/lex`](https://npmx.dev/package/@atproto/lex) to integrate directly with HappyView: + +- [**`@happyview/lex-agent`**](https://npmx.dev/package/@happyview/lex-agent) - Allows [`@atproto/lex`](https://npmx.dev/package/@atproto/lex) to work directly with HappyView. +- [**`@happyview/oauth-client-browser`**](https://npmx.dev/package/@happyview/oauth-client-browser) - Integrates directly with [`@atproto/oauth-client-browser`](https://npmx.dev/package/@atproto/oauth-client-browser) for applications using the classic SDK. +- [**`@happyview/oauth-client`**](https://npmx.dev/package/@happyview/oauth-client) - For use with [`@atproto/oauth-client`](https://npmx.dev/package/@atproto/oauth-client) when building custom clients or doing more low-level work. + +### Plugins: The Beginning + +v2 introduces a WebAssembly plugin system for extending HappyView without the need to fork it. Plugins run sandboxed with fuel limits, declare their required secrets and auth type up front, and can be installed from anywhere, including an official registry. Plugins currently only support third-party auth, but they'll eventually be expanded to support many more features, like caching, backfill and real-time, and ingestion. The goal is to keep HappyView small and focused at its core while letting operators build exactly the AppView they need. + +### And much more... + +Also new: + +- **Labeler integration** built in. Subscribe to labelers over WebSocket, store labels locally. +- **Dead letter queue** for failed index hooks, with retry, re-index, and dismiss actions. +- **Rate limiting** configurable per API client with token buckets. +- **Permission system** with 29 granular permissions across four templates. +- **API client management**, **instance branding**, and **multi-domain support** in the dashboard. +- **Expanded Lua scripting** with new APIs for database queries, HTTP and XRPC calls, and atproto operations. + +## Go get some + +If you're currently running HappyView v1, breaking changes are listed in the [changelog](https://happyview.dev/reference/changelog). Full documentation is at [happyview.dev](https://happyview.dev). If you're running v1, the [migration guide](https://happyview.dev/guides/upgrading-to-v2) walks through the process. + +I hope y'all enjoy the release, and make sure to ping me if you're building something cool with it! ✌️ diff --git a/packages/docs/package.json b/packages/docs/package.json index a5f0540..03bb418 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -21,7 +21,8 @@ "next-themes": "^0.4.6", "react": "^19.2.0", "react-dom": "^19.2.0", - "tailwindcss": "^4.2.0" + "tailwindcss": "^4.2.0", + "zod": "^4.4.3" }, "devDependencies": { "@types/node": "^22.0.0", diff --git a/packages/docs/public/authors/trezy.webp b/packages/docs/public/authors/trezy.webp new file mode 100644 index 0000000000000000000000000000000000000000..e2016a66b0cf0c09ba0857d7bd66d947f768f680 GIT binary patch literal 1734 zcmWIYbaUIs#=sEn>J$(bV4<*yje$WxnPD!YRs%x=gI%t{RCg7nMT#pX-L`1p?M!9f zAiFW8quTB5uBmH8AI#k$^se~= z=^ZS|H#}~BI?gkFZ)4KB&YMcF*^>CpYj`|PbaTmh-Ek;xs#aYnsbw5`RQ~-IL63KP zy0i47^rx*mD=>Q*w};jZUAZG2pVlok`#yPZ*~XoJp6?Hd{eS)c^9{yN@{Pr<5>9f| zY6ZM4$g0d_in!{3d&Z%v`!hJyx@PaKvoUxv*Q!WdH15T*#k;Rwn6*L6bjzei3uS7* zIX<+X>NQc}p6mPX-l#Xn!ZZrJuZpT} zEYaJze~FkjS8iqD9tMVgf1YniY>-{}^rDl3uIrhcNsm;E)){Z+@6X=1!*7bd^y$lM z8PzAN&D5;yQBh8snz2>++vomMuRQhQc1W*KY|d_sI=6dD$jd1^=U1@&6tL^PeKq%| z3jb!=5=WMnbk(TYzYOR7>9BQHSGn6WkMW^9?>f`Wxgp!v~6%F@Yi=Oi58cRz8(GJz8Jt6?n5ud_Y+ z!nE~Z+l0SA3VI&hY_rV$8~5wVx{apEn-UjX?JW8HGO73U(YJ4px9?)CeE;uv+nkl* zR|A~*F8sWg7i#cz>#_Iio$F%PI{lr%(z`IO?pa}IY*ugkA3-&nL(!%0?foC89^*-k zm793Z%tGmQt4$D}*3wCjXO&f2zIKR>dB9oD-!JrB>9VAqoUNgd?2-vw0e{(Rw8|M2 z=N%4sV|ebN@vNfW*k!Xz>id|2kBRz!>WJx1Xqp6u10#Pthl{=Y9c?e@A6Z?mv-P zpAmHbURKT`odtb2v)@Na`-&_(tQ~dvMTEpvEuQvM|9v|)&6++halhlIDEXg^tjTe= zPX;;avs-1Z{q*9>*+)fEZw|FTy|>-!`pxH?Ie*{&bZPR`?Z&(xY--nUG4*%4dg05$ zHJ;m*_?8r}6L@l6XXCFKva%NYEz%zTF+5>hayjq)PA>7e5?+G*;-{}(zP)^Um5P-t zSF8c2=+ApIz8$J!`t7vcP;&nlRyDt?n}0})zkUCD`P)+-6F;-;l=)ZdJA3=z*{i}Q zoqPS~xLim@sJ*F9;{BzL`~HUC>zk=|hb_di(`c3Y^n+Y?W&W>ypyxX8?&dY+UuQaZ zbaSqcneqSiG0x+XcTe10&EjUaZQK6NnUCKs{&=>L!zey{vHwvM9&2+g-u7<&J;}!z z>t2OaC#YDyt(+Zu?Cj~QmM5dWZohK+?t8Td#xWIUIV`useyC2Iu~JIR?(yHo|GlR@ z`{J(kR!qBh?%r+bZL5V2?G8z9KKf0MZIY_^r|{y>Zx^<&;OG9Hz9GP&#CEPs+Ml^Q zV?vkk=mg!K!nQmktS+%2(cQNEG!y4}J>@kDe14)`4Dq$enmwLp!)*NI`=!q4_vl@Z z7h2LFA}(3+xRL#vU(?(3LiVet1lT_3Umx&`>sAGW3ClA}<-8pm=BlwS4EecnR(__%6L?+%vZRSydK zyk>Aac)X47z8l>;KSAb`PsFyEsUNRh6g5cW=l+v%+GS?r>gbN(^KpLb?Ehw0 zZ>#iny4|*YM=Ce(d-Zhbj~I=KHH;rQ6XcFacvK1;kg{bs;-ON$`uP8Qec|spt#tBM zeM<|wT~aC@(ehV6cDqz&Y^W*Atk$pZmDe5ES?qlGAlI)ZwUish%2Pg0ldYB7a(H=H z!H19Tvsbyl%?z#4t4$5?ZZ3#?AM)k&#zQLhe^0v4N)Ze=)EfOXwmx@TR-L`&Ncp^(Sv#ldtcF3BUFiM2I(r zeOW52xn82-`Xf!2MHw#9GvbS7jQzy>#czDwG;i{C!v~V@v>gogI=a6wnG*Br%(HhV z_q-@P;$6hOW5JflowBQ!aOoZ|6r1OLciuhIT%&n`6SybG{&4gS-})nBdc3#=*Furh z }} + searchToggle={{ enabled: false }} + themeSwitch={{ enabled: false }} + > + {children} + + ); +} diff --git a/packages/docs/src/app/blog/[slug]/page.tsx b/packages/docs/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..ee2dbd3 --- /dev/null +++ b/packages/docs/src/app/blog/[slug]/page.tsx @@ -0,0 +1,95 @@ +import { blogSource } from '@/lib/source'; +import { notFound } from 'next/navigation'; +import defaultMdxComponents from 'fumadocs-ui/mdx'; +import { Mermaid } from '@/components/mermaid'; +import { VaporwaveGrid } from '@/components/vaporwave-grid'; +import Image from 'next/image'; + +export default async function BlogPost(props: { + params: Promise<{ slug: string }>; +}) { + const params = await props.params; + const page = blogSource.getPage([params.slug]); + + if (!page) notFound(); + + const { title, description, date, author, tags } = page.data; + const Mdx = page.data.body; + + return ( +
+
+

{title}

+ {description && ( +

+ {description} +

+ )} +
+
+ {author.name} + + {author.name} + +
+ + {tags && tags.length > 0 && ( +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+ )} +
+
+
+ +
+ +
+ ); +} + +export function generateStaticParams() { + return blogSource.generateParams().map((params) => ({ + slug: params.slug?.[0] ?? '', + })); +} + +export async function generateMetadata(props: { + params: Promise<{ slug: string }>; +}) { + const params = await props.params; + const page = blogSource.getPage([params.slug]); + if (!page) return {}; + + return { + title: page.data.title, + description: page.data.description, + }; +} diff --git a/packages/docs/src/app/blog/layout.tsx b/packages/docs/src/app/blog/layout.tsx new file mode 100644 index 0000000..db4e320 --- /dev/null +++ b/packages/docs/src/app/blog/layout.tsx @@ -0,0 +1,5 @@ +import type { ReactNode } from 'react'; + +export default function BlogLayout({ children }: { children: ReactNode }) { + return <>{children}; +} diff --git a/packages/docs/src/app/blog/page.tsx b/packages/docs/src/app/blog/page.tsx new file mode 100644 index 0000000..5d300ba --- /dev/null +++ b/packages/docs/src/app/blog/page.tsx @@ -0,0 +1,92 @@ +import { blogSource } from '@/lib/source'; +import { VaporwaveGrid } from '@/components/vaporwave-grid'; +import Image from 'next/image'; +import Link from 'next/link'; + +export const metadata = { + title: 'Blog', + description: 'Project updates, release notes, and feature announcements.', +}; + +export default function BlogIndex() { + const posts = blogSource + .getPages() + .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); + + return ( +
+

Blog

+

+ Project updates, release notes, and feature announcements. +

+
+ {posts.map((post) => ( + +

+ {post.data.title} +

+ {post.data.description && ( +

+ {post.data.description} +

+ )} +
+
+ {post.data.author.name} + + {post.data.author.name} + +
+ + {post.data.tags && post.data.tags.length > 0 && ( +
+ {post.data.tags.map((tag) => ( + + {tag} + + ))} +
+ )} +
+ + ))} +
+ +
+ ); +} diff --git a/packages/docs/src/app/global.css b/packages/docs/src/app/global.css index f1f3042..d2155a1 100644 --- a/packages/docs/src/app/global.css +++ b/packages/docs/src/app/global.css @@ -24,7 +24,9 @@ } #nd-page > .prose :is(h2, h3, h4), -#nd-page > .prose :is(h2, h3, h4) > a { +#nd-page > .prose :is(h2, h3, h4) > a, +.prose :is(h2, h3, h4), +.prose :is(h2, h3, h4) > a { color: rgb(var(--color-magenta)) !important; } @@ -101,13 +103,102 @@ border-radius: 8px !important; } +/* --- Nav bar --- */ + +#nd-nav { + height: auto; +} + +#nd-nav > div { + background: rgb(var(--color-surface)) !important; + border-color: rgb(var(--color-border)); +} + +#nd-nav > div > * { + max-width: none !important; + margin: 0 !important; +} + +#nd-nav nav { + height: auto; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +/* Nav links (exclude logo) */ +#nd-nav nav ul a, +#nd-nav nav > div a { + color: rgb(var(--color-fg)); + padding-left: 1rem; + padding-right: 1rem; + border-radius: 9999px; + transition: color 0.2s, background-color 0.2s; +} + +#nd-nav nav ul a:hover, +#nd-nav nav > div a:hover { + color: rgb(var(--color-aqua)) !important; + background-color: rgb(var(--color-bg)) !important; + border-radius: 9999px; +} + +#nd-nav nav ul a[data-active="true"] { + color: rgb(var(--color-aqua)) !important; + background: rgb(var(--color-bg)) !important; + border-radius: 9999px; +} + +#nd-nav [data-search-full] { + color: rgb(var(--color-fg-muted)); + border-color: rgb(var(--color-border)); + background: rgb(var(--color-bg)) !important; +} + +#nd-nav [data-search-full]:hover { + border-color: rgb(var(--color-aqua)); + color: rgb(var(--color-aqua)); + background: rgb(var(--color-bg)) !important; +} + +#nd-nav [data-search] { + color: rgb(var(--color-fg-muted)); +} + +#nd-nav [data-search]:hover { + color: rgb(var(--color-aqua)) !important; + background: rgb(var(--color-bg)) !important; +} + +/* --- Blog layout --- */ + +#nd-home-layout { + background: transparent; +} + +#nd-home-layout article > canvas { + margin-bottom: -40vh; +} + +/* --- Blog card hover --- */ + +.group:hover { + border-color: rgb(var(--color-aqua)) !important; + box-shadow: 0 0 20px rgb(var(--color-aqua) / 0.15); +} + +.group:hover h2 { + color: rgb(var(--color-aqua)) !important; +} + /* Reduced motion */ -html[data-reduced-motion="true"] #nd-sidebar [data-active="true"] { +html[data-reduced-motion="true"] #nd-sidebar [data-active="true"], +html[data-reduced-motion="true"] #nd-nav nav a[data-active="true"] { animation: none; } @media (prefers-reduced-motion: reduce) { - #nd-sidebar [data-active="true"] { + #nd-sidebar [data-active="true"], + #nd-nav nav a[data-active="true"] { animation: none; } } diff --git a/packages/docs/src/app/layout.tsx b/packages/docs/src/app/layout.tsx index 0e09520..976ce1a 100644 --- a/packages/docs/src/app/layout.tsx +++ b/packages/docs/src/app/layout.tsx @@ -1,10 +1,9 @@ import './global.css'; import { RootProvider } from 'fumadocs-ui/provider/next'; -import { DocsLayout } from 'fumadocs-ui/layouts/docs'; -import { source } from '@/lib/source'; +import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { fontVariables } from '@/lib/fonts'; import { ReducedMotionProvider } from '@/lib/reduced-motion'; -import { SidebarFooter } from '@/components/sidebar-footer'; +import { navTitle, navLinks } from '@/lib/nav'; import type { ReactNode } from 'react'; export const metadata = { @@ -28,43 +27,13 @@ export default function RootLayout({ children }: { children: ReactNode }) { - - HappyView - HappyView - - ), - }} - sidebar={{ - footer: , - }} - themeSwitch={{ enabled: false }} - links={[ - { - text: 'Docs', - url: '/', - active: 'nested-url', - }, - { - text: 'Source', - url: 'https://tangled.org/gamesgamesgamesgames.games/happyview', - external: true, - }, - ]} - > - {children} - + + {children} + diff --git a/packages/docs/src/lib/nav.tsx b/packages/docs/src/lib/nav.tsx new file mode 100644 index 0000000..9fc430f --- /dev/null +++ b/packages/docs/src/lib/nav.tsx @@ -0,0 +1,35 @@ +import type { LinkItemType } from 'fumadocs-ui/layouts/shared'; +import type { ReactNode } from 'react'; + +export const navTitle: ReactNode = ( + + HappyView + HappyView + +); + +export const navLinks: LinkItemType[] = [ + { + text: 'Docs', + url: '/', + active: 'nested-url', + }, + { + text: 'Blog', + url: '/blog', + active: 'nested-url', + }, + { + text: 'Source', + url: 'https://tangled.org/gamesgamesgamesgames.games/happyview', + external: true, + }, +]; diff --git a/packages/docs/src/lib/source.ts b/packages/docs/src/lib/source.ts index 12d573a..7c66a1b 100644 --- a/packages/docs/src/lib/source.ts +++ b/packages/docs/src/lib/source.ts @@ -1,6 +1,10 @@ -import { docs } from 'collections/server'; +import { docs, blog } from 'collections/server'; import { loader } from 'fumadocs-core/source'; export const source = loader(docs.toFumadocsSource(), { baseUrl: '/', }); + +export const blogSource = loader(blog.toFumadocsSource(), { + baseUrl: '/blog', +}); -- 2.51.2