@odla-ai/blog 0.0.2 → 0.0.3
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/README.md +7 -3
- package/llms.txt +30 -13
- package/package.json +2 -1
- package/src/build.js +30 -8
- package/src/index.js +1 -0
- package/src/templates/base.js +1 -0
- package/src/themes.js +32 -0
- package/src/themes/chalk/styles.css +0 -720
- package/src/themes/chalk/theme.json +0 -6
- package/src/themes/clay/styles.css +0 -726
- package/src/themes/clay/theme.json +0 -6
- package/src/themes/juniper/styles.css +0 -660
- package/src/themes/juniper/theme.json +0 -6
- package/src/themes/salt/styles.css +0 -728
- package/src/themes/salt/theme.json +0 -6
package/README.md
CHANGED
|
@@ -33,9 +33,13 @@ Four bundled themes, all with light + dark modes (system preference + ☾/☀ to
|
|
|
33
33
|
- **chalk** — chalkboard on graph paper; copper/cerulean, mono chrome
|
|
34
34
|
- **clay** — sand paper and terracotta; system fonts only, zero external requests
|
|
35
35
|
|
|
36
|
-
Pick one with `"theme": "salt"` in `blog.config.json`.
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
Pick one with `"theme": "salt"` in `blog.config.json`. Themes ship in the
|
|
37
|
+
[`@odla-ai/ui`](https://www.npmjs.com/package/@odla-ai/ui) dependency (the odla
|
|
38
|
+
design system); every built site also gets ui's class-scoped component layer
|
|
39
|
+
(`assets/odla-ui.css` — buttons, form fields, cards, …) theme-matched via the
|
|
40
|
+
theme's `ui.css` token aliases. Override any theme file by placing a
|
|
41
|
+
same-named file in your site's `theme/` directory. Preview all four against
|
|
42
|
+
the example site with `npm run preview` (from the monorepo root).
|
|
39
43
|
|
|
40
44
|
## Deploy (Cloudflare)
|
|
41
45
|
|
package/llms.txt
CHANGED
|
@@ -109,12 +109,13 @@ const result = await build({
|
|
|
109
109
|
|
|
110
110
|
Output: `index.html`, `posts/<slug>/index.html`, `<page>/index.html`,
|
|
111
111
|
`archive/`, `tags/`, `tags/<tag>/`, `rss.xml`, `sitemap.xml`,
|
|
112
|
-
`assets/styles.css`, plus everything from `public/`.
|
|
113
|
-
with `config.basePath` (default `/`) — set it when
|
|
114
|
-
(e.g. GitHub project pages).
|
|
112
|
+
`assets/styles.css`, `assets/odla-ui.css`, plus everything from `public/`.
|
|
113
|
+
All URLs are prefixed with `config.basePath` (default `/`) — set it when
|
|
114
|
+
deploying under a subpath (e.g. GitHub project pages).
|
|
115
115
|
|
|
116
116
|
Also exported: `loadPosts`, `loadPages`, `parseFrontmatter`, `slugFromFilename`,
|
|
117
|
-
`renderMarkdown`, `slugify`, `escapeHtml`, `resolveThemeFile
|
|
117
|
+
`renderMarkdown`, `slugify`, `escapeHtml`, `resolveThemeFile`, `listThemes`,
|
|
118
|
+
`THEME_NAMES`.
|
|
118
119
|
|
|
119
120
|
## Markdown
|
|
120
121
|
|
|
@@ -125,13 +126,19 @@ no client-side JS. Headings get slugified `id` anchors.
|
|
|
125
126
|
## Theming
|
|
126
127
|
|
|
127
128
|
Bundled themes: `juniper` (default), `salt`, `chalk`, `clay` — pick with
|
|
128
|
-
`"theme": "<name>"` in config.
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
`"theme": "<name>"` in config. **Themes live in the `@odla-ai/ui`
|
|
130
|
+
dependency** (`node_modules/@odla-ai/ui/themes/<name>/`): `styles.css` (the
|
|
131
|
+
full theme), `ui.css` (maps the theme's palette onto the `--ui-*` design
|
|
132
|
+
tokens), `theme.json` (metadata). Resolution order for any theme file:
|
|
133
|
+
`<site>/theme/<file>` → `@odla-ai/ui/themes/<name>/<file>` →
|
|
134
|
+
`src/templates/<file>`. (`@odla-ai/ui` also ships a `paper` theme — it is a
|
|
135
|
+
dashboard theme, NOT a blog theme; it doesn't style the selector contract.)
|
|
136
|
+
|
|
137
|
+
Invariants every theme upholds (keep them when editing or adding one;
|
|
138
|
+
tested in @odla-ai/ui's `test/themes.test.js`):
|
|
132
139
|
- Light tokens on `:root`; dark tokens duplicated on BOTH `[data-theme="dark"]`
|
|
133
140
|
and `@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) }`.
|
|
134
|
-
The two dark blocks must stay identical
|
|
141
|
+
The two dark blocks must stay identical.
|
|
135
142
|
- Common token names: `--bg`, `--text`, `--accent`, `--border`, `--code-bg`,
|
|
136
143
|
and the highlight set `--hl-keyword`, `--hl-string`, `--hl-comment`,
|
|
137
144
|
`--hl-number`, `--hl-title`, `--hl-attr`, `--hl-type`, `--hl-meta`,
|
|
@@ -141,15 +148,25 @@ Invariants every theme upholds (keep them when editing or adding one):
|
|
|
141
148
|
`.post-title`, `.post-meta`, `.post-tag`, `.prose`, `.page-title`,
|
|
142
149
|
`.archive-year`, `.archive-list`, `.archive-item`, `.tag-cloud`, `.hljs-*`.
|
|
143
150
|
|
|
151
|
+
**Component classes on any page:** every built site also ships
|
|
152
|
+
`assets/odla-ui.css` (linked before the theme stylesheet) — the class-scoped
|
|
153
|
+
@odla-ai/ui component layer (`.btn`, `.field`/`.input`/`.textarea`, `.card`,
|
|
154
|
+
`.table`, `.banner`, chat classes, …), theme-correct because the build
|
|
155
|
+
appends the theme's `ui.css` token aliases into `assets/styles.css`. Use
|
|
156
|
+
these classes in pages/templates instead of inventing inline styles; the
|
|
157
|
+
full inventory is in `node_modules/@odla-ai/ui/llms.txt`. A site
|
|
158
|
+
`public/assets/odla-ui.css` overrides the shipped copy (public/ copies
|
|
159
|
+
last), same valve as styles.css.
|
|
160
|
+
|
|
144
161
|
**Site-local overrides:** any file placed in the site's `theme/` directory
|
|
145
|
-
shadows the packaged theme file of the same name (`styles.css`, or
|
|
146
|
-
template like `post.js` or `index.js`). Copy the packaged file out, edit,
|
|
162
|
+
shadows the packaged theme file of the same name (`styles.css`, `ui.css`, or
|
|
163
|
+
any template like `post.js` or `index.js`). Copy the packaged file out, edit,
|
|
147
164
|
done. Templates are plain JS functions returning template-literal HTML
|
|
148
165
|
strings; they may import shared helpers from the package
|
|
149
166
|
(`import { postList } from "@odla-ai/blog"`). To ADD styles without forking
|
|
150
167
|
the whole stylesheet, put them in `theme/custom.css` — the build appends it
|
|
151
|
-
to the theme's CSS. Post URLs live under
|
|
152
|
-
"posts").
|
|
168
|
+
to the theme's CSS (after the `ui.css` aliases). Post URLs live under
|
|
169
|
+
`config.postsPath` (default "posts").
|
|
153
170
|
|
|
154
171
|
## Deploy (Cloudflare)
|
|
155
172
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odla-ai/blog",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Minimal-dependency, static-first blogging platform. Files in, site out; odla-db lights up interactivity when configured.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"test": "node --test --test-reporter=spec"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@odla-ai/ui": "^0.1.0",
|
|
27
28
|
"highlight.js": "^11.11.0",
|
|
28
29
|
"marked": "^16.0.0",
|
|
29
30
|
"yaml": "^2.8.0"
|
package/src/build.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Static build: content + theme → dist/. Pure pipeline, top to bottom.
|
|
2
2
|
//
|
|
3
3
|
// Theme resolution (file-by-file shadowing):
|
|
4
|
-
// 1. <site>/theme/<file>
|
|
5
|
-
// 2.
|
|
6
|
-
// 3. src/templates/<file>
|
|
7
|
-
// Themes usually only provide styles.css; templates come from
|
|
4
|
+
// 1. <site>/theme/<file> — site-local override
|
|
5
|
+
// 2. @odla-ai/ui/themes/<name>/<file> — the chosen packaged theme
|
|
6
|
+
// 3. src/templates/<file> — shared default templates
|
|
7
|
+
// Themes usually only provide styles.css (+ ui.css); templates come from
|
|
8
|
+
// the shared set. Themes live in the @odla-ai/ui dependency.
|
|
8
9
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
9
10
|
import { existsSync } from "node:fs";
|
|
10
11
|
import { join, dirname } from "node:path";
|
|
@@ -13,15 +14,16 @@ import { loadPosts, loadPages, loadJsPages, loadCollections } from "./content.js
|
|
|
13
14
|
import { renderMarkdown, slugify } from "./markdown.js";
|
|
14
15
|
import { excerpt } from "./excerpt.js";
|
|
15
16
|
import { atom, rss, sitemap } from "./feeds.js";
|
|
17
|
+
import { THEME_NAMES, uiFile, uiThemeFile } from "./themes.js";
|
|
16
18
|
|
|
17
19
|
const SRC = dirname(fileURLToPath(import.meta.url));
|
|
18
20
|
|
|
19
21
|
export function resolveThemeFile(siteDir, themeName, file) {
|
|
20
22
|
const candidates = [
|
|
21
23
|
join(siteDir, "theme", file),
|
|
22
|
-
|
|
24
|
+
uiThemeFile(themeName, file),
|
|
23
25
|
join(SRC, "templates", file),
|
|
24
|
-
];
|
|
26
|
+
].filter(Boolean);
|
|
25
27
|
return candidates.find((c) => existsSync(c)) ?? null;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -188,17 +190,37 @@ export async function build({
|
|
|
188
190
|
await write("atom.xml", atom({ config, posts }));
|
|
189
191
|
await write("sitemap.xml", sitemap({ config, posts, pages }));
|
|
190
192
|
|
|
191
|
-
//
|
|
193
|
+
// Theme stylesheet assembly: styles.css, then the theme's ui.css (maps its
|
|
194
|
+
// palette onto the @odla-ai/ui --ui-* token contract so component classes
|
|
195
|
+
// render theme-correct), then site theme/custom.css — additions without
|
|
192
196
|
// forking the whole file (full replacement: shadow styles.css itself).
|
|
193
197
|
const styles = resolveThemeFile(siteDir, themeName, "styles.css");
|
|
194
|
-
if (!styles)
|
|
198
|
+
if (!styles) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Theme "${themeName}" has no styles.css — bundled themes: ${THEME_NAMES.join(", ")} (or add <site>/theme/styles.css)`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
195
203
|
let css = await readFile(styles, "utf8");
|
|
204
|
+
const uiAlias = resolveThemeFile(siteDir, themeName, "ui.css");
|
|
205
|
+
if (uiAlias) {
|
|
206
|
+
css += `\n/* ─── ${themeName}/ui.css (odla-ui token aliases) ─── */\n${await readFile(uiAlias, "utf8")}`;
|
|
207
|
+
}
|
|
196
208
|
const customCss = join(siteDir, "theme", "custom.css");
|
|
197
209
|
if (existsSync(customCss)) {
|
|
198
210
|
css += `\n/* ─── site custom.css ─── */\n${await readFile(customCss, "utf8")}`;
|
|
199
211
|
}
|
|
200
212
|
await write(join("assets", "styles.css"), css);
|
|
201
213
|
|
|
214
|
+
// Every site ships the ui component layer (class-scoped, inert until a
|
|
215
|
+
// page uses the classes). base.js links it BEFORE styles.css so the theme
|
|
216
|
+
// and site css win ties. A site public/assets/odla-ui.css overrides it
|
|
217
|
+
// (public/ copies last), same valve as styles.css.
|
|
218
|
+
const uiCss = uiFile("odla-ui.css");
|
|
219
|
+
if (!uiCss) {
|
|
220
|
+
throw new Error("@odla-ai/ui not found — run npm install (blog themes and component CSS ship there)");
|
|
221
|
+
}
|
|
222
|
+
await write(join("assets", "odla-ui.css"), await readFile(uiCss, "utf8"));
|
|
223
|
+
|
|
202
224
|
const publicDir = join(siteDir, "public");
|
|
203
225
|
if (existsSync(publicDir)) await cp(publicDir, outDir, { recursive: true });
|
|
204
226
|
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { build, resolveThemeFile } from "./build.js";
|
|
2
|
+
export { listThemes, THEME_NAMES } from "./themes.js";
|
|
2
3
|
export { loadPosts, loadPages, parseFrontmatter, slugFromFilename } from "./content.js";
|
|
3
4
|
export { renderMarkdown, slugify, escapeHtml } from "./markdown.js";
|
|
4
5
|
export { postList, tagList, tagUrl, formatDate, isoDate } from "./templates/helpers.js";
|
package/src/templates/base.js
CHANGED
|
@@ -125,6 +125,7 @@ export function base({ config, title, description, path, image, content }) {
|
|
|
125
125
|
<meta charset="utf-8">
|
|
126
126
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
127
127
|
${headMeta(config, { fullTitle, description, path, image })}
|
|
128
|
+
<link rel="stylesheet" href="${base}assets/odla-ui.css">
|
|
128
129
|
<link rel="stylesheet" href="${base}assets/styles.css">
|
|
129
130
|
<script>${NO_FLASH}</script>
|
|
130
131
|
</head>
|
package/src/themes.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Themes ship in @odla-ai/ui (themes/<name>/{styles.css,ui.css,theme.json});
|
|
2
|
+
// this module is the blog's bridge to them. createRequire (not
|
|
3
|
+
// import.meta.resolve) because it works on every Node 20.x, returns plain
|
|
4
|
+
// filesystem paths, and verifies existence with a catchable error.
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
// The bundled themes that uphold the blog selector contract. @odla-ai/ui
|
|
11
|
+
// also ships "paper", which does not — keep it out of this list.
|
|
12
|
+
export const THEME_NAMES = ["chalk", "clay", "juniper", "salt"];
|
|
13
|
+
|
|
14
|
+
// Absolute path of a file shipped by @odla-ai/ui, or null if absent.
|
|
15
|
+
export function uiFile(subpath) {
|
|
16
|
+
try {
|
|
17
|
+
return require.resolve(`@odla-ai/ui/${subpath}`);
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function uiThemeFile(themeName, file) {
|
|
24
|
+
return uiFile(`themes/${themeName}/${file}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Metadata for every bundled blog theme (for pickers/galleries).
|
|
28
|
+
export function listThemes() {
|
|
29
|
+
return THEME_NAMES.map((name) =>
|
|
30
|
+
JSON.parse(readFileSync(uiThemeFile(name, "theme.json"), "utf8"))
|
|
31
|
+
);
|
|
32
|
+
}
|