fossbook 0.2.10 → 0.2.12

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 CHANGED
@@ -1,504 +1,573 @@
1
- # Fossbook
2
-
3
- A lightweight static blog site generator for GitHub Pages, similar to Hugo but built with Node.js.
4
- It was originally part of the [F/OSS Comics blog](https://fosscomics.com) and is now an independent, installable package that anyone can use.
5
-
6
- ## Features
7
-
8
- - **Markdown-based** Write posts in Markdown with YAML front-matter
9
- - **Pagination** Automatic home page pagination
10
- - **Tags** Tag-based categorization with tag index and per-tag listing pages
11
- - **Theming** Bundled Archie theme with support for custom themes
12
- - **SEO** — Open Graph and Twitter Card meta tags out of the box
13
- - **Multilingual sites** Generate language-specific pages with translation links and `hreflang` metadata
14
- - **GitHub Pages** — Built-in CNAME support for custom domains
15
- - **Dev server** — Local preview server with Express
16
- - **Syntax highlighting** — Code block highlighting via highlight.js
17
- - **Mermaid diagrams** — ` ```mermaid ` code blocks render as diagrams
18
-
19
- ## Quick Start
20
-
21
- ### Install globally
22
-
23
- ```bash
24
- npm install -g fossbook
25
- ```
26
-
27
- ### Create a new site
28
-
29
- ```bash
30
- mkdir my-blog && cd my-blog
31
- fossbook init
32
- ```
33
-
34
- This creates the following structure:
35
-
36
- ```
37
- my-blog/
38
- ├── content/
39
- │ ├── about.md
40
- │ └── posts/
41
- ├── static/
42
- │ └── images/
43
- ├── fossbook.config.js
44
- └── package.json
45
- ```
46
-
47
- ### Create a new post
48
-
49
- ```bash
50
- fossbook new "My First Post"
51
- ```
52
-
53
- This creates `content/posts/My First Post/index.md` with pre-filled front-matter and an `images/` directory.
54
-
55
- To create the default article and one or more configured translations together:
56
-
57
- ```bash
58
- fossbook new "My First Post" --lang ko,ja
59
- ```
60
-
61
- This also creates `index.ko.md` and `index.ja.md` in the same post directory. Running the command again preserves existing files and creates only missing translations.
62
-
63
- ### Build the site
64
-
65
- ```bash
66
- fossbook build
67
- ```
68
-
69
- ### Preview locally
70
-
71
- ```bash
72
- fossbook serve
73
- ```
74
-
75
- Open http://localhost:3000 to view your site.
76
-
77
- ## Configuration
78
-
79
- Create a `fossbook.config.js` in your project root:
80
-
81
- ```js
82
- module.exports = {
83
- blogName: "My Blog",
84
- authorName: "Your Name",
85
- authorDescription: "A short bio",
86
- authorWebsite: "https://example.com",
87
- blogDescription: "A blog about things",
88
- blogsite: "https://example.com",
89
-
90
- // Optional
91
- githubCNAME: "example.com",
92
- googleAnalyticsID: "",
93
- authorTwitter: "@you",
94
- siteTwitter: "@yourblog",
95
- githubRepository: "https://github.com/you/your-blog",
96
- image: "https://example.com/default-image.png",
97
- theme: "archie",
98
-
99
- // Optional: URL prefix for posts. Defaults to "posts" -> /posts/<slug>/.
100
- // Set to "" to serve posts at the site root, /<slug>/.
101
- postsPath: "posts",
102
-
103
- // Optional multilingual configuration
104
- defaultLanguage: "en",
105
- defaultLanguageInSubdir: false,
106
- languages: {
107
- en: { languageName: "English", locale: "en-US" },
108
- ko: { languageName: "한국어", locale: "ko-KR" },
109
- },
110
-
111
- // Optional comments (see "Comments" below)
112
- comments: {
113
- provider: "utterances",
114
- repo: "you/your-blog",
115
- issueTerm: "pathname",
116
- theme: "github-light",
117
- },
118
-
119
- // Directory overrides (defaults shown)
120
- content: "./content",
121
- postsDir: "./content/posts",
122
- outputDir: "./public",
123
- staticDir: "./static",
124
- themesDir: "./themes",
125
- };
126
- ```
127
-
128
- ## Content Format
129
-
130
- Fossbook supports GitHub Flavored Markdown plus extensions for image sizing
131
- and alignment, captions, comic dialogue, aligned links, Mermaid diagrams, and
132
- syntax-highlighted code. See [Fossbook Markdown](fossbook_markdown.md) for
133
- syntax and examples.
134
-
135
- ### Post front-matter
136
-
137
- ```markdown
138
- ---
139
- title: My Post Title
140
- date: 2026-02-17
141
- description: "A brief summary of the post"
142
- image: "feature.png"
143
- tags: "JavaScript, Node.js, Static Site"
144
- ---
145
-
146
- Your Markdown content here...
147
- ```
148
-
149
- ### Directory structure
150
-
151
- ```
152
- content/posts/My Post Title/
153
- ├── index.md
154
- └── images/
155
- └── feature.png
156
- ```
157
-
158
- ### Post URLs
159
-
160
- By default, posts are served under `/posts/`, e.g. `/posts/my-post-title/`. To
161
- change the prefix or move posts to the site root, set `postsPath` in
162
- `fossbook.config.js`:
163
-
164
- ```js
165
- postsPath: "", // serves the post above at /my-post-title/ (site root)
166
- // postsPath: "blog", // or use a different prefix: /blog/my-post-title/
167
- ```
168
-
169
- This affects the generated output directory, the post URL, post links on the
170
- home/all-posts/tag pages, and image paths. The on-disk source layout under
171
- `content/posts/` does not change.
172
-
173
- ## Multilingual Sites
174
-
175
- Fossbook generates a separate static page for each available language. The
176
- browser loads only the selected language page; changing languages follows a
177
- normal link and does not require JavaScript.
178
-
179
- When a reader uses the language switcher, Fossbook remembers that choice in
180
- the browser. A later visit to the default-language homepage redirects to the
181
- remembered language's homepage. Direct links to posts and other pages are
182
- never redirected, and language links continue to work when browser storage is
183
- unavailable.
184
-
185
- ### Configuration
186
-
187
- Add the languages supported by the site to `fossbook.config.js`:
188
-
189
- ```js
190
- module.exports = {
191
- defaultLanguage: "en",
192
- defaultLanguageInSubdir: false,
193
- languages: {
194
- en: {
195
- languageName: "English",
196
- locale: "en-US",
197
- },
198
- ko: {
199
- languageName: "한국어",
200
- locale: "ko-KR",
201
- },
202
- ja: {
203
- languageName: "日本語",
204
- locale: "ja-JP",
205
- },
206
- },
207
- };
208
- ```
209
-
210
- Put localized blog information in a sibling config file for each language. For
211
- example, `fossbook.config.en.js` contains:
212
-
213
- ```js
214
- module.exports = {
215
- blogName: "My Blog",
216
- authorName: "Jane Doe",
217
- authorDescription: "Open source developer and writer",
218
- blogDescription: "An English-language blog",
219
- };
220
- ```
221
-
222
- And `fossbook.config.ko.js` contains:
223
-
224
- ```js
225
- module.exports = {
226
- blogName: "나의 블로그",
227
- authorName: "이수현",
228
- authorDescription: "오픈 소스 개발자이자 작가",
229
- blogDescription: "한국어 블로그",
230
- homeLabel: "대문",
231
- allPostsLabel: "모든 글",
232
- aboutLabel: "소개",
233
- tagsLabel: "태그",
234
- allTagsLabel: "모든 태그",
235
- };
236
- ```
237
-
238
- - `defaultLanguage` identifies the language represented by `index.md` and
239
- `about.md`.
240
- - `defaultLanguageInSubdir: false` keeps the default language at the site root.
241
- Other languages are generated below their language code, such as `/ko/`.
242
- - Set `defaultLanguageInSubdir: true` to generate the default language below
243
- its code as well, such as `/en/`.
244
- - Language config files use the main config filename with the language code
245
- inserted before the extension: `fossbook.config.<language>.js`.
246
- - Values omitted from a language config fall back to `fossbook.config.js`.
247
- - `locale` controls language-specific date formatting.
248
- - Language keys should use standard language tags such as `en`, `ko`, `ja`, or
249
- `zh-Hant`.
250
-
251
- If `languages` is omitted, Fossbook keeps its existing single-language
252
- behavior and URLs.
253
-
254
- ### Create translated posts
255
-
256
- Create only the default-language article:
257
-
258
- ```bash
259
- fossbook new "Charles Babbage and Ada Lovelace"
260
- ```
261
-
262
- Create the default article and multiple translations together:
263
-
264
- ```bash
265
- fossbook new "Charles Babbage and Ada Lovelace" --lang ko,ja
266
- ```
267
-
268
- The languages passed to `--lang` must be present in the `languages`
269
- configuration. The command always ensures that the default `index.md` exists,
270
- then creates the requested translation files. Existing Markdown files and
271
- images are never overwritten, so the command can be run later to add another
272
- translation:
273
-
274
- ```bash
275
- fossbook new "Charles Babbage and Ada Lovelace" --lang ja
276
- ```
277
-
278
- ### Source structure
279
-
280
- Translations live in the same post bundle and share its `images/` directory:
281
-
282
- ```text
283
- content/
284
- ├── about.md
285
- ├── about.ko.md
286
- └── posts/
287
- └── Charles Babbage and Ada Lovelace/
288
- ├── index.md
289
- ├── index.ko.md
290
- ├── index.ja.md
291
- └── images/
292
- └── feature.png
293
- ```
294
-
295
- - `index.md` is the default-language article.
296
- - `index.<language>.md` is a translated article.
297
- - `about.md` is the default About page.
298
- - `about.<language>.md` is a translated About page.
299
- - A translation is generated only when its Markdown source exists. Fossbook
300
- does not substitute default-language content for a missing translation.
301
-
302
- ### Generated output
303
-
304
- With `defaultLanguage: "en"` and `defaultLanguageInSubdir: false`, the example
305
- above generates:
306
-
307
- ```text
308
- public/
309
- ├── index.html
310
- ├── about/index.html
311
- ├── posts/Charles Babbage and Ada Lovelace/index.html
312
- ├── ko/
313
- │ ├── index.html
314
- │ ├── about/index.html
315
- │ └── posts/Charles Babbage and Ada Lovelace/index.html
316
- └── ja/
317
- ├── index.html
318
- └── posts/Charles Babbage and Ada Lovelace/index.html
319
- ```
320
-
321
- Translated article and About pages display ISO language links such as `EN`,
322
- `KO`, and `JA`. Only translations that exist are shown. These pages also emit
323
- canonical URLs and `hreflang` alternate links for search engines.
324
-
325
- ### Mermaid diagrams
326
-
327
- Fenced code blocks tagged `mermaid` are rendered as diagrams instead of code.
328
- The [Mermaid](https://mermaid.js.org) script is loaded from a CDN only on pages
329
- that contain a diagram.
330
-
331
- ````markdown
332
- ```mermaid
333
- sequenceDiagram
334
- Alice->>Bob: Hello Bob
335
- Bob-->>Alice: Hi Alice
336
- ```
337
- ````
338
-
339
- ## CLI Reference
340
-
341
- ```
342
- Usage: fossbook <command> [options]
343
-
344
- Commands:
345
- build Build the static site
346
- serve Build and start a local dev server
347
- new <title> Create a new post
348
- init Create a new fossbook site project
349
-
350
- Options:
351
- -c, --config Path to config file (default: ./fossbook.config.js)
352
- -o, --output Output directory (default: ./public)
353
- -p, --port Dev server port (default: 3000)
354
- --lang (new) Comma-separated translation languages, e.g. ko,ja
355
- -v, --version Show version number
356
- -h, --help Show help
357
- ```
358
-
359
- ## Theming
360
-
361
- Fossbook ships with the Archie theme by default. To use a custom theme:
362
-
363
- 1. Create a `themes/<your-theme>/` directory in your project
364
- 2. Add `layouts/` (HTML templates) and `assets/` (CSS, fonts, images)
365
- 3. Set `theme: "your-theme"` in `fossbook.config.js`
366
-
367
- Theme resolution order: user project `themes/` built-in `themes/`.
368
-
369
- ## Comments
370
-
371
- Fossbook supports [utterances](https://utteranc.es) — a commenting widget that
372
- stores comments as GitHub issues. When enabled, a comment box is rendered at the
373
- bottom of every post page.
374
-
375
- ### Setup
376
-
377
- 1. Make the repository that backs the comments **public**.
378
- 2. Install the [utterances GitHub App](https://github.com/apps/utterances) on
379
- that repository so the bot can create issues.
380
- 3. Add a `comments` block to `fossbook.config.js`:
381
-
382
- ```js
383
- comments: {
384
- provider: "utterances", // currently the only supported provider
385
- repo: "you/your-blog", // owner/repo that stores the comment issues
386
- issueTerm: "pathname", // how a post maps to an issue (see below)
387
- theme: "github-light", // any utterances theme, e.g. "github-dark"
388
- },
389
- ```
390
-
391
- Omit the `comments` block (or set it to `null`) to disable comments.
392
-
393
- ### Options
394
-
395
- | Field | Required | Default | Description |
396
- | ----------- | -------- | ---------------- | ---------------------------------------------------------------- |
397
- | `provider` | yes | — | Must be `"utterances"`. |
398
- | `repo` | yes | — | `owner/repo` whose issues store the comments. |
399
- | `issueTerm` | no | `"pathname"` | Mapping between a page and its issue: `pathname`, `url`, `title`, `og:title`. |
400
- | `theme` | no | `"github-light"` | Any [utterances theme](https://utteranc.es/#configuration). |
401
-
402
- ### Mapping notes
403
-
404
- With `issueTerm: "pathname"`, each post is matched to a GitHub issue whose title
405
- equals the page's pathname (with the leading slash stripped). If you change a
406
- post's URL, the existing comment thread no longer matches — rename the issue
407
- title to the new pathname to keep the old comments.
408
-
409
- ### Required layout files
410
-
411
- ```
412
- layouts/
413
- ├── home.html # Home page with pagination
414
- ├── post.html # Individual post page
415
- ├── page.html # Static pages (e.g., about)
416
- ├── all_posts.html # All posts listing
417
- ├── tag.html # Per-tag listing
418
- ├── tag_list.html # Tag index page
419
- └── partials/
420
- └── footer.html # Footer partial
421
- ```
422
-
423
- ## Deploying to GitHub Pages
424
-
425
- Fossbook can automatically deploy your blog to GitHub Pages using GitHub Actions.
426
-
427
- ### Prerequisites: Install GitHub CLI (`gh`)
428
-
429
- The `fossbook deploy` command uses the GitHub CLI to create repositories and monitor deployments.
430
-
431
- **Linux (Debian/Ubuntu):**
432
-
433
- ```bash
434
- sudo apt install gh
435
- ```
436
-
437
- **macOS:**
438
-
439
- ```bash
440
- brew install gh
441
- ```
442
-
443
- **Windows:**
444
-
445
- ```bash
446
- winget install GitHub.cli
447
- ```
448
-
449
- Then authenticate with your GitHub account:
450
-
451
- ```bash
452
- gh auth login
453
- ```
454
-
455
- Follow the prompts to log in via browser or token.
456
-
457
- ### Initialize with GitHub
458
-
459
- ```bash
460
- mkdir my-blog && cd my-blog
461
- fossbook init --github
462
- ```
463
-
464
- This will:
465
- 1. Scaffold the site project (config, content directories)
466
- 2. Create a GitHub repository for your blog
467
- 3. Generate `.github/workflows/deploy.yml` for automatic deployments
468
- 4. Push the initial commit to GitHub
469
-
470
- ### Publish a post
471
-
472
- ```bash
473
- fossbook new "My New Article"
474
- # ... edit content/posts/My New Article/index.md ...
475
- fossbook deploy
476
- ```
477
-
478
- Fossbook will build the site, commit, push to GitHub, wait for the CI/CD pipeline to finish, and display the live URL:
479
-
480
- ```
481
- Building site... done.
482
- Committing: "Publish: My New Article"
483
- Pushing to origin/main...
484
- Waiting for GitHub Pages deployment... ✓
485
-
486
- Published! View your article at:
487
- https://username.github.io/my-blog/My%20New%20Article/
488
- ```
489
-
490
- **Deploy options:**
491
-
492
- ```bash
493
- fossbook deploy --message "Update homepage" # Custom commit message
494
- fossbook deploy --no-wait # Push without waiting for CI
495
- ```
496
-
497
- ## License
498
-
499
- - Generator code: [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
500
- - Archie theme: [MIT License](https://github.com/athul/archie?tab=MIT-1-ov-file#readme)
501
-
502
- ## Credits
503
-
504
- Adapted from [kartiknair's blog](https://github.com/kartiknair/blog) and styled using the [Archie theme](https://github.com/athul/archie).
1
+ # Fossbook
2
+
3
+ A Markdown-based authoring and web-publishing tool for comics, illustrated
4
+ stories, and articles. Fossbook extends familiar Markdown with comic panels,
5
+ responsive layouts, image-linked dialogue, and reader-controlled transcripts,
6
+ then generates a complete static site that can be published on GitHub Pages.
7
+
8
+ Fossbook began as the publishing system for
9
+ [F/OSS Comics](https://fosscomics.com). It is now an independent Node.js
10
+ package for creators who want to keep words, artwork, translations, and site
11
+ configuration in portable source files rather than a proprietary editor.
12
+
13
+ ![A cartoonist assembles colorful blocks into a larger structure, representing Fossbook's composable publishing workflow.](docs/images/fossbook-comic-authoring.png)
14
+
15
+ _Artwork from [F/OSS Comics](https://fosscomics.com)._
16
+
17
+ ## Features
18
+
19
+ - **Comic authoring in Markdown** — Combine artwork, captions, dialogue,
20
+ narration, and links in readable text files
21
+ - **Panels and responsive layouts** — Arrange images or mixed-content comic
22
+ panels in grids that collapse for narrow screens
23
+ - **Accessible transcripts** — Associate dialogue with artwork and let readers
24
+ show or hide transcript text while keeping it available for printing
25
+ - **Image presentation controls** — Set image size, alignment, captions, panel
26
+ borders, dividers, backgrounds, and spacing with constrained Markdown syntax
27
+ - **Multilingual publishing** Keep translations beside the original work and
28
+ generate language-specific pages, navigation, and `hreflang` metadata
29
+ - **Static web output** — Generate fast, portable HTML with pagination, tags,
30
+ SEO metadata, syntax highlighting, and Mermaid diagrams
31
+ - **Creator-owned workflow** — Store Markdown and artwork in Git, preview
32
+ locally, customize themes, and deploy to GitHub Pages
33
+
34
+ Fossbook also works as a conventional static blog generator. Its comic
35
+ extensions build on GitHub Flavored Markdown instead of replacing it.
36
+
37
+ ## Quick Start
38
+
39
+ ### Install globally
40
+
41
+ ```bash
42
+ npm install -g fossbook
43
+ ```
44
+
45
+ ### Create a new publication
46
+
47
+ ```bash
48
+ mkdir my-blog && cd my-blog
49
+ fossbook init
50
+ ```
51
+
52
+ This creates a publication project with the following structure:
53
+
54
+ ```
55
+ my-blog/
56
+ ├── content/
57
+ │ ├── about.md
58
+ │ └── posts/
59
+ ├── static/
60
+ │ └── images/
61
+ ├── fossbook.config.js
62
+ └── package.json
63
+ ```
64
+
65
+ ### Create a new work
66
+
67
+ ```bash
68
+ fossbook new "My First Post"
69
+ ```
70
+
71
+ This creates `content/posts/My First Post/index.md` with pre-filled front-matter
72
+ and an `images/` directory for comic panels, illustrations, and other artwork.
73
+
74
+ To create the default article and one or more configured translations together:
75
+
76
+ ```bash
77
+ fossbook new "My First Post" --lang ko,ja
78
+ ```
79
+
80
+ This also creates `index.ko.md` and `index.ja.md` in the same post directory. Running the command again preserves existing files and creates only missing translations.
81
+
82
+ ### Build the site
83
+
84
+ ```bash
85
+ fossbook build
86
+ ```
87
+
88
+ ### Preview locally
89
+
90
+ ```bash
91
+ fossbook serve
92
+ ```
93
+
94
+ Open http://localhost:3000 to view your site.
95
+
96
+ ## Configuration
97
+
98
+ Create a `fossbook.config.js` in your project root:
99
+
100
+ ```js
101
+ module.exports = {
102
+ blogName: "My Blog",
103
+ authorName: "Your Name",
104
+ authorDescription: "A short bio",
105
+ authorWebsite: "https://example.com",
106
+ blogDescription: "A blog about things",
107
+ blogsite: "https://example.com",
108
+
109
+ // Optional
110
+ githubCNAME: "example.com",
111
+ googleAnalyticsID: "",
112
+ authorTwitter: "@you",
113
+ siteTwitter: "@yourblog",
114
+ githubRepository: "https://github.com/you/your-blog",
115
+ image: "https://example.com/default-image.png",
116
+ theme: "archie",
117
+
118
+ // Optional: URL prefix for posts. Defaults to "posts" -> /posts/<slug>/.
119
+ // Set to "" to serve posts at the site root, /<slug>/.
120
+ postsPath: "posts",
121
+
122
+ // Optional multilingual configuration
123
+ defaultLanguage: "en",
124
+ defaultLanguageInSubdir: false,
125
+ languages: {
126
+ en: { languageName: "English", locale: "en-US" },
127
+ ko: { languageName: "한국어", locale: "ko-KR" },
128
+ },
129
+
130
+ // Optional comments (see "Comments" below)
131
+ comments: {
132
+ provider: "utterances",
133
+ repo: "you/your-blog",
134
+ issueTerm: "pathname",
135
+ theme: "github-light",
136
+ },
137
+
138
+ // Directory overrides (defaults shown)
139
+ content: "./content",
140
+ postsDir: "./content/posts",
141
+ outputDir: "./public",
142
+ staticDir: "./static",
143
+ themesDir: "./themes",
144
+ };
145
+ ```
146
+
147
+ ## Content Format
148
+
149
+ Fossbook source remains readable Markdown. A work combines YAML front-matter,
150
+ prose, and standard GitHub Flavored Markdown with optional publishing
151
+ extensions for visual storytelling.
152
+
153
+ ### Authoring comics
154
+
155
+ Keep each work and its artwork together. Standard Markdown images become comic
156
+ panels; image titles can control captions, size, and alignment. A blockquote
157
+ placed immediately after an image becomes its dialogue transcript:
158
+
159
+ ```markdown
160
+ ![Two programmers talking](images/panel-01.png "size:70% A late-night debugging session")
161
+
162
+ > "Do you think anyone will notice?" \
163
+ > "They always notice."
164
+ ```
165
+
166
+ Use `:::panel` containers to frame artwork, dialogue, and narration as one
167
+ scene. Use `:::panels` to compose responsive multi-column sequences:
168
+
169
+ ```markdown
170
+ ::::panels columns="2" label="Two scenes"
171
+ :::panel
172
+ ![The first scene](images/scene-01.png)
173
+
174
+ > Dialogue for the first scene.
175
+
176
+ Narration following the first scene.
177
+ :::
178
+
179
+ :::panel divider="true"
180
+ ![The second scene](images/scene-02.png)
181
+
182
+ > Dialogue for the second scene.
183
+
184
+ Narration following the dialogue.
185
+ :::
186
+ ::::
187
+ ```
188
+
189
+ The bundled theme adapts panel groups and sized images for mobile screens. It
190
+ also adds a transcript visibility control when a work contains image-linked
191
+ dialogue. Alternative text, captions, group labels, and transcript text remain
192
+ part of the authored source rather than being baked into the page layout.
193
+
194
+ See [Fossbook Markdown](fossbook_markdown.md) for the complete syntax for
195
+ panels, responsive groups, image presentation, dialogue, aligned links,
196
+ Mermaid diagrams, and syntax-highlighted code.
197
+
198
+ ### Post front-matter
199
+
200
+ ```markdown
201
+ ---
202
+ title: My Post Title
203
+ date: 2026-02-17
204
+ description: "A brief summary of the post"
205
+ image: "feature.png"
206
+ tags: "JavaScript, Node.js, Static Site"
207
+ ---
208
+
209
+ Your Markdown content here...
210
+ ```
211
+
212
+ ### Directory structure
213
+
214
+ ```
215
+ content/posts/My Post Title/
216
+ ├── index.md
217
+ └── images/
218
+ └── feature.png
219
+ ```
220
+
221
+ ### Post URLs
222
+
223
+ By default, posts are served under `/posts/`, e.g. `/posts/my-post-title/`. To
224
+ change the prefix or move posts to the site root, set `postsPath` in
225
+ `fossbook.config.js`:
226
+
227
+ ```js
228
+ postsPath: "", // serves the post above at /my-post-title/ (site root)
229
+ // postsPath: "blog", // or use a different prefix: /blog/my-post-title/
230
+ ```
231
+
232
+ This affects the generated output directory, the post URL, post links on the
233
+ home/all-posts/tag pages, and image paths. The on-disk source layout under
234
+ `content/posts/` does not change.
235
+
236
+ ## Multilingual Sites
237
+
238
+ Fossbook generates a separate static page for each available language. The
239
+ browser loads only the selected language page; changing languages follows a
240
+ normal link and does not require JavaScript.
241
+
242
+ When a reader uses the language switcher, Fossbook remembers that choice in
243
+ the browser. A later visit to the default-language homepage redirects to the
244
+ remembered language's homepage. Direct links to posts and other pages are
245
+ never redirected, and language links continue to work when browser storage is
246
+ unavailable.
247
+
248
+ ### Configuration
249
+
250
+ Add the languages supported by the site to `fossbook.config.js`:
251
+
252
+ ```js
253
+ module.exports = {
254
+ defaultLanguage: "en",
255
+ defaultLanguageInSubdir: false,
256
+ languages: {
257
+ en: {
258
+ languageName: "English",
259
+ locale: "en-US",
260
+ },
261
+ ko: {
262
+ languageName: "한국어",
263
+ locale: "ko-KR",
264
+ },
265
+ ja: {
266
+ languageName: "日本語",
267
+ locale: "ja-JP",
268
+ },
269
+ },
270
+ };
271
+ ```
272
+
273
+ Put localized blog information in a sibling config file for each language. For
274
+ example, `fossbook.config.en.js` contains:
275
+
276
+ ```js
277
+ module.exports = {
278
+ blogName: "My Blog",
279
+ authorName: "Jane Doe",
280
+ authorDescription: "Open source developer and writer",
281
+ blogDescription: "An English-language blog",
282
+ };
283
+ ```
284
+
285
+ And `fossbook.config.ko.js` contains:
286
+
287
+ ```js
288
+ module.exports = {
289
+ blogName: "나의 블로그",
290
+ authorName: "이수현",
291
+ authorDescription: "오픈 소스 개발자이자 작가",
292
+ blogDescription: "한국어 블로그",
293
+ homeLabel: "처음",
294
+ allPostsLabel: "모든 글",
295
+ aboutLabel: "이곳은",
296
+ tagsLabel: "태그",
297
+ allTagsLabel: "모든 태그",
298
+ postedOnLabel: "올린 날:",
299
+ readMoreLabel: "더 읽기",
300
+ previousPageLabel: "앞으로",
301
+ nextPageLabel: "뒤로",
302
+ transcriptLabel: "말글 보이기",
303
+ };
304
+ ```
305
+
306
+ - `defaultLanguage` identifies the language represented by `index.md` and
307
+ `about.md`.
308
+ - `defaultLanguageInSubdir: false` keeps the default language at the site root.
309
+ Other languages are generated below their language code, such as `/ko/`.
310
+ - Set `defaultLanguageInSubdir: true` to generate the default language below
311
+ its code as well, such as `/en/`.
312
+ - Language config files use the main config filename with the language code
313
+ inserted before the extension: `fossbook.config.<language>.js`.
314
+ - Values omitted from a language config fall back to `fossbook.config.js`.
315
+ - `locale` controls language-specific date formatting.
316
+ - Language keys should use standard language tags such as `en`, `ko`, `ja`, or
317
+ `zh-Hant`.
318
+
319
+ If `languages` is omitted, Fossbook keeps its existing single-language
320
+ behavior and URLs.
321
+
322
+ ### Create translated posts
323
+
324
+ Create only the default-language article:
325
+
326
+ ```bash
327
+ fossbook new "Charles Babbage and Ada Lovelace"
328
+ ```
329
+
330
+ Create the default article and multiple translations together:
331
+
332
+ ```bash
333
+ fossbook new "Charles Babbage and Ada Lovelace" --lang ko,ja
334
+ ```
335
+
336
+ The languages passed to `--lang` must be present in the `languages`
337
+ configuration. The command always ensures that the default `index.md` exists,
338
+ then creates the requested translation files. Existing Markdown files and
339
+ images are never overwritten, so the command can be run later to add another
340
+ translation:
341
+
342
+ ```bash
343
+ fossbook new "Charles Babbage and Ada Lovelace" --lang ja
344
+ ```
345
+
346
+ ### Source structure
347
+
348
+ Translations live in the same post bundle and share its `images/` directory:
349
+
350
+ ```text
351
+ content/
352
+ ├── about.md
353
+ ├── about.ko.md
354
+ └── posts/
355
+ └── Charles Babbage and Ada Lovelace/
356
+ ├── index.md
357
+ ├── index.ko.md
358
+ ├── index.ja.md
359
+ └── images/
360
+ └── feature.png
361
+ ```
362
+
363
+ - `index.md` is the default-language article.
364
+ - `index.<language>.md` is a translated article.
365
+ - `about.md` is the default About page.
366
+ - `about.<language>.md` is a translated About page.
367
+ - A translation is generated only when its Markdown source exists. Fossbook
368
+ does not substitute default-language content for a missing translation.
369
+
370
+ ### Generated output
371
+
372
+ With `defaultLanguage: "en"` and `defaultLanguageInSubdir: false`, the example
373
+ above generates:
374
+
375
+ ```text
376
+ public/
377
+ ├── index.html
378
+ ├── about/index.html
379
+ ├── posts/Charles Babbage and Ada Lovelace/index.html
380
+ ├── ko/
381
+ │ ├── index.html
382
+ │ ├── about/index.html
383
+ │ └── posts/Charles Babbage and Ada Lovelace/index.html
384
+ └── ja/
385
+ ├── index.html
386
+ └── posts/Charles Babbage and Ada Lovelace/index.html
387
+ ```
388
+
389
+ Translated article and About pages display ISO language links such as `EN`,
390
+ `KO`, and `JA`. Only translations that exist are shown. These pages also emit
391
+ canonical URLs and `hreflang` alternate links for search engines.
392
+
393
+ ### Mermaid diagrams
394
+
395
+ Fenced code blocks tagged `mermaid` are rendered as diagrams instead of code.
396
+ The [Mermaid](https://mermaid.js.org) script is loaded from a CDN only on pages
397
+ that contain a diagram.
398
+
399
+ ````markdown
400
+ ```mermaid
401
+ sequenceDiagram
402
+ Alice->>Bob: Hello Bob
403
+ Bob-->>Alice: Hi Alice
404
+ ```
405
+ ````
406
+
407
+ ## CLI Reference
408
+
409
+ ```
410
+ Usage: fossbook <command> [options]
411
+
412
+ Commands:
413
+ build Build the static site
414
+ serve Build and start a local dev server
415
+ new <title> Create a new post
416
+ init Create a new fossbook site project
417
+
418
+ Options:
419
+ -c, --config Path to config file (default: ./fossbook.config.js)
420
+ -o, --output Output directory (default: ./public)
421
+ -p, --port Dev server port (default: 3000)
422
+ --lang (new) Comma-separated translation languages, e.g. ko,ja
423
+ -v, --version Show version number
424
+ -h, --help Show help
425
+ ```
426
+
427
+ ## Theming
428
+
429
+ Fossbook ships with the Archie theme by default. To use a custom theme:
430
+
431
+ 1. Create a `themes/<your-theme>/` directory in your project
432
+ 2. Add `layouts/` (HTML templates) and `assets/` (CSS, fonts, images)
433
+ 3. Set `theme: "your-theme"` in `fossbook.config.js`
434
+
435
+ Theme resolution order: user project `themes/` → built-in `themes/`.
436
+
437
+ ## Comments
438
+
439
+ Fossbook supports [utterances](https://utteranc.es) — a commenting widget that
440
+ stores comments as GitHub issues. When enabled, a comment box is rendered at the
441
+ bottom of every post page.
442
+
443
+ ### Setup
444
+
445
+ 1. Make the repository that backs the comments **public**.
446
+ 2. Install the [utterances GitHub App](https://github.com/apps/utterances) on
447
+ that repository so the bot can create issues.
448
+ 3. Add a `comments` block to `fossbook.config.js`:
449
+
450
+ ```js
451
+ comments: {
452
+ provider: "utterances", // currently the only supported provider
453
+ repo: "you/your-blog", // owner/repo that stores the comment issues
454
+ issueTerm: "pathname", // how a post maps to an issue (see below)
455
+ theme: "github-light", // any utterances theme, e.g. "github-dark"
456
+ },
457
+ ```
458
+
459
+ Omit the `comments` block (or set it to `null`) to disable comments.
460
+
461
+ ### Options
462
+
463
+ | Field | Required | Default | Description |
464
+ | ----------- | -------- | ---------------- | ----------------------------------------------------------------------------- |
465
+ | `provider` | yes | — | Must be `"utterances"`. |
466
+ | `repo` | yes | — | `owner/repo` whose issues store the comments. |
467
+ | `issueTerm` | no | `"pathname"` | Mapping between a page and its issue: `pathname`, `url`, `title`, `og:title`. |
468
+ | `theme` | no | `"github-light"` | Any [utterances theme](https://utteranc.es/#configuration). |
469
+
470
+ ### Mapping notes
471
+
472
+ With `issueTerm: "pathname"`, each post is matched to a GitHub issue whose title
473
+ equals the page's pathname (with the leading slash stripped). If you change a
474
+ post's URL, the existing comment thread no longer matches — rename the issue
475
+ title to the new pathname to keep the old comments.
476
+
477
+ ### Required layout files
478
+
479
+ ```
480
+ layouts/
481
+ ├── home.html # Home page with pagination
482
+ ├── post.html # Individual post page
483
+ ├── page.html # Static pages (e.g., about)
484
+ ├── all_posts.html # All posts listing
485
+ ├── tag.html # Per-tag listing
486
+ ├── tag_list.html # Tag index page
487
+ └── partials/
488
+ └── footer.html # Footer partial
489
+ ```
490
+
491
+ ## Deploying to GitHub Pages
492
+
493
+ Fossbook can automatically deploy your blog to GitHub Pages using GitHub Actions.
494
+
495
+ ### Prerequisites: Install GitHub CLI (`gh`)
496
+
497
+ The `fossbook deploy` command uses the GitHub CLI to create repositories and monitor deployments.
498
+
499
+ **Linux (Debian/Ubuntu):**
500
+
501
+ ```bash
502
+ sudo apt install gh
503
+ ```
504
+
505
+ **macOS:**
506
+
507
+ ```bash
508
+ brew install gh
509
+ ```
510
+
511
+ **Windows:**
512
+
513
+ ```bash
514
+ winget install GitHub.cli
515
+ ```
516
+
517
+ Then authenticate with your GitHub account:
518
+
519
+ ```bash
520
+ gh auth login
521
+ ```
522
+
523
+ Follow the prompts to log in via browser or token.
524
+
525
+ ### Initialize with GitHub
526
+
527
+ ```bash
528
+ mkdir my-blog && cd my-blog
529
+ fossbook init --github
530
+ ```
531
+
532
+ This will:
533
+
534
+ 1. Scaffold the site project (config, content directories)
535
+ 2. Create a GitHub repository for your blog
536
+ 3. Generate `.github/workflows/deploy.yml` for automatic deployments
537
+ 4. Push the initial commit to GitHub
538
+
539
+ ### Publish a post
540
+
541
+ ```bash
542
+ fossbook new "My New Article"
543
+ # ... edit content/posts/My New Article/index.md ...
544
+ fossbook deploy
545
+ ```
546
+
547
+ Fossbook will build the site, commit, push to GitHub, wait for the CI/CD pipeline to finish, and display the live URL:
548
+
549
+ ```
550
+ Building site... done.
551
+ Committing: "Publish: My New Article"
552
+ Pushing to origin/main...
553
+ Waiting for GitHub Pages deployment... ✓
554
+
555
+ ✅ Published! View your article at:
556
+ https://username.github.io/my-blog/My%20New%20Article/
557
+ ```
558
+
559
+ **Deploy options:**
560
+
561
+ ```bash
562
+ fossbook deploy --message "Update homepage" # Custom commit message
563
+ fossbook deploy --no-wait # Push without waiting for CI
564
+ ```
565
+
566
+ ## License
567
+
568
+ - Generator code: [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
569
+ - Archie theme: [MIT License](https://github.com/athul/archie?tab=MIT-1-ov-file#readme)
570
+
571
+ ## Credits
572
+
573
+ Adapted from [kartiknair's blog](https://github.com/kartiknair/blog) and styled using the [Archie theme](https://github.com/athul/archie).
package/lib/init.js CHANGED
@@ -128,7 +128,9 @@ function initProject(options = {}) {
128
128
  // },
129
129
  // Add localized blog information to fossbook.config.en.js,
130
130
  // fossbook.config.ko.js, and so on. Menu labels can also be localized with
131
- // homeLabel, allPostsLabel, aboutLabel, tagsLabel, allTagsLabel, and transcriptLabel.
131
+ // homeLabel, allPostsLabel, aboutLabel, tagsLabel, allTagsLabel,
132
+ // postedOnLabel, readMoreLabel, previousPageLabel, nextPageLabel,
133
+ // and transcriptLabel.
132
134
 
133
135
  // Comment system (optional)
134
136
  // comments: { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" },
@@ -16,7 +16,12 @@ module.exports = class PageBase {
16
16
  }
17
17
 
18
18
  formatDate(date) {
19
- const options = { year: "numeric", month: "short", day: "numeric" };
19
+ const options = {
20
+ year: "numeric",
21
+ month: "short",
22
+ day: "numeric",
23
+ timeZone: "UTC",
24
+ };
20
25
  return date.toLocaleDateString(this.config.locale || "en-US", options);
21
26
  }
22
27
 
package/package.json CHANGED
@@ -1,45 +1,48 @@
1
- {
2
- "name": "fossbook",
3
- "version": "0.2.10",
4
- "description": "A lightweight static blog site generator for GitHub Pages",
5
- "main": "lib/index.js",
6
- "scripts": {
7
- "build": "node bin/fossbook.js build",
8
- "start": "node bin/fossbook.js serve",
9
- "test": "mocha test",
10
- "prettier": "prettier --write ."
11
- },
12
- "keywords": [
13
- "static-site-generator",
14
- "blog",
15
- "github-pages",
16
- "markdown"
17
- ],
18
- "author": "Joone Hur",
19
- "license": "BSD-3-Clause",
20
- "dependencies": {
21
- "express": "^4.19.1",
22
- "front-matter": "^4.0.2",
23
- "highlight.js": "^11.9.0",
24
- "marked": "^12.0.1"
25
- },
26
- "devDependencies": {
27
- "mocha": "^10.2.0",
28
- "prettier": "^3.2.5"
29
- },
30
- "bin": {
31
- "fossbook": "bin/fossbook.js"
32
- },
33
- "engines": {
34
- "node": ">=18"
35
- },
36
- "files": [
37
- "bin/",
38
- "lib/",
39
- "themes/"
40
- ],
41
- "repository": {
42
- "type": "git",
43
- "url": "https://github.com/joone/fossbook"
44
- }
45
- }
1
+ {
2
+ "name": "fossbook",
3
+ "version": "0.2.12",
4
+ "description": "A Markdown-based authoring and web-publishing tool for comics, illustrated stories, and articles",
5
+ "main": "lib/index.js",
6
+ "scripts": {
7
+ "build": "node bin/fossbook.js build",
8
+ "start": "node bin/fossbook.js serve",
9
+ "test": "mocha test",
10
+ "prettier": "prettier --write ."
11
+ },
12
+ "keywords": [
13
+ "comic-authoring",
14
+ "webcomics",
15
+ "publishing",
16
+ "static-site-generator",
17
+ "github-pages",
18
+ "markdown"
19
+ ],
20
+ "author": "Joone Hur",
21
+ "license": "BSD-3-Clause",
22
+ "dependencies": {
23
+ "express": "^4.19.1",
24
+ "front-matter": "^4.0.2",
25
+ "highlight.js": "^11.9.0",
26
+ "marked": "^12.0.1"
27
+ },
28
+ "devDependencies": {
29
+ "mocha": "^10.2.0",
30
+ "prettier": "^3.2.5"
31
+ },
32
+ "bin": {
33
+ "fossbook": "bin/fossbook.js"
34
+ },
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "files": [
39
+ "bin/",
40
+ "docs/images/",
41
+ "lib/",
42
+ "themes/"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/joone/fossbook"
47
+ }
48
+ }
@@ -456,6 +456,18 @@
456
456
  font-size: 1rem;
457
457
  }
458
458
 
459
+ .readmore {
460
+ display: inline-block;
461
+ margin-top: 0.75rem;
462
+ }
463
+
464
+ ul.pagination {
465
+ display: flex;
466
+ justify-content: center;
467
+ align-items: center;
468
+ padding-left: 0;
469
+ }
470
+
459
471
  /* navigation */
460
472
  h1.assistive-text {
461
473
  display: none;
@@ -517,6 +529,11 @@
517
529
  .disable-link {
518
530
  color: grey;
519
531
  }
532
+
533
+ .pagination-separator {
534
+ color: grey;
535
+ margin: 0 0.5rem;
536
+ }
520
537
 
521
538
  /* Callout */
522
539
  .callout {
@@ -543,6 +560,9 @@
543
560
  display: flex;
544
561
  justify-content: space-between;
545
562
  }
563
+ .site-description + .list-item h1 {
564
+ margin-top: 1rem;
565
+ }
546
566
  .post-tags ul.tags {
547
567
  display: flex;
548
568
  flex-wrap: wrap;
@@ -47,16 +47,17 @@
47
47
  <img src="${page.config.basePath}${post.path}/images/${post.image}" alt="${post.title}">
48
48
  </div>` : ''}
49
49
  <div class="description">${post.description || ''}</div>
50
- <a class="readmore" href="${page.config.basePath}${post.path}">Read more ⟶</a>
50
+ <a class="readmore" href="${page.config.basePath}${post.path}">${page.config.readMoreLabel || "Read more"} ⟶</a>
51
51
  </section>`,
52
52
  )
53
53
  .join("")}
54
54
  <ul class="pagination" role="navigation">
55
55
  <span class="page-item page-prev">
56
- ${data.prev ? `<a href="${page.config.basePath}page/${data.prev}.html" class="page-link" aria-label="Previous">← Prev</a>` : `<span class="page-link disable-link"><span aria-hidden="true">← Prev</span></span>`}
56
+ ${data.prev ? `<a href="${page.config.basePath}page/${data.prev}.html" class="page-link" aria-label="${page.config.previousPageLabel || "Previous"}">← ${page.config.previousPageLabel || "Prev"}</a>` : `<span class="page-link disable-link"><span aria-hidden="true">← ${page.config.previousPageLabel || "Prev"}</span></span>`}
57
57
  </span>
58
+ <span class="pagination-separator" aria-hidden="true">|</span>
58
59
  <span class="page-item page-next">
59
- ${data.next ? `<a href="${page.config.basePath}page/${data.next}.html" class="page-link" aria-label="Next">Next →</a>` : `<span class="page-link disable-link"><span aria-hidden="true">Next →</span></span>`}
60
+ ${data.next ? `<a href="${page.config.basePath}page/${data.next}.html" class="page-link" aria-label="${page.config.nextPageLabel || "Next"}">${page.config.nextPageLabel || "Next"} →</a>` : `<span class="page-link disable-link"><span aria-hidden="true">${page.config.nextPageLabel || "Next"} →</span></span>`}
60
61
  </span>
61
62
  </ul>
62
63
  </main>
@@ -53,7 +53,7 @@
53
53
  <article class="content">
54
54
  <div class="title">
55
55
  <h1 class="title">${page.title}</h1>
56
- <div class="meta">Posted on ${page.formatDate(new Date(page.date))}</div>
56
+ <div class="meta">${page.config.postedOnLabel || "Posted on"} ${page.formatDate(new Date(page.date))}</div>
57
57
  ${page.languageSwitcher()}
58
58
  </div>
59
59
  ${page.body.includes('class="blockquote-container image-dialogue"') ? `<div class="transcript-control">