@stackbox/cms 0.0.3 → 0.0.5
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.
- package/AGENTS.md +87 -0
- package/README.md +57 -17
- package/dist/blocks.d.ts +18 -0
- package/dist/blocks.d.ts.map +1 -0
- package/dist/blocks.js +25 -0
- package/dist/blocks.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +10 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +128 -0
- package/dist/link.js.map +1 -0
- package/dist/modules/blog/module.d.ts +20 -0
- package/dist/modules/blog/module.d.ts.map +1 -0
- package/dist/modules/blog/module.js +52 -0
- package/dist/modules/blog/module.js.map +1 -0
- package/dist/modules/blog/posts.d.ts +15 -0
- package/dist/modules/blog/posts.d.ts.map +1 -0
- package/dist/modules/blog/posts.js +46 -0
- package/dist/modules/blog/posts.js.map +1 -0
- package/dist/modules.d.ts +18 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +25 -0
- package/dist/modules.js.map +1 -0
- package/dist/pages.d.ts +55 -0
- package/dist/pages.d.ts.map +1 -0
- package/dist/pages.js +85 -0
- package/dist/pages.js.map +1 -0
- package/dist/plugins/blog/index.d.ts +20 -0
- package/dist/plugins/blog/index.d.ts.map +1 -0
- package/dist/plugins/blog/index.js +52 -0
- package/dist/plugins/blog/index.js.map +1 -0
- package/dist/plugins/blog/module.d.ts +20 -0
- package/dist/plugins/blog/module.d.ts.map +1 -0
- package/dist/plugins/blog/module.js +52 -0
- package/dist/plugins/blog/module.js.map +1 -0
- package/dist/plugins/blog/plugin.d.ts +20 -0
- package/dist/plugins/blog/plugin.d.ts.map +1 -0
- package/dist/plugins/blog/plugin.js +52 -0
- package/dist/plugins/blog/plugin.js.map +1 -0
- package/dist/plugins/blog/posts.d.ts +15 -0
- package/dist/plugins/blog/posts.d.ts.map +1 -0
- package/dist/plugins/blog/posts.js +46 -0
- package/dist/plugins/blog/posts.js.map +1 -0
- package/dist/plugins/random-quote/index.d.ts +12 -0
- package/dist/plugins/random-quote/index.d.ts.map +1 -0
- package/dist/plugins/random-quote/index.js +21 -0
- package/dist/plugins/random-quote/index.js.map +1 -0
- package/dist/plugins/random-quote/module.d.ts +9 -0
- package/dist/plugins/random-quote/module.d.ts.map +1 -0
- package/dist/plugins/random-quote/module.js +31 -0
- package/dist/plugins/random-quote/module.js.map +1 -0
- package/dist/plugins/random-quote/plugin.d.ts +8 -0
- package/dist/plugins/random-quote/plugin.d.ts.map +1 -0
- package/dist/plugins/random-quote/plugin.js +31 -0
- package/dist/plugins/random-quote/plugin.js.map +1 -0
- package/dist/plugins/random-quote/public_assets/quotes.json +230 -0
- package/dist/plugins/random-quote/quotes.json +230 -0
- package/dist/public/_sb/plugins/random-quote/quotes.json +230 -0
- package/dist/render-head.d.ts +7 -0
- package/dist/render-head.d.ts.map +1 -0
- package/dist/render-head.js +27 -0
- package/dist/render-head.js.map +1 -0
- package/dist/render-page.d.ts +6 -0
- package/dist/render-page.d.ts.map +1 -0
- package/dist/render-page.js +30 -0
- package/dist/render-page.js.map +1 -0
- package/dist/routing.d.ts +3 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +11 -0
- package/dist/routing.js.map +1 -0
- package/dist/site.d.ts +21 -0
- package/dist/site.d.ts.map +1 -0
- package/dist/site.js +102 -0
- package/dist/site.js.map +1 -0
- package/dist/slot-content.d.ts +30 -0
- package/dist/slot-content.d.ts.map +1 -0
- package/dist/slot-content.js +34 -0
- package/dist/slot-content.js.map +1 -0
- package/dist/slot-handle.d.ts +32 -0
- package/dist/slot-handle.d.ts.map +1 -0
- package/dist/slot-handle.js +68 -0
- package/dist/slot-handle.js.map +1 -0
- package/dist/stackbox/context.d.ts +52 -0
- package/dist/stackbox/context.d.ts.map +1 -0
- package/dist/stackbox/context.js +165 -0
- package/dist/stackbox/context.js.map +1 -0
- package/dist/templates.d.ts +63 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +106 -0
- package/dist/templates.js.map +1 -0
- package/package.json +19 -6
- package/src/plugins/blog/AGENTS.md +131 -0
- package/src/plugins/random-quote/AGENTS.md +79 -0
- package/src/plugins/random-quote/index.ts +33 -0
- package/src/plugins/random-quote/public_assets/quotes.json +230 -0
- package/.claude/settings.local.json +0 -8
- package/src/index.ts +0 -67
- package/src/modules.ts +0 -58
- package/src/pages.ts +0 -198
- package/src/render-head.ts +0 -32
- package/src/render-page.ts +0 -51
- package/src/routing.ts +0 -14
- package/src/site.ts +0 -153
- package/src/slot-content.ts +0 -77
- package/src/slot-handle.ts +0 -131
- package/src/stackbox/context.ts +0 -257
- package/src/templates.ts +0 -230
- package/test/async-render.test.ts +0 -103
- package/test/blog.test.ts +0 -93
- package/test/fetch-routing.test.ts +0 -76
- package/test/stackbox-context.test.ts +0 -44
- package/tsconfig.json +0 -19
- /package/src/{modules/blog/module.ts → plugins/blog/index.ts} +0 -0
- /package/src/{modules → plugins}/blog/posts.ts +0 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @stackbox/cms — agent instructions
|
|
2
|
+
|
|
3
|
+
Stackbox is a code-first CMS that assembles TypeScript into a **fetch-handler-compatible server**. Use these conventions when building or extending a site.
|
|
4
|
+
|
|
5
|
+
## Site conventions
|
|
6
|
+
|
|
7
|
+
| Primitive | Factory | Purpose |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Site config | `createSiteConfig(config)` | Definition-time settings shared by templates and pages |
|
|
10
|
+
| Site | `createSite(siteConfig, { pages })` | Runtime router with `fetch(request, env)` |
|
|
11
|
+
| Template | `createTemplate({ siteConfig, slots, render })` | Reusable layout with named slots |
|
|
12
|
+
| Page | `createPage(template, { path, title, slots })` | A single URL built from a template + slot content |
|
|
13
|
+
| Block | `createBlock({ name, render })` | A content block placed into a slot at request time |
|
|
14
|
+
|
|
15
|
+
**Project layout:**
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
site.config.ts # createSiteConfig
|
|
19
|
+
server.ts # createSite(...) — default export is the fetch handler
|
|
20
|
+
templates/ # shared createTemplate() layouts
|
|
21
|
+
pages/ # createPage() per route or feature
|
|
22
|
+
content/ # markdown or other bundle-time content (per feature)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The default export from `server.ts` implements `fetch(request, env)` and returns a `Response`. It works on Cloudflare Workers, Bun, Deno, and any runtime that speaks the fetch-handler pattern.
|
|
26
|
+
|
|
27
|
+
## Plugins vs blocks
|
|
28
|
+
|
|
29
|
+
- **Plugin** — a packaged use-case under `@stackbox/cms/plugins/<name>` (e.g. blog). May export factories, content objects, blocks, types, and helpers.
|
|
30
|
+
- **Block** — a core primitive via `createBlock()`. Renders HTML into a template slot. Usable inside or outside plugins.
|
|
31
|
+
|
|
32
|
+
Slot content is `Block | string`. Plugin content arrays can interleave blocks and HTML strings.
|
|
33
|
+
|
|
34
|
+
## Plugin catalog
|
|
35
|
+
|
|
36
|
+
Before implementing a use-case (blog, newsletter, docs, …), check this table. If the user's request matches a row, **read only that plugin's AGENTS.md** and follow it. Do not invent a parallel implementation.
|
|
37
|
+
|
|
38
|
+
| Plugin | Import | Read when the user mentions | Instructions |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| blog | `@stackbox/cms/plugins/blog` | blog, posts, articles, journal, markdown posts, blog listing, blog page | `src/plugins/blog/AGENTS.md` |
|
|
41
|
+
| random-quote | `@stackbox/cms/plugins/random-quote` | random quote, quote of the day, inspirational quote, sidebar quote | `src/plugins/random-quote/AGENTS.md` |
|
|
42
|
+
|
|
43
|
+
When adding a new plugin to this package, add a row here and create `src/plugins/<name>/AGENTS.md` using the same heading structure as the blog plugin. Document all export kinds (factory, types, blocks, helpers).
|
|
44
|
+
|
|
45
|
+
## Plugin public assets
|
|
46
|
+
|
|
47
|
+
Runtime files that must be **publicly available** (images, CSS, fonts, JSON served over HTTP) go in:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/plugins/<name>/public_assets/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Opt-in:** only plugins with a `public_assets/` folder get copied at build time. Do not edit the copy script when adding files.
|
|
54
|
+
|
|
55
|
+
Build copies each `public_assets/` folder to:
|
|
56
|
+
|
|
57
|
+
- `dist/public/_sb/plugins/<name>/` — served at `/_sb/plugins/<name>/...`
|
|
58
|
+
- `dist/plugins/<name>/public_assets/` — so `import "./public_assets/..." with { type: "json" }` works in compiled JS
|
|
59
|
+
|
|
60
|
+
Use `pluginAssetPath()` from `@stackbox/cms/link` for `<img src>`, `<link href>`, etc. Do not put site files under `/_sb/` — that prefix is reserved for Stackbox plugin assets.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { pluginAssetPath } from "@stackbox/cms/link";
|
|
64
|
+
|
|
65
|
+
pluginAssetPath("random-quote", "photo.jpg");
|
|
66
|
+
// → "/_sb/plugins/random-quote/photo.jpg"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For data bundled into JS at build time, import from `./public_assets/...` in the plugin's `index.ts`.
|
|
70
|
+
|
|
71
|
+
## For site agents
|
|
72
|
+
|
|
73
|
+
If you are working in a **consumer site repo** (not this package), read this file from the installed package:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
node_modules/@stackbox/cms/AGENTS.md
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Recommended snippet for the site's own `AGENTS.md`:
|
|
80
|
+
|
|
81
|
+
```md
|
|
82
|
+
This site uses @stackbox/cms. Before adding features, read
|
|
83
|
+
`node_modules/@stackbox/cms/AGENTS.md` and follow its plugin catalog.
|
|
84
|
+
Do not reimplement bundled plugins.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When a request matches a catalog row, open **only** that plugin's instructions file (e.g. `node_modules/@stackbox/cms/src/plugins/blog/AGENTS.md`).
|
package/README.md
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# @stackbox/cms
|
|
2
2
|
|
|
3
|
-
A small, code-first CMS engine for building dynamic
|
|
3
|
+
A small, code-first CMS engine for building dynamic sites behind a standard **fetch handler**. Stackbox is designed to be driven by AI: pages, templates, blocks, and plugins are plain TypeScript files with typed, composable APIs, so an agent can author and assemble a site without a database, admin UI, or hand-written backend.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`createSite()` returns an object with a `fetch(request, env)` method — the same shape used by Cloudflare Workers, Bun, Deno, and other runtimes that serve HTTP via the Fetch API:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
export default {
|
|
9
|
+
fetch(req: Request): Response | Promise<Response> {
|
|
10
|
+
return new Response(...);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Pages are rendered on each request, so content, templates, blocks, and plugins can be fully dynamic — driven by request data, environment bindings, and async data fetching.
|
|
6
16
|
|
|
7
17
|
## Why this exists
|
|
8
18
|
|
|
9
|
-
Traditional CMSes assume a human clicking around an admin panel. Stackbox inverts that: a site is TypeScript
|
|
19
|
+
Traditional CMSes assume a human clicking around an admin panel. Stackbox inverts that: a site is TypeScript assembled into a fetch handler. Every primitive (`createSiteConfig`, `createSite`, `createTemplate`, `createPage`, `createBlock`) is a typed factory suited for AI to generate, edit, and validate site content as code — and the same files render dynamically at request time.
|
|
10
20
|
|
|
11
21
|
## Requirements
|
|
12
22
|
|
|
@@ -23,19 +33,21 @@ npm install @stackbox/cms
|
|
|
23
33
|
| Primitive | Factory | Purpose |
|
|
24
34
|
| --- | --- | --- |
|
|
25
35
|
| **Site config** | `createSiteConfig(config)` | Definition-time settings shared by templates and pages. |
|
|
26
|
-
| **Site** | `createSite(siteConfig, { pages }` | Runtime router with `fetch(request, env)`
|
|
27
|
-
| **Template** | `createTemplate({ siteConfig, slots, render }` | A reusable page layout that declares named **slots**. |
|
|
28
|
-
| **Page** | `createPage(template, { path, title, slots }` | A single URL, built by filling a template's slots with content. |
|
|
29
|
-
| **
|
|
36
|
+
| **Site** | `createSite(siteConfig, { pages })` | Runtime router with `fetch(request, env)` — a fetch-handler-compatible server. |
|
|
37
|
+
| **Template** | `createTemplate({ siteConfig, slots, render })` | A reusable page layout that declares named **slots**. |
|
|
38
|
+
| **Page** | `createPage(template, { path, title, slots })` | A single URL, built by filling a template's slots with content. |
|
|
39
|
+
| **Block** | `createBlock({ name, render })` | A self-contained content block placed into a slot at request time. |
|
|
30
40
|
|
|
31
|
-
**
|
|
41
|
+
**Plugins vs blocks:** **Plugins** package whole features (blog, newsletter) under `@stackbox/cms/plugins/<name>` — they may export factories, content objects, blocks, types, and helpers. **Blocks** are the core slot primitive via `createBlock()`; plugins can ship blocks alongside other exports.
|
|
42
|
+
|
|
43
|
+
**Slots** are named regions in a template. Page content — strings, HTML, or blocks — is dropped into slots, and the engine resolves and renders everything (including async blocks, concurrently) to a single HTML string.
|
|
32
44
|
|
|
33
45
|
## Project layout
|
|
34
46
|
|
|
35
47
|
```
|
|
36
|
-
my-
|
|
48
|
+
my-site/
|
|
37
49
|
site.config.ts # createSiteConfig({ name, url, ... })
|
|
38
|
-
|
|
50
|
+
server.ts # createSite(...) — default export is your fetch handler
|
|
39
51
|
templates/
|
|
40
52
|
site-template.ts # shared createTemplate() layouts
|
|
41
53
|
pages/
|
|
@@ -87,7 +99,7 @@ const homePage = createPage(siteTemplate, {
|
|
|
87
99
|
export default homePage;
|
|
88
100
|
```
|
|
89
101
|
|
|
90
|
-
`
|
|
102
|
+
`server.ts`:
|
|
91
103
|
|
|
92
104
|
```ts
|
|
93
105
|
import { createSite } from "@stackbox/cms";
|
|
@@ -100,9 +112,13 @@ export default createSite(siteConfig, {
|
|
|
100
112
|
});
|
|
101
113
|
```
|
|
102
114
|
|
|
103
|
-
|
|
115
|
+
The default export implements `fetch(request, env)` and returns a `Response` — drop it into any runtime that speaks the fetch-handler pattern. For example:
|
|
116
|
+
|
|
117
|
+
- **[Cloudflare Workers](https://developers.cloudflare.com/workers/)** — deploy with [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) (often as `worker.ts`)
|
|
118
|
+
- **[Bun](https://bun.sh/docs/api/http#fetch-handler)** — `Bun.serve({ fetch: site.fetch })`
|
|
119
|
+
- **[Deno](https://docs.deno.com/runtime/fundamentals/http_server/)** — `Deno.serve(site.fetch)`
|
|
104
120
|
|
|
105
|
-
## Blog
|
|
121
|
+
## Blog plugin
|
|
106
122
|
|
|
107
123
|
`createBlog()` loads markdown at bundle time and returns **content objects** you wire into your own pages with `createPage()` — so you control templates, slots, and any extra content alongside blog output.
|
|
108
124
|
|
|
@@ -110,7 +126,7 @@ Deploy with [`wrangler`](https://developers.cloudflare.com/workers/wrangler/). T
|
|
|
110
126
|
// pages/blog.ts
|
|
111
127
|
import { join } from "node:path";
|
|
112
128
|
import { createPage } from "@stackbox/cms";
|
|
113
|
-
import { createBlog } from "@stackbox/cms/
|
|
129
|
+
import { createBlog } from "@stackbox/cms/plugins/blog";
|
|
114
130
|
import { siteTemplate } from "../templates/site-template";
|
|
115
131
|
|
|
116
132
|
const blog = createBlog({
|
|
@@ -140,7 +156,7 @@ export const blogPostPages = blog.posts.map((post) =>
|
|
|
140
156
|
```
|
|
141
157
|
|
|
142
158
|
```ts
|
|
143
|
-
//
|
|
159
|
+
// server.ts
|
|
144
160
|
import { blogListingPages, blogPostPages } from "./pages/blog";
|
|
145
161
|
|
|
146
162
|
export default createSite(siteConfig, {
|
|
@@ -148,10 +164,34 @@ export default createSite(siteConfig, {
|
|
|
148
164
|
});
|
|
149
165
|
```
|
|
150
166
|
|
|
151
|
-
## Bundled
|
|
167
|
+
## Bundled plugins
|
|
168
|
+
|
|
169
|
+
**Blog** — content objects wired into pages:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { createBlog } from "@stackbox/cms/plugins/blog";
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Random quote** — block-only plugin (drop into any slot):
|
|
152
176
|
|
|
153
177
|
```ts
|
|
154
|
-
import {
|
|
178
|
+
import { randomQuoteBlock } from "@stackbox/cms/plugins/random-quote";
|
|
179
|
+
import myQuotes from "../content/quotes.json" with { type: "json" };
|
|
180
|
+
|
|
181
|
+
slots: { sidebar: [randomQuoteBlock()] } // bundled quotes
|
|
182
|
+
slots: { sidebar: [randomQuoteBlock({ quotes: myQuotes })] } // your own
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## AI agents
|
|
186
|
+
|
|
187
|
+
Bundled plugins include agent playbooks. See [`AGENTS.md`](AGENTS.md) for site conventions and a plugin catalog. When a user asks for a feature (e.g. "add a blog"), read **only** the matching plugin's `AGENTS.md` — do not load every plugin file.
|
|
188
|
+
|
|
189
|
+
If you are building a site that uses this package, add this to your project's `AGENTS.md`:
|
|
190
|
+
|
|
191
|
+
```md
|
|
192
|
+
This site uses @stackbox/cms. Before adding features, read
|
|
193
|
+
`node_modules/@stackbox/cms/AGENTS.md` and follow its plugin catalog.
|
|
194
|
+
Do not reimplement bundled plugins.
|
|
155
195
|
```
|
|
156
196
|
|
|
157
197
|
## Development
|
package/dist/blocks.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RenderContext } from "./pages.js";
|
|
2
|
+
import type { HSHtml } from "@hyperspan/html";
|
|
3
|
+
export type BlockRenderResult = HSHtml | Promise<HSHtml>;
|
|
4
|
+
export type Block = {
|
|
5
|
+
readonly __kind: "block";
|
|
6
|
+
readonly name: string;
|
|
7
|
+
render(ctx: RenderContext): BlockRenderResult;
|
|
8
|
+
};
|
|
9
|
+
type BlockDef<TOptions = undefined> = {
|
|
10
|
+
name: string;
|
|
11
|
+
render(options: TOptions, ctx?: RenderContext): BlockRenderResult;
|
|
12
|
+
};
|
|
13
|
+
export type BlockOptionsOf<F> = F extends (options?: infer O) => unknown ? [O] extends [undefined] ? undefined : O : never;
|
|
14
|
+
export type BlockFactory<TOptions = undefined> = (options?: TOptions) => Block;
|
|
15
|
+
export declare function createBlock<TOptions = undefined>(def: BlockDef<TOptions>): BlockFactory<TOptions>;
|
|
16
|
+
export declare function isBlock(value: unknown): value is Block;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=blocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,iBAAiB,CAAC;CAC/C,CAAC;AAEF,KAAK,QAAQ,CAAC,QAAQ,GAAG,SAAS,IAAI;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,aAAa,GAAG,iBAAiB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,KAAK,OAAO,GACpE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,GACrB,SAAS,GACT,CAAC,GACH,KAAK,CAAC;AAEV,MAAM,MAAM,YAAY,CAAC,QAAQ,GAAG,SAAS,IAAI,CAC/C,OAAO,CAAC,EAAE,QAAQ,KACf,KAAK,CAAC;AAQX,wBAAgB,WAAW,CAAC,QAAQ,GAAG,SAAS,EAC9C,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,GACtB,YAAY,CAAC,QAAQ,CAAC,CAYxB;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAStD"}
|
package/dist/blocks.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function validateBlockName(name) {
|
|
2
|
+
if (!name || name.length === 0) {
|
|
3
|
+
throw new Error("createBlock(def): name is required");
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export function createBlock(def) {
|
|
7
|
+
validateBlockName(def.name);
|
|
8
|
+
if (typeof def.render !== "function") {
|
|
9
|
+
throw new Error("createBlock(def): render is required");
|
|
10
|
+
}
|
|
11
|
+
return ((options) => ({
|
|
12
|
+
__kind: "block",
|
|
13
|
+
name: def.name,
|
|
14
|
+
render: (ctx) => def.render(options, ctx),
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
export function isBlock(value) {
|
|
18
|
+
return (typeof value === "object" &&
|
|
19
|
+
value !== null &&
|
|
20
|
+
value.__kind === "block" &&
|
|
21
|
+
typeof value.name === "string" &&
|
|
22
|
+
value.name.length > 0 &&
|
|
23
|
+
typeof value.render === "function");
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AA0BA,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAuB;IAEvB,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,CAAC,CAAC,OAAkB,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,EAAE,OAAgB;QACxB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,CAAC,GAAkB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,OAAmB,EAAE,GAAG,CAAC;KACrE,CAAC,CAA2B,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAe,CAAC,MAAM,KAAK,OAAO;QACnC,OAAQ,KAAe,CAAC,IAAI,KAAK,QAAQ;QACxC,KAAe,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAChC,OAAQ,KAAe,CAAC,MAAM,KAAK,UAAU,CAC9C,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { html } from "@hyperspan/html";
|
|
2
|
+
export { createBlock, isBlock, type Block, type BlockFactory, type BlockOptionsOf, type BlockRenderResult, } from "./blocks.js";
|
|
3
|
+
export { createPage, isPage, PageValidationError, toPageRenderView, validateSlotContentItem, type Page, type PageMeta, type PageRenderView, type RenderContext, type SitePage, } from "./pages.js";
|
|
4
|
+
export { renderStandardHead } from "./render-head.js";
|
|
5
|
+
export { renderPage, RenderError } from "./render-page.js";
|
|
6
|
+
export { matchPagePath, normalizePathname } from "./routing.js";
|
|
7
|
+
export { createSite, createSiteConfig, isSite, isSiteConfig, SiteError, type Site, type SiteConfig, } from "./site.js";
|
|
8
|
+
export { createContext, Stackbox } from "./stackbox/context.js";
|
|
9
|
+
export { anySlotContentSchema, blockSlotContentSchema, slotHasContent, SlotContentValidationError, stringSlotContentSchema, type DefaultSlotContent, type PageSlotsInput, type SlotContentFromDefinition, } from "./slot-content.js";
|
|
10
|
+
export { buildPageSlots, buildStubSlots, renderSlotContent, slotSentinel, SLOT_SENTINEL_PREFIX, type Slot, type SlotRenderValue, type TemplateSlotsFrom, } from "./slot-handle.js";
|
|
11
|
+
export { createTemplate, isTemplate, TemplateBuildError, type RequiredSlotNamesFrom, type SlotDefinition, type SlotMeta, type SlotNamesFrom, type SlotOptions, type TemplateDescriptor, type TemplateRenderContext, type TemplateRenderFn, } from "./templates.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EACL,WAAW,EACX,OAAO,EACP,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,UAAU,EACV,MAAM,EACN,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,IAAI,EACT,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,YAAY,EACZ,SAAS,EACT,KAAK,IAAI,EACT,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,0BAA0B,EAC1B,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,EACpB,KAAK,IAAI,EACT,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { html } from "@hyperspan/html";
|
|
2
|
+
export { createBlock, isBlock, } from "./blocks.js";
|
|
3
|
+
export { createPage, isPage, PageValidationError, toPageRenderView, validateSlotContentItem, } from "./pages.js";
|
|
4
|
+
export { renderStandardHead } from "./render-head.js";
|
|
5
|
+
export { renderPage, RenderError } from "./render-page.js";
|
|
6
|
+
export { matchPagePath, normalizePathname } from "./routing.js";
|
|
7
|
+
export { createSite, createSiteConfig, isSite, isSiteConfig, SiteError, } from "./site.js";
|
|
8
|
+
export { createContext } from "./stackbox/context.js";
|
|
9
|
+
export { anySlotContentSchema, blockSlotContentSchema, slotHasContent, SlotContentValidationError, stringSlotContentSchema, } from "./slot-content.js";
|
|
10
|
+
export { buildPageSlots, buildStubSlots, renderSlotContent, slotSentinel, SLOT_SENTINEL_PREFIX, } from "./slot-handle.js";
|
|
11
|
+
export { createTemplate, isTemplate, TemplateBuildError, } from "./templates.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EACL,WAAW,EACX,OAAO,GAKR,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,UAAU,EACV,MAAM,EACN,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,GAMxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,YAAY,EACZ,SAAS,GAGV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAY,MAAM,uBAAuB,CAAC;AAChE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,0BAA0B,EAC1B,uBAAuB,GAIxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,GAIrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,cAAc,EACd,UAAU,EACV,kBAAkB,GASnB,MAAM,gBAAgB,CAAC"}
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const PLUGIN_PUBLIC_PREFIX = "/_sb/plugins";
|
|
2
|
+
export declare function pluginAssetPath(pluginName: string, relativePath: string): string;
|
|
3
|
+
export type PluginAssetRequest = {
|
|
4
|
+
plugin: string;
|
|
5
|
+
relativePath: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function parsePluginAssetRequest(pathname: string): PluginAssetRequest | null;
|
|
8
|
+
export declare function getPluginPublicRootDir(): string;
|
|
9
|
+
export declare function servePluginAsset(pathname: string, method: string): globalThis.Response | null;
|
|
10
|
+
//# sourceMappingURL=link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,oBAAoB,iBAAiB,CAAC;AA8BnD,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAShF;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,GACf,kBAAkB,GAAG,IAAI,CAkC3B;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAa/C;AAwBD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,UAAU,CAAC,QAAQ,GAAG,IAAI,CA4B5B"}
|
package/dist/link.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, normalize, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { normalizePathname } from "./routing.js";
|
|
5
|
+
export const PLUGIN_PUBLIC_PREFIX = "/_sb/plugins";
|
|
6
|
+
const CONTENT_TYPES = {
|
|
7
|
+
".css": "text/css; charset=utf-8",
|
|
8
|
+
".gif": "image/gif",
|
|
9
|
+
".html": "text/html; charset=utf-8",
|
|
10
|
+
".jpeg": "image/jpeg",
|
|
11
|
+
".jpg": "image/jpeg",
|
|
12
|
+
".js": "text/javascript; charset=utf-8",
|
|
13
|
+
".json": "application/json; charset=utf-8",
|
|
14
|
+
".png": "image/png",
|
|
15
|
+
".svg": "image/svg+xml",
|
|
16
|
+
".txt": "text/plain; charset=utf-8",
|
|
17
|
+
".webp": "image/webp",
|
|
18
|
+
".woff": "font/woff",
|
|
19
|
+
".woff2": "font/woff2",
|
|
20
|
+
};
|
|
21
|
+
function assertSafeSegment(segment, label) {
|
|
22
|
+
if (!segment ||
|
|
23
|
+
segment === "." ||
|
|
24
|
+
segment === ".." ||
|
|
25
|
+
segment.includes("/") ||
|
|
26
|
+
segment.includes("\\")) {
|
|
27
|
+
throw new Error(`pluginAssetPath: invalid ${label}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function pluginAssetPath(pluginName, relativePath) {
|
|
31
|
+
assertSafeSegment(pluginName, "plugin name");
|
|
32
|
+
const segments = relativePath
|
|
33
|
+
.split(/[/\\]+/)
|
|
34
|
+
.filter((segment) => segment.length > 0);
|
|
35
|
+
for (const segment of segments) {
|
|
36
|
+
assertSafeSegment(segment, "relative path segment");
|
|
37
|
+
}
|
|
38
|
+
return `${PLUGIN_PUBLIC_PREFIX}/${pluginName}/${segments.join("/")}`;
|
|
39
|
+
}
|
|
40
|
+
export function parsePluginAssetRequest(pathname) {
|
|
41
|
+
const normalized = normalizePathname(pathname);
|
|
42
|
+
const prefix = `${PLUGIN_PUBLIC_PREFIX}/`;
|
|
43
|
+
if (!normalized.startsWith(prefix)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const rest = normalized.slice(prefix.length);
|
|
47
|
+
const slash = rest.indexOf("/");
|
|
48
|
+
if (slash <= 0 || slash === rest.length - 1) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const plugin = rest.slice(0, slash);
|
|
52
|
+
const relativePath = rest.slice(slash + 1);
|
|
53
|
+
if (!plugin ||
|
|
54
|
+
!relativePath ||
|
|
55
|
+
relativePath.includes("..") ||
|
|
56
|
+
relativePath.includes("\\")) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
assertSafeSegment(plugin, "plugin name");
|
|
61
|
+
for (const segment of relativePath.split("/")) {
|
|
62
|
+
assertSafeSegment(segment, "relative path segment");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return { plugin, relativePath };
|
|
69
|
+
}
|
|
70
|
+
export function getPluginPublicRootDir() {
|
|
71
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
72
|
+
const fromDist = join(here, "public", "_sb", "plugins");
|
|
73
|
+
if (existsSync(fromDist)) {
|
|
74
|
+
return fromDist;
|
|
75
|
+
}
|
|
76
|
+
const fromSrcDev = join(here, "..", "dist", "public", "_sb", "plugins");
|
|
77
|
+
if (existsSync(fromSrcDev)) {
|
|
78
|
+
return fromSrcDev;
|
|
79
|
+
}
|
|
80
|
+
return fromDist;
|
|
81
|
+
}
|
|
82
|
+
function contentTypeForPath(relativePath) {
|
|
83
|
+
const dot = relativePath.lastIndexOf(".");
|
|
84
|
+
if (dot === -1) {
|
|
85
|
+
return "application/octet-stream";
|
|
86
|
+
}
|
|
87
|
+
return CONTENT_TYPES[relativePath.slice(dot).toLowerCase()] ??
|
|
88
|
+
"application/octet-stream";
|
|
89
|
+
}
|
|
90
|
+
function resolvePluginAssetFile(request) {
|
|
91
|
+
const root = getPluginPublicRootDir();
|
|
92
|
+
const absolute = normalize(join(root, request.plugin, request.relativePath));
|
|
93
|
+
const rootWithSep = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
94
|
+
if (!absolute.startsWith(rootWithSep)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (!existsSync(absolute)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return absolute;
|
|
101
|
+
}
|
|
102
|
+
export function servePluginAsset(pathname, method) {
|
|
103
|
+
const request = parsePluginAssetRequest(pathname);
|
|
104
|
+
if (!request) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const filePath = resolvePluginAssetFile(request);
|
|
108
|
+
if (!filePath) {
|
|
109
|
+
return new globalThis.Response("Not Found", { status: 404 });
|
|
110
|
+
}
|
|
111
|
+
const contentType = contentTypeForPath(request.relativePath);
|
|
112
|
+
if (method === "HEAD") {
|
|
113
|
+
const body = readFileSync(filePath);
|
|
114
|
+
return new globalThis.Response(null, {
|
|
115
|
+
status: 200,
|
|
116
|
+
headers: {
|
|
117
|
+
"content-type": contentType,
|
|
118
|
+
"content-length": String(body.byteLength),
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const body = readFileSync(filePath);
|
|
123
|
+
return new globalThis.Response(body, {
|
|
124
|
+
status: 200,
|
|
125
|
+
headers: { "content-type": contentType },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=link.js.map
|
package/dist/link.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PageMeta } from "../../pages.js";
|
|
2
|
+
import type { DefaultSlotContent } from "../../slot-content.js";
|
|
3
|
+
import { type BlogOptions } from "./posts.js";
|
|
4
|
+
export type { BlogPost, BlogOptions } from "./posts.js";
|
|
5
|
+
export type BlogPostContent = {
|
|
6
|
+
path: string;
|
|
7
|
+
title: string;
|
|
8
|
+
meta?: PageMeta;
|
|
9
|
+
content: DefaultSlotContent[];
|
|
10
|
+
};
|
|
11
|
+
export type BlogListingContent = {
|
|
12
|
+
path: string;
|
|
13
|
+
content: DefaultSlotContent[];
|
|
14
|
+
};
|
|
15
|
+
export type Blog = {
|
|
16
|
+
readonly posts: readonly BlogPostContent[];
|
|
17
|
+
readonly listings: readonly BlogListingContent[];
|
|
18
|
+
};
|
|
19
|
+
export declare function createBlog(options: BlogOptions): Blog;
|
|
20
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/blog/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAa,KAAK,WAAW,EAAiB,MAAM,YAAY,CAAC;AAExE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAClD,CAAC;AAwDF,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAQrD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { loadPosts } from "./posts.js";
|
|
2
|
+
function postToContent(post) {
|
|
3
|
+
return {
|
|
4
|
+
path: post.path,
|
|
5
|
+
title: post.title,
|
|
6
|
+
meta: post.description ? { description: post.description } : undefined,
|
|
7
|
+
content: [
|
|
8
|
+
`<article data-content-type="article" data-slug="${post.slug}">
|
|
9
|
+
<h1>${post.title}</h1>
|
|
10
|
+
${post.bodyHtml}
|
|
11
|
+
</article>`,
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function listingPath(pathPrefix, page) {
|
|
16
|
+
return page === 1 ? pathPrefix : `${pathPrefix}/page/${page}`;
|
|
17
|
+
}
|
|
18
|
+
function buildPostListHtml(posts) {
|
|
19
|
+
return `<ul>${posts
|
|
20
|
+
.map((post) => `<li><a href="${post.path}">${post.title}</a></li>`)
|
|
21
|
+
.join("")}</ul>`;
|
|
22
|
+
}
|
|
23
|
+
function buildListingPages(posts, pathPrefix, postsPerPage) {
|
|
24
|
+
if (posts.length === 0) {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
path: pathPrefix,
|
|
28
|
+
content: ["<p>No blog posts found.</p>"],
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
const perPage = postsPerPage && postsPerPage > 0 ? postsPerPage : posts.length;
|
|
33
|
+
const pages = [];
|
|
34
|
+
for (let i = 0; i < posts.length; i += perPage) {
|
|
35
|
+
const chunk = posts.slice(i, i + perPage);
|
|
36
|
+
const page = i / perPage + 1;
|
|
37
|
+
pages.push({
|
|
38
|
+
path: listingPath(pathPrefix, page),
|
|
39
|
+
content: [buildPostListHtml(chunk)],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return pages;
|
|
43
|
+
}
|
|
44
|
+
export function createBlog(options) {
|
|
45
|
+
const loaded = loadPosts(options);
|
|
46
|
+
const pathPrefix = options.pathPrefix.replace(/\/+$/, "") || "/";
|
|
47
|
+
return {
|
|
48
|
+
posts: loaded.map(postToContent),
|
|
49
|
+
listings: buildListingPages(loaded, pathPrefix, options.postsPerPage),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../src/modules/blog/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAqBxE,SAAS,aAAa,CAAC,IAAc;IACnC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;QACtE,OAAO,EAAE;YACP,mDAAmD,IAAI,CAAC,IAAI;QAC1D,IAAI,CAAC,KAAK;IACd,IAAI,CAAC,QAAQ;WACN;SACN;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,UAAkB,EAAE,IAAY;IACnD,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,SAAS,IAAI,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAiB;IAC1C,OAAO,OAAO,KAAK;SAChB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,WAAW,CAAC;SAClE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;AACrB,CAAC;AAED,SAAS,iBAAiB,CACxB,KAAiB,EACjB,UAAkB,EAClB,YAAqB;IAErB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL;gBACE,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,CAAC,6BAA6B,CAAC;aACzC;SACF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GACX,YAAY,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;IACjE,MAAM,KAAK,GAAyB,EAAE,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC;YACnC,OAAO,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAoB;IAC7C,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IAEjE,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;QAChC,QAAQ,EAAE,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,YAAY,CAAC;KACtE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type BlogPost = {
|
|
2
|
+
slug: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
path: string;
|
|
6
|
+
bodyHtml: string;
|
|
7
|
+
};
|
|
8
|
+
export type BlogOptions = {
|
|
9
|
+
contentPath: string;
|
|
10
|
+
pathPrefix: string;
|
|
11
|
+
/** Max posts per listing page. Omit to put all posts on one page. */
|
|
12
|
+
postsPerPage?: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function loadPosts(options: BlogOptions): BlogPost[];
|
|
15
|
+
//# sourceMappingURL=posts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../../src/modules/blog/posts.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA8BF,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,EAAE,CAqB1D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { basename, extname, join, resolve } from "node:path";
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
function parseFrontmatter(raw) {
|
|
5
|
+
if (!raw.startsWith("---")) {
|
|
6
|
+
return { meta: {}, body: raw };
|
|
7
|
+
}
|
|
8
|
+
const end = raw.indexOf("\n---", 3);
|
|
9
|
+
if (end === -1) {
|
|
10
|
+
return { meta: {}, body: raw };
|
|
11
|
+
}
|
|
12
|
+
const frontmatter = raw.slice(3, end).trim();
|
|
13
|
+
const body = raw.slice(end + 4).replace(/^\n/, "");
|
|
14
|
+
const meta = {};
|
|
15
|
+
for (const line of frontmatter.split("\n")) {
|
|
16
|
+
const colon = line.indexOf(":");
|
|
17
|
+
if (colon === -1)
|
|
18
|
+
continue;
|
|
19
|
+
const key = line.slice(0, colon).trim();
|
|
20
|
+
const value = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
21
|
+
if (key)
|
|
22
|
+
meta[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return { meta, body };
|
|
25
|
+
}
|
|
26
|
+
export function loadPosts(options) {
|
|
27
|
+
const absoluteDir = resolve(options.contentPath);
|
|
28
|
+
const files = readdirSync(absoluteDir)
|
|
29
|
+
.filter((name) => extname(name) === ".md")
|
|
30
|
+
.sort();
|
|
31
|
+
return files.map((filename) => {
|
|
32
|
+
const raw = readFileSync(join(absoluteDir, filename), "utf8");
|
|
33
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
34
|
+
const slug = meta.slug ?? basename(filename, ".md");
|
|
35
|
+
const title = meta.title ?? slug;
|
|
36
|
+
const prefix = options.pathPrefix.replace(/\/+$/, "");
|
|
37
|
+
return {
|
|
38
|
+
slug,
|
|
39
|
+
title,
|
|
40
|
+
description: meta.description,
|
|
41
|
+
path: `${prefix}/${slug}`,
|
|
42
|
+
bodyHtml: marked.parse(body),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=posts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts.js","sourceRoot":"","sources":["../../../src/modules/blog/posts.ts"],"names":[],"mappings":"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"}
|