From c4d39ea1ad53384d050effb1f5e6fac38278efa5 Mon Sep 17 00:00:00 2001 From: biosias Date: Sat, 12 Sep 2026 16:47:02 +0200 Subject: [PATCH 1/2] Split config.js into config-default.js (tracked) + config.js (gitignored) Lets cmjs be used as a git submodule (or updated by any other means) without a local config override -- e.g. a custom ARTICLES_URL -- being clobbered by the update. js/config-default.js now holds the shipped defaults and is tracked; js/config.js is gitignored and re-exports those defaults unless overridden, and is what every other module actually imports from. Since js/config.js no longer ships with the repo, added a required one-time setup step (cp js/config-default.js js/config.js) -- new "Local configuration" section in the README, cross-referenced from "Running locally", "Embedding in an existing page", and "Pointing articles/ somewhere else". Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd --- .gitignore | 1 + README.md | 46 +++++++++++++++++++++++------ js/{config.js => config-default.js} | 9 ++++++ 3 files changed, 47 insertions(+), 9 deletions(-) rename js/{config.js => config-default.js} (68%) diff --git a/.gitignore b/.gitignore index 439dc65..6d2b586 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /articles/ +/js/config.js diff --git a/README.md b/README.md index c636baa..5a8261b 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,8 @@ from the library — not something an embedding page needs to bring along. │ ├── toc.css # Auto-generated table of contents │ └── author-profile.css # Author profile card (avatar, bio, social links) ├── js/ -│ ├── config.js # BASE_URL, MARKED_URL, ARTICLES_URL (all via import.meta.url) +│ ├── config-default.js # Shipped defaults for BASE_URL, MARKED_URL, ARTICLES_URL +│ ├── config.js # Your local overrides — gitignored; see "Local configuration" below │ ├── frontmatter.js # parseFrontmatter() — pure YAML-front parser │ ├── templates.js # loadTemplates(), templates cache │ ├── discovery.js # discoverArticles(), fetchJson(), article cache @@ -86,6 +87,31 @@ from the library — not something an embedding page needs to bring along. └── slug~DDMMYYYY~tag1+tag2.md # One file per article — see "Adding an article" ``` +## Local configuration + +**Before first use, create your own `js/config.js`** — it doesn't ship with the repo (it's +gitignored) and nothing works without it: + +```bash +cp js/config-default.js js/config.js +``` + +`js/config-default.js` holds the shipped defaults (`BASE_URL`, `MARKED_URL`, `ARTICLES_URL`) and +is tracked in git; every other module imports from `js/config.js` instead, which by default just +re-exports those defaults unchanged. The split exists for using cmjs as a git submodule (or +otherwise pulling in a newer version some other way): since `config.js` is gitignored, updating +cmjs never overwrites it, so any local overrides you've made survive the update. Only +`config-default.js` gets updated. + +To override a setting, re-export the rest from `config-default.js` and define your own instead — +e.g. to move where articles live (see "Pointing articles/ somewhere else" below for the matching +nginx-side change this needs): + +```js +export { BASE_URL, MARKED_URL } from './config-default.js'; +export const ARTICLES_URL = new URL('/blog-content/articles/', location.origin).href; +``` + ## Adding an article An article is a single Markdown file placed directly in its author's directory. Its *file name* @@ -254,6 +280,9 @@ translate it by editing that page directly (`demo/index.html` for this repo's ow ## Embedding in an existing page +Make sure you've done the one-time setup in "Local configuration" above (`cp js/config-default.js +js/config.js`) wherever you place these files — nothing loads without it. + To add cmjs to any HTML page, three things are needed: the CSS, the two mount-point `
`s, and the script. ### 1. CSS — in `` @@ -393,14 +422,10 @@ server { ### Pointing articles/ somewhere else -By default the crawler looks for articles at `articles/` next to the rest of cmjs (`ARTICLES_URL` -in `js/config.js`, computed as a sibling of the `js/` folder). Articles don't have to live there — -edit `ARTICLES_URL` in `js/config.js` to point anywhere under the same origin (or a different one, -CORS permitting), e.g.: - -```js -export const ARTICLES_URL = new URL('/blog-content/articles/', location.origin).href; -``` +By default the crawler looks for articles at `articles/` next to the rest of cmjs (`ARTICLES_URL`, +computed as a sibling of the `js/` folder). Articles don't have to live there — override +`ARTICLES_URL` in your local `js/config.js` (see "Local configuration" above) to point anywhere +under the same origin (or a different one, CORS permitting). Whatever it resolves to needs its own matching `location` block enabling the JSON (or HTML) autoindex — it doesn't have to be named `articles/` or nested under wherever cmjs itself lives: @@ -504,6 +529,9 @@ of content is ever fetched per page view. ## Running locally +Don't forget the one-time setup step in "Local configuration" above (`cp js/config-default.js +js/config.js`) — nothing loads without it. + Using nginx locally (example for Linux): ```bash # Symlink or copy the repo to your nginx webroot, then reload: diff --git a/js/config.js b/js/config-default.js similarity index 68% rename from js/config.js rename to js/config-default.js index ff3405b..4b8e980 100644 --- a/js/config.js +++ b/js/config-default.js @@ -1,5 +1,14 @@ // All paths are derived from this module's own location via import.meta.url, // so they resolve correctly regardless of where the embedding HTML page lives. +// +// This file ships with cmjs and is tracked in git — don't edit it directly. +// Everything else imports from the sibling `config.js` instead (gitignored, +// not part of this repo's history), which re-exports these defaults as-is +// unless overridden. That split means your own overrides (e.g. a custom +// ARTICLES_URL) survive updating cmjs — as a git submodule or by any other +// means of pulling in a newer version — since a fresh copy of this file +// never touches your untracked config.js. See "Local configuration" in the +// README. // Base URL of the repo root — strings/ and templates/ live here, one level // up from this file (e.g. https://example.com/). -- 2.54.0 From 9e8af56069228091405407d45347387a328953f3 Mon Sep 17 00:00:00 2001 From: biosias Date: Sat, 12 Sep 2026 16:51:18 +0200 Subject: [PATCH 2/2] Make config.js truly optional, falling back to config-default.js (AI) A plain static `import ... from './config.js'` can't gracefully degrade if that file doesn't exist -- a missing module breaks the whole graph before any application code can catch it. So the four consumers (discovery.js, i18n.js, templates.js, router.js) now import BASE_URL/MARKED_URL/ARTICLES_URL from config-default.js instead, which tries a dynamic import() of the optional config.js (catchable, unlike a static import) and merges in whatever it exports, falling back to its own shipped defaults for anything config.js doesn't override -- or doesn't exist at all. Top-level await makes every importing module wait for that resolution first, same as a plain synchronous export. config.js itself is now just an optional, empty-by-default file: only export the specific constant(s) you want to change. Updated the README's "Local configuration" section (and its three cross-references in Running locally / Embedding in an existing page / Pointing articles/ somewhere else) to drop the now-unnecessary "copy this file before first use" step entirely. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd --- README.md | 35 +++++++++++--------------------- js/config-default.js | 48 ++++++++++++++++++++++++++++---------------- js/discovery.js | 2 +- js/i18n.js | 2 +- js/router.js | 2 +- js/templates.js | 2 +- 6 files changed, 47 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 5a8261b..b1a3fdc 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ from the library — not something an embedding page needs to bring along. │ └── author-profile.css # Author profile card (avatar, bio, social links) ├── js/ │ ├── config-default.js # Shipped defaults for BASE_URL, MARKED_URL, ARTICLES_URL -│ ├── config.js # Your local overrides — gitignored; see "Local configuration" below +│ ├── config.js # Optional local overrides — gitignored; see "Local configuration" below │ ├── frontmatter.js # parseFrontmatter() — pure YAML-front parser │ ├── templates.js # loadTemplates(), templates cache │ ├── discovery.js # discoverArticles(), fetchJson(), article cache @@ -89,26 +89,21 @@ from the library — not something an embedding page needs to bring along. ## Local configuration -**Before first use, create your own `js/config.js`** — it doesn't ship with the repo (it's -gitignored) and nothing works without it: - -```bash -cp js/config-default.js js/config.js -``` - `js/config-default.js` holds the shipped defaults (`BASE_URL`, `MARKED_URL`, `ARTICLES_URL`) and -is tracked in git; every other module imports from `js/config.js` instead, which by default just -re-exports those defaults unchanged. The split exists for using cmjs as a git submodule (or -otherwise pulling in a newer version some other way): since `config.js` is gitignored, updating -cmjs never overwrites it, so any local overrides you've made survive the update. Only -`config-default.js` gets updated. +is tracked in git; every other module imports from it. It also loads an optional `js/config.js` +— gitignored, not part of cmjs's own git history — to override any of them. **Nothing needs to +be created before first use**: if `js/config.js` doesn't exist, the shipped defaults apply as-is. -To override a setting, re-export the rest from `config-default.js` and define your own instead — -e.g. to move where articles live (see "Pointing articles/ somewhere else" below for the matching -nginx-side change this needs): +The split exists for using cmjs as a git submodule (or otherwise pulling in a newer version some +other way): since `config.js` is gitignored, updating cmjs never touches it, so any override +you've made survives. Only `config-default.js` gets updated. + +To override a setting, create `js/config.js` and export just the constant(s) you want to change — +anything you don't export falls back to `config-default.js`. For example, to move where articles +live (see "Pointing articles/ somewhere else" below for the matching nginx-side change this +needs): ```js -export { BASE_URL, MARKED_URL } from './config-default.js'; export const ARTICLES_URL = new URL('/blog-content/articles/', location.origin).href; ``` @@ -280,9 +275,6 @@ translate it by editing that page directly (`demo/index.html` for this repo's ow ## Embedding in an existing page -Make sure you've done the one-time setup in "Local configuration" above (`cp js/config-default.js -js/config.js`) wherever you place these files — nothing loads without it. - To add cmjs to any HTML page, three things are needed: the CSS, the two mount-point `
`s, and the script. ### 1. CSS — in `` @@ -529,9 +521,6 @@ of content is ever fetched per page view. ## Running locally -Don't forget the one-time setup step in "Local configuration" above (`cp js/config-default.js -js/config.js`) — nothing loads without it. - Using nginx locally (example for Linux): ```bash # Symlink or copy the repo to your nginx webroot, then reload: diff --git a/js/config-default.js b/js/config-default.js index 4b8e980..02fda04 100644 --- a/js/config-default.js +++ b/js/config-default.js @@ -1,28 +1,42 @@ +// Shipped defaults for BASE_URL, MARKED_URL, ARTICLES_URL, plus the loader +// that lets an optional local js/config.js (gitignored — not part of this +// repo's history; see "Local configuration" in the README) override any of +// them. Every other module imports from THIS file, not config.js directly — +// config.js might not exist at all (nothing has created one yet, or it was +// deleted), and a plain `import ... from './config.js'` would then break +// the whole module graph with no way to catch it. A dynamic import() can be +// caught, so that's what resolves it below; top-level await makes every +// module that imports this one wait for that resolution before it runs, +// same as if these were plain synchronous exports. +// +// config.js, if present, only needs to export whichever of these it wants +// to change — anything it doesn't export falls back to the default here. // All paths are derived from this module's own location via import.meta.url, // so they resolve correctly regardless of where the embedding HTML page lives. -// -// This file ships with cmjs and is tracked in git — don't edit it directly. -// Everything else imports from the sibling `config.js` instead (gitignored, -// not part of this repo's history), which re-exports these defaults as-is -// unless overridden. That split means your own overrides (e.g. a custom -// ARTICLES_URL) survive updating cmjs — as a git submodule or by any other -// means of pulling in a newer version — since a fresh copy of this file -// never touches your untracked config.js. See "Local configuration" in the -// README. + +const DEFAULT_BASE_URL = new URL('../', import.meta.url).href; +const DEFAULT_MARKED_URL = new URL('../vendor/marked.min.js', import.meta.url).href; +const DEFAULT_ARTICLES_URL = new URL('articles/', DEFAULT_BASE_URL).href; + +let overrides = {}; +try { + overrides = await import('./config.js'); +} catch { + // No local config.js (or it failed to load) — shipped defaults stand. +} // Base URL of the repo root — strings/ and templates/ live here, one level // up from this file (e.g. https://example.com/). -export const BASE_URL = new URL('../', import.meta.url).href; +export const BASE_URL = overrides.BASE_URL ?? DEFAULT_BASE_URL; // Path to the marked.js vendor script — lives in vendor/, a sibling of this // file's own js/ directory, not alongside the app's own modules. -export const MARKED_URL = new URL('../vendor/marked.min.js', import.meta.url).href; +export const MARKED_URL = overrides.MARKED_URL ?? DEFAULT_MARKED_URL; // Base URL of the articles/ folder. Defaults to a sibling of this file's own // js/ directory (the repo root's own articles/), but articles don't have to -// live anywhere near the rest of cmjs — replace this line to point anywhere -// under the same origin (`new URL('/blog-content/articles/', location.origin).href`) -// or even a different one, CORS permitting. Whatever it resolves to must -// still be a directory nginx serves a JSON (or HTML) autoindex listing for -// — see "nginx configuration" in the README. -export const ARTICLES_URL = new URL('articles/', BASE_URL).href; +// live anywhere near the rest of cmjs — override it in config.js to point +// anywhere under the same origin, or even a different one, CORS permitting. +// Whatever it resolves to must still be a directory nginx serves a JSON (or +// HTML) autoindex listing for — see "nginx configuration" in the README. +export const ARTICLES_URL = overrides.ARTICLES_URL ?? DEFAULT_ARTICLES_URL; diff --git a/js/discovery.js b/js/discovery.js index 19bd930..4632ef6 100644 --- a/js/discovery.js +++ b/js/discovery.js @@ -1,5 +1,5 @@ import { parseFrontmatter } from './frontmatter.js'; -import { BASE_URL, ARTICLES_URL } from './config.js'; +import { BASE_URL, ARTICLES_URL } from './config-default.js'; import { mapWithConcurrency } from './concurrency.js'; import { getAllCacheEntries, setCacheEntry, pruneCache } from './cache.js'; import { t } from './i18n.js'; diff --git a/js/i18n.js b/js/i18n.js index e8285ef..34380b9 100644 --- a/js/i18n.js +++ b/js/i18n.js @@ -5,7 +5,7 @@ // locale, that file is fetched too and merged on top — a translation file // only needs to cover the keys it actually translates, missing ones still // render in English rather than breaking. -import { BASE_URL } from './config.js'; +import { BASE_URL } from './config-default.js'; const FALLBACK_LOCALE = 'en'; diff --git a/js/router.js b/js/router.js index c72f967..f5895cb 100644 --- a/js/router.js +++ b/js/router.js @@ -12,7 +12,7 @@ // 4 segments, first = "category", third = "page" → category filter, page N (#category/linux/page/2) // 4 segments, first = "tag", third = "page" → tag filter, page N (#tag/guides/page/2) -import { MARKED_URL } from './config.js'; +import { MARKED_URL } from './config-default.js'; import { loadTemplates } from './templates.js'; import { loadStrings, t } from './i18n.js'; import { loadArticleList, loadAuthorView, loadCategoryView, loadTagView, loadArticle, loadSearch } from './views.js'; diff --git a/js/templates.js b/js/templates.js index 4b746f1..9221dc5 100644 --- a/js/templates.js +++ b/js/templates.js @@ -2,7 +2,7 @@ // Each template needs its own