ligarb 0.9.0 → 0.10.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10e90698ccef79745b7709265f5657df0354c86000ef85be7401b56cb443da0b
4
- data.tar.gz: 5edf902c6f4d06b0fe923f5eb24372b33379bfb50296cadeec04ef308c0cc217
3
+ metadata.gz: 4ba0a513e610d71f30b92bf61b42e2acdab5516e6105b56a43603692c60a3a9a
4
+ data.tar.gz: 5824b715da62d8d568d597a2edc0dc9a85dc0b7b7ef028499d7ce9cbcb65d161
5
5
  SHA512:
6
- metadata.gz: c2d201101b94c3b63405599c21e91c043ac98967fbf6b25035a0e959782c70fbbfa9948981caac86cdb9f25405ebe0e8f2d80c5e5bab6453912a62f1c212cf24
7
- data.tar.gz: ed28454394317ade969c4fd8e5dd974b3f34e732208f7089a5f603160a9e9f5172c78d11ef04f872d3b455de18811a56501b9e69e6983ce4d38942bae93d77d4
6
+ metadata.gz: c58b836062f00ef965e59f44fef837fe0551d22a57f93f957b0a25d718b18ef79447b11b50f8c4a52540fec9d96656397266d4500ecacc60e42faa474099abd3
7
+ data.tar.gz: f74f174bc842665f9d68387381cd92cfc9ee48745f1bf339d3f6393e96b17a7ddec0adbd561f8e56cfc92fd6a6b9eac6d3b102e4abc1e9f071407baa7870bbd2
@@ -2,11 +2,19 @@
2
2
  //
3
3
  // Usage: node mermaid_check.mjs <path/to/mermaid.min.js>
4
4
  // stdin: JSON array of {"id": <any>, "text": <mermaid source>}
5
- // stdout: JSON array of {"id": <any>, "error": <message or null>}
5
+ // stdout: JSON array of {"id": <any>, "error": <message or null>,
6
+ // "kind": "syntax" | "environment"}
6
7
  //
7
8
  // Loads the browser UMD bundle of mermaid in Node by stubbing just enough
8
9
  // of the DOM (mermaid.parse() only parses; it never renders, but DOMPurify
9
10
  // refuses to initialize without something that looks like a document).
11
+ //
12
+ // The DOM stub is intentionally minimal, so it cannot satisfy DOMPurify when a
13
+ // node label contains HTML (e.g. "A[1<br>2]"): sanitizing real markup needs a
14
+ // real DOM tree to walk, which the stub does not provide. That surfaces as a
15
+ // generic JS error (e.g. TypeError "Right-hand side of 'instanceof'..."), NOT a
16
+ // diagram syntax error. classifyError() tells the two apart so callers only
17
+ // warn about genuine mermaid problems and not these harness limitations.
10
18
 
11
19
  const noop = () => {};
12
20
 
@@ -39,7 +47,11 @@ function makeStub(name, overrides = {}) {
39
47
  globalThis.window = globalThis;
40
48
  // nodeType 9 = DOCUMENT_NODE; DOMPurify checks it to decide it has a real DOM.
41
49
  globalThis.document = makeStub("document", { nodeType: 9 });
42
- globalThis.navigator = { userAgent: "node" };
50
+ // Node >= 21 ships a built-in read-only `navigator` global; assigning to it
51
+ // throws a TypeError. Only define our stub when the runtime lacks one.
52
+ if (!("navigator" in globalThis)) {
53
+ globalThis.navigator = { userAgent: "node" };
54
+ }
43
55
  globalThis.addEventListener = noop;
44
56
  globalThis.location = { href: "http://localhost/", protocol: "http:" };
45
57
  globalThis.Element = function Element() {};
@@ -73,14 +85,36 @@ if (!mermaid || typeof mermaid.parse !== "function") {
73
85
  process.exit(2);
74
86
  }
75
87
 
88
+ // Decide whether a thrown error is a genuine mermaid diagram problem
89
+ // ("syntax") or an artifact of our minimal DOM stub ("environment").
90
+ //
91
+ // - jison grammar errors carry a structured `.hash` -> syntax
92
+ // - mermaid's typed errors (e.g. UnknownDiagramError) -> syntax
93
+ // - generic JS runtime errors (TypeError/ReferenceError/RangeError/EvalError)
94
+ // with no hash come from the DOM stub -> environment
95
+ // - anything else is reported as syntax, erring toward visibility
96
+ function classifyError(e) {
97
+ if (e && e.hash !== undefined) return "syntax";
98
+ const name = e && e.name;
99
+ if (name && name.endsWith("DiagramError")) return "syntax";
100
+ if (["TypeError", "ReferenceError", "RangeError", "EvalError"].includes(name)) {
101
+ return "environment";
102
+ }
103
+ return "syntax";
104
+ }
105
+
76
106
  const blocks = JSON.parse(readFileSync(0, "utf8"));
77
107
  const results = [];
78
108
  for (const block of blocks) {
79
109
  try {
80
110
  await mermaid.parse(block.text);
81
- results.push({ id: block.id, error: null });
111
+ results.push({ id: block.id, error: null, kind: "syntax" });
82
112
  } catch (e) {
83
- results.push({ id: block.id, error: String(e && e.message ? e.message : e) });
113
+ results.push({
114
+ id: block.id,
115
+ error: String(e && e.message ? e.message : e),
116
+ kind: classifyError(e),
117
+ });
84
118
  }
85
119
  }
86
120
  console.log(JSON.stringify(results));
data/assets/style.css CHANGED
@@ -77,6 +77,22 @@ body {
77
77
  opacity: 1;
78
78
  }
79
79
 
80
+ .ai-md-hint {
81
+ margin: 0 0 1.5rem;
82
+ font-size: 0.8rem;
83
+ color: var(--color-text-muted);
84
+ opacity: 0.8;
85
+ }
86
+
87
+ .ai-md-hint a {
88
+ color: inherit;
89
+ text-decoration: underline;
90
+ }
91
+
92
+ @media print {
93
+ .ai-md-hint { display: none; }
94
+ }
95
+
80
96
  .book-title {
81
97
  font-size: 1.1rem;
82
98
  font-weight: 700;
data/docs/help.md ADDED
@@ -0,0 +1,868 @@
1
+ # ligarb - Generate a single-page HTML book from Markdown files
2
+
3
+ ## Overview
4
+
5
+ ligarb converts multiple Markdown files into a self-contained index.html.
6
+ The generated HTML includes:
7
+ - A left sidebar with a searchable table of contents (h1-h3)
8
+ - Chapter-based content switching in the main area
9
+ - Permalink support via URL hash (#chapter-slug)
10
+ - Responsive design with print-friendly styles
11
+ - Syntax-highlighted code blocks
12
+ - Search with content highlighting
13
+ - Chapter and section numbering (configurable)
14
+ - Previous/Next chapter navigation
15
+ - Dark mode toggle (saved to localStorage)
16
+ - Custom CSS support
17
+ - "Edit on GitHub" links (optional)
18
+ - Footnotes (kramdown syntax)
19
+
20
+ **Intended use:** ligarb is a local CLI tool for authors who build books from
21
+ their own Markdown sources. The generated HTML is a static file meant to be
22
+ opened in a browser or deployed to a static hosting service. `ligarb serve`
23
+ is for local preview only and must not be exposed to the internet.
24
+
25
+ **Non-goals:** ligarb does not sanitize Markdown-derived HTML, CSS, or
26
+ bibliography URLs. It is not designed for untrusted third-party content,
27
+ public upload services, or automated pipelines that ingest unreviewed input.
28
+ If such use cases are needed, add HTML sanitization, URL validation, and CSP
29
+ headers externally.
30
+
31
+ ## Commands
32
+
33
+ ### `ligarb init [DIRECTORY]`
34
+
35
+ Create a new book project with scaffolding.
36
+ If DIRECTORY is given, creates and populates that directory.
37
+ If omitted, populates the current directory.
38
+ Generates book.yml, 01-introduction.md, and images/.
39
+ If .md files already exist, registers them as chapters.
40
+ Aborts if book.yml already exists.
41
+
42
+ ### `ligarb setup-github-review [DIRECTORY] [--owner NAME]`
43
+
44
+ Set up a GitHub-based review workflow in an existing project (book.yml must
45
+ exist). DIRECTORY defaults to the current directory. Composable: run it after
46
+ `ligarb init` or `ligarb write`, or on any hand-made project.
47
+
48
+ It generates `.github/`, `SETUP.md`, and `SETUP.sh` — GitHub Actions workflows
49
+ for publishing the book to GitHub Pages, checking that PRs still build, and
50
+ (opt-in) letting Claude triage reader-filed issues into pull requests — plus
51
+ issue forms for structured feedback and a gh CLI one-shot script (`SETUP.sh`)
52
+ that does the repo creation / secret / Pages / labels setup. It also:
53
+ - adds `github_review.enabled: true` to book.yml (the reader feedback UI) if
54
+ absent;
55
+ - seeds a default `repository:` (`https://github.com/<owner>/<dir>`) if absent,
56
+ so the templates and SETUP.sh get concrete values — edit it if the guess is
57
+ wrong and re-run. `<owner>` is the `--owner` (alias `--user`) flag when given
58
+ — useful for an organization — otherwise `$USER`. If `repository:` is already
59
+ set, passing `--owner` is an error (edit `repository:` in book.yml directly);
60
+ - seeds a default `site_url:` (the GitHub Pages URL derived from `repository`)
61
+ if absent, so the build emits `og:url`/canonical — edit it for a custom domain;
62
+ - creates a project `README.md` linking to the GitHub Pages site, unless one
63
+ already exists (your README is never overwritten).
64
+
65
+ Re-run it any time to update the scaffolding to the latest templates (e.g. after
66
+ upgrading ligarb): existing generated files are **overwritten**, so review the
67
+ changes with `git diff` and revert any local edits you want to keep. Your
68
+ book.yml is never overwritten. `__OWNER__` / `__REPO__` placeholders in the
69
+ templates are filled from book.yml's `repository:`; if it is unset the
70
+ placeholders are left as-is (and the Discussions link in
71
+ `.github/ISSUE_TEMPLATE/config.yml` is commented out). The generated files are
72
+ the only thing that depends on Claude or GitHub Actions — ligarb itself never
73
+ calls either at runtime. Read the generated `SETUP.md` for the manual steps
74
+ (token registration, repository settings) that cannot be generated; it includes
75
+ a gh CLI quickstart.
76
+
77
+ ### `ligarb build [CONFIG]`
78
+
79
+ Build the HTML book.
80
+ CONFIG defaults to 'book.yml' in the current directory.
81
+
82
+ The output directory contains index.html plus js/ and css/ subdirectories
83
+ for auto-downloaded libraries (highlight.js, mermaid, KaTeX, etc.).
84
+ Open index.html directly in a browser — no web server needed.
85
+
86
+ The build also emits a Markdown full-text version of the book next to
87
+ index.html, intended for AI/LLM readers (HTML is noisy and costly to read;
88
+ clean Markdown is cheaper and more faithful). For a single-language build it
89
+ is `index.md`; for a multi-language (hub) build it is `index.<lang>.md` per
90
+ language. Each is the concatenated chapter sources in reading order. The HTML
91
+ points to it two ways: a small visible note at the top of the content ("For
92
+ AI/LLM readers: a clean Markdown full-text version of this book is at …") and
93
+ a `<link rel="alternate" type="text/markdown">` in the head. Both use a
94
+ relative href (the bare filename), so they resolve against whatever URL the
95
+ page is actually served from — local preview, staging, or production — rather
96
+ than hard-coding `site_url` (the absolute canonical URL still lives in
97
+ `og:url` / `<link rel="canonical">`). A fetcher resolves it against the page's
98
+ own URL, so a model that fetched the page finds the bundle next to it. No tool
99
+ is guaranteed to honor these automatically —
100
+ the visible note is what actually reaches a model that fetched the page,
101
+ since the in-page text is included in what the fetcher hands to the model.
102
+
103
+ When `github_review.enabled` is true and `repository` is set (see
104
+ Configuration below), the build also injects a static "Report as issue"
105
+ feedback UI: readers select text, pick a type and add a comment, and a
106
+ prefilled GitHub Issue form opens in a new tab (chapter/section, quoted text,
107
+ source path, and current page URL are filled in). It is pure client-side JS
108
+ with no backend or tokens — it only builds a URL and opens it. If
109
+ `github_review.enabled` is true but `repository` is unset, build prints a
110
+ warning and skips the UI. Without `github_review`, nothing is injected.
111
+
112
+ ### `ligarb serve [CONFIG...]`
113
+
114
+ Start a local web server with live reload and review UI.
115
+ CONFIG defaults to 'book.yml' in the current directory.
116
+ Multiple CONFIG paths can be given to serve multiple books.
117
+
118
+ Options:
119
+ - `--port PORT` — Server port (default: 3000)
120
+ - `--host ADDR` — Bind address (default: 127.0.0.1, loopback only).
121
+ Use `0.0.0.0` (or a specific LAN IP) to reach the server from other
122
+ devices — e.g. from the Windows host when running under WSL, or a
123
+ phone on the same network. Binding beyond loopback prints a warning:
124
+ the review/feedback APIs (file writes, Claude runs) become reachable
125
+ by other hosts, so only do this on a trusted network. Same-origin
126
+ protection still applies (POSTs must come from the page's own origin).
127
+ - `--multi` — Force multi-book mode (even with 1 CONFIG)
128
+
129
+ **Single book mode** (1 CONFIG, without --multi):
130
+ - Serves the built HTML book at http://localhost:PORT
131
+
132
+ **Multi-book mode** (2+ CONFIGs, or --multi):
133
+ - Top page (/) shows a book index with links
134
+ - Each book is served at /\<directory-name\>/
135
+ - "Write a new book" button on the index page to generate
136
+ a new book via AI (posts a brief, runs Writer in background)
137
+ - Example: `ligarb serve */book.yml`
138
+
139
+ Features:
140
+ - Injects a reload button that pulses when build output changes
141
+ - Injects a review UI for commenting on book text
142
+ - Review comments are saved to .ligarb/reviews/\*.json
143
+ (in each book's directory)
144
+ - If 'claude' CLI is installed, comments are sent to Claude
145
+ for review suggestions, and approved changes are applied
146
+ to the source Markdown files and the book is rebuilt
147
+ - Review patches can span multiple chapters and the
148
+ bibliography file (Claude reads book.yml to find all files)
149
+
150
+ ### `ligarb librarium`
151
+
152
+ Start a multi-book library server.
153
+ Automatically discovers \*/book.yml in the current directory.
154
+ Equivalent to: `ligarb serve --multi */book.yml`
155
+
156
+ Options:
157
+ - `--port PORT` — Server port (default: 3000)
158
+ - `--host ADDR` — Bind address (default: 127.0.0.1; see `serve` above)
159
+
160
+ ### `ligarb write [BRIEF]`
161
+
162
+ Generate a complete book using AI (Claude).
163
+ BRIEF defaults to 'brief.yml' in the current directory.
164
+ Reads the brief, sends a prompt to Claude, and builds
165
+ the generated book. Files are created in the same
166
+ directory as brief.yml.
167
+
168
+ - `ligarb write --init [DIR]` — Create a brief.yml template.
169
+ If DIR is given, creates DIR/brief.yml (mkdir as needed).
170
+ If omitted, creates brief.yml in the current directory.
171
+ - `ligarb write --no-build` — Generate files only, skip the build step.
172
+
173
+ Requires the 'claude' CLI to be installed.
174
+ See the "Write Command" section below for brief.yml details.
175
+
176
+ ### `ligarb help`
177
+
178
+ Show this detailed specification.
179
+
180
+ ### `ligarb --help`
181
+
182
+ Show short usage summary.
183
+
184
+ ### `ligarb version`
185
+
186
+ Show the version number.
187
+
188
+ ## Configuration: book.yml
189
+
190
+ The configuration file is a YAML file with the following fields:
191
+
192
+ | Field | Required | Default | Description |
193
+ |-------|----------|---------|-------------|
194
+ | `title` | required | — | The book title displayed in the header and \<title\> tag. |
195
+ | `description` | optional | — | Short description for the `<meta name="description">` and Open Graph (`og:description`) tags. When omitted, it is auto-extracted from the first prose paragraph of the cover (or first) chapter. Not inherited by translations — set it per language. |
196
+ | `site_url` | optional | — | Canonical published URL of the book. When set, emits `og:url` and `<link rel="canonical">`. Used verbatim, so set it to where this build is actually served. `setup-github-review` seeds it from `repository` (the GitHub Pages URL) if absent; edit it for custom domains or other hosting. |
197
+ | `author` | optional | empty | Author name displayed in the header. |
198
+ | `language` | optional | `"en"` | HTML lang attribute value. |
199
+ | `output_dir` | optional | `"build"` | Output directory relative to book.yml. |
200
+ | `chapter_numbers` | optional | `true` | Show chapter/section numbers (e.g. "1.", "1.1", "1.1.1"). |
201
+ | `style` | optional | — | Path to a custom CSS file relative to book.yml. Loaded after the default styles, so it can override any rule. |
202
+ | `repository` | optional | — | GitHub repository URL (e.g. "https://github.com/user/repo"). When set, each chapter shows a "View on GitHub" link pointing to `{repository}/blob/HEAD/{path-from-git-root}`. |
203
+ | `ai_generated` | optional | `false` | Mark the book as AI-generated content. When true: adds an "AI Generated" badge in the sidebar header, adds a default disclaimer footer to each chapter, and adds noindex/noai meta tags. The footer text can be overridden with the `footer` field. |
204
+ | `footer` | optional | — | Custom text displayed at the bottom of each chapter. Overrides the default ai_generated disclaimer if both are set. |
205
+ | `bibliography` | optional | — | Path to a bibliography file (.yml or .bib) relative to book.yml. See "Bibliography" section. |
206
+ | `sources` | optional | — | Reference files for AI context. Array of strings or `{path:, label:}` objects. Used by the `write` command and review AI. |
207
+ | `github_review` | optional | — | A mapping enabling the static reader feedback UI. `enabled: true` injects a "Report as issue" UI into `build` output (requires `repository`). Optional sub-keys: `issue_template` (default `book-feedback.yml`) and `labels` (default `[feedback]`) prefilled into the GitHub Issue URL. Usually set by `ligarb setup-github-review`. |
208
+ | `translations` | optional | — | A mapping of language codes to config file paths. Enables multi-language support. See "Translations" section. |
209
+ | `inherit` | optional | — | Path to a parent config file. Inheritable settings (author, language, style, etc.) are loaded as defaults. Used by per-language configs for standalone builds. |
210
+ | `chapters` | required | — | Book structure. An array that can contain: a cover, chapter strings, parts, and appendix. |
211
+
212
+ ### chapters array
213
+
214
+ All paths in the chapters array are relative to book.yml.
215
+
216
+ The chapters array supports four element types:
217
+
218
+ **1. Cover** (object with 'cover' key):
219
+
220
+ ```yaml
221
+ chapters:
222
+ - cover: cover.md # Markdown file: landing page shown when the book is opened
223
+ # Not shown in the TOC sidebar.
224
+ ```
225
+
226
+ **2. Plain chapter** (string):
227
+
228
+ ```yaml
229
+ chapters:
230
+ - 01-introduction.md
231
+ ```
232
+
233
+ **3. Part** (object with 'part' and 'chapters' keys):
234
+
235
+ ```yaml
236
+ chapters:
237
+ - part: part1.md
238
+ chapters:
239
+ - 01-introduction.md
240
+ - 02-getting-started.md
241
+ ```
242
+
243
+ The part file's h1 becomes the part title, and the body text (if any)
244
+ is shown as the part's opening page.
245
+
246
+ **4. Appendix** (object with 'appendix' key, value is array of chapter files):
247
+
248
+ ```yaml
249
+ chapters:
250
+ - appendix:
251
+ - a1-references.md
252
+ - a2-glossary.md
253
+ ```
254
+
255
+ These can be combined freely:
256
+
257
+ ```yaml
258
+ chapters:
259
+ - cover: cover.md
260
+ - part: part1.md
261
+ chapters:
262
+ - 01-introduction.md
263
+ - 02-getting-started.md
264
+ - part: part2.md
265
+ chapters:
266
+ - 03-advanced.md
267
+ - appendix:
268
+ - a1-references.md
269
+ ```
270
+
271
+ Part numbering is sequential across parts (1, 2, 3, ...).
272
+ Appendix numbering uses letters (A, B, C, ...).
273
+
274
+ ### Social metadata (Open Graph)
275
+
276
+ Every build emits Open Graph tags in `<head>` so the generated `index.html`
277
+ previews nicely when shared: `og:type` (`book`), `og:title` and `og:site_name`
278
+ (the book title), `og:locale` (derived from `language`, e.g. `ja` → `ja_JP`),
279
+ and `book:author` when `author` is set. If a description is available (the
280
+ `description` field, or auto-extracted from the cover/first chapter) it is also
281
+ emitted as `<meta name="description">` and `og:description`. A
282
+ `twitter:card` (`summary`) tag is always included. No image tag is produced.
283
+
284
+ When `site_url` is set, `og:url` and `<link rel="canonical">` are emitted with
285
+ that URL verbatim (the book is a single page, so it has one canonical URL).
286
+ Running `setup-github-review` seeds `site_url` in book.yml from `repository`
287
+ (the GitHub Pages URL — `https://<owner>.github.io/<repo>/`, or
288
+ `https://<owner>.github.io/` for an `<owner>.github.io` repo), so books deployed
289
+ that way get `og:url` automatically; edit it for a custom domain.
290
+
291
+ ### Example book.yml (simple)
292
+
293
+ ```yaml
294
+ title: "My Software Guide"
295
+ author: "Author Name"
296
+ language: "ja"
297
+ chapters:
298
+ - 01-introduction.md
299
+ - 02-getting-started.md
300
+ - 03-advanced.md
301
+ ```
302
+
303
+ ### Example book.yml (with parts and appendix)
304
+
305
+ ```yaml
306
+ title: "My Software Guide"
307
+ author: "Author Name"
308
+ language: "ja"
309
+ chapters:
310
+ - part: part1.md
311
+ chapters:
312
+ - 01-introduction.md
313
+ - 02-getting-started.md
314
+ - part: part2.md
315
+ chapters:
316
+ - 03-advanced.md
317
+ - 04-deployment.md
318
+ - appendix:
319
+ - a1-config-reference.md
320
+ ```
321
+
322
+ ## Translations (Multi-Language)
323
+
324
+ ligarb supports building the same book in multiple languages. A parent
325
+ config file (hub) defines shared settings and points to per-language
326
+ config files.
327
+
328
+ ### Hub config (book.yml)
329
+
330
+ ```yaml
331
+ repository: "https://github.com/user/repo"
332
+ ai_generated: true
333
+ translations:
334
+ ja: book.ja.yml
335
+ en: book.en.yml
336
+ ```
337
+
338
+ ### Per-language config (book.ja.yml)
339
+
340
+ ```yaml
341
+ title: "マニュアル"
342
+ language: "ja"
343
+ inherit: book.yml
344
+ chapters:
345
+ - chapters/ja/01-intro.md
346
+ ```
347
+
348
+ ### Per-language config (book.en.yml)
349
+
350
+ ```yaml
351
+ title: "Manual"
352
+ language: "en"
353
+ inherit: book.yml
354
+ chapters:
355
+ - chapters/en/01-intro.md
356
+ ```
357
+
358
+ ### Building
359
+
360
+ There are two ways to build a multi-language book:
361
+
362
+ **Hub build** — pass the hub config to build all languages at once.
363
+ Produces a single output with a language switcher linking all translations:
364
+
365
+ ```
366
+ ligarb build book.yml # Builds all languages, adds language switcher
367
+ ```
368
+
369
+ **Standalone build** — pass a per-language config to build one language.
370
+ No language switcher is generated. The `inherit` field loads the hub's
371
+ inheritable settings (repository, style, ai_generated, etc.) as defaults:
372
+
373
+ ```
374
+ ligarb build book.ja.yml # Builds only Japanese, no language switcher
375
+ ```
376
+
377
+ ### Inheritance rules
378
+
379
+ - The hub's settings (repository, style, ai_generated, etc.) are
380
+ inherited by each per-language config as defaults.
381
+ - Per-language configs can override any inherited setting.
382
+ - title, language, and chapters are always per-language (required in
383
+ each per-language config).
384
+ - output_dir is NOT inherited — it always comes from the config file
385
+ passed directly to `ligarb build`.
386
+
387
+ The hub config does not need 'title' or 'chapters' fields — it only
388
+ needs 'translations'. If the hub has no 'chapters', it is treated
389
+ purely as a translations hub.
390
+
391
+ ### Language switcher
392
+
393
+ - Hub build produces a single index.html containing all languages.
394
+ A language switcher in the sidebar header (e.g. `[JA | EN]`) toggles
395
+ visibility via JavaScript.
396
+ - The current language button is highlighted; others switch the display.
397
+
398
+ ## Directory Structure
399
+
400
+ A typical book project has this structure:
401
+
402
+ ```
403
+ my-book/
404
+ ├── book.yml # Configuration file
405
+ ├── part1.md # Part opening page (optional)
406
+ ├── 01-introduction.md # Markdown source files
407
+ ├── 02-getting-started.md
408
+ ├── 03-advanced.md
409
+ └── images/ # Image files (optional)
410
+ ├── screenshot.png
411
+ └── diagram.svg
412
+ ```
413
+
414
+ After running `ligarb build`, the output is:
415
+
416
+ ```
417
+ my-book/
418
+ └── build/
419
+ ├── index.html # Single-page HTML book
420
+ ├── js/ # Auto-downloaded (only if needed)
421
+ ├── css/ # Auto-downloaded (only if needed)
422
+ └── images/ # Copied image files
423
+ ```
424
+
425
+ ## Markdown Authoring
426
+
427
+ ### Basics
428
+
429
+ Each Markdown file represents one chapter. ligarb uses GitHub Flavored
430
+ Markdown (GFM) via kramdown. Supported syntax includes:
431
+
432
+ - Headings (# h1, ## h2, ### h3) — used for TOC generation
433
+ - Code blocks with language-specific syntax highlighting (\`\`\` fenced blocks)
434
+ - Tables, task lists, strikethrough, and other GFM extensions
435
+ - Inline HTML
436
+
437
+ Each file must start with an h1 heading (`#`). This becomes the chapter
438
+ title in the TOC. Use h2 (`##`) and h3 (`###`) for sections within the chapter.
439
+
440
+ ### Images
441
+
442
+ Place image files in the `images/` directory next to book.yml.
443
+ Reference them from Markdown with relative paths:
444
+
445
+ ```markdown
446
+ ![Screenshot](images/screenshot.png)
447
+ ```
448
+
449
+ ligarb rewrites all local image paths to `images/{basename}` in the output
450
+ (directory components are stripped). Images are copied from the `images/`
451
+ directory next to book.yml — only top-level files, not subdirectories.
452
+
453
+ This means:
454
+ - `![img](images/foo.png)` and `![img](sub/dir/foo.png)` both become
455
+ `images/foo.png` in the output.
456
+ - All image filenames must be unique regardless of source directory.
457
+ - Remote URLs (http://, https://, data:) are left unchanged.
458
+ - In multi-language builds, all languages share the same `images/` pool.
459
+
460
+ ### Footnotes
461
+
462
+ Footnotes use kramdown syntax:
463
+
464
+ ```markdown
465
+ This is a sentence with a footnote[^1].
466
+
467
+ [^1]: This is the footnote content.
468
+ ```
469
+
470
+ Footnote IDs are scoped per chapter to avoid collisions in the single-page
471
+ output.
472
+
473
+ ### Admonitions
474
+
475
+ GFM-style blockquote alerts are converted to styled admonition boxes.
476
+ Five types are supported: NOTE, TIP, WARNING, CAUTION, IMPORTANT.
477
+
478
+ Syntax:
479
+
480
+ ```markdown
481
+ > [!NOTE]
482
+ > This is a note.
483
+
484
+ > [!TIP]
485
+ > Helpful advice here.
486
+
487
+ > [!WARNING]
488
+ > Be careful about this.
489
+
490
+ > [!CAUTION]
491
+ > Dangerous operation.
492
+
493
+ > [!IMPORTANT]
494
+ > Critical information.
495
+ ```
496
+
497
+ Each type renders with a distinct color and icon:
498
+
499
+ | Type | Color | Icon |
500
+ |------|-------|------|
501
+ | NOTE | blue | info |
502
+ | TIP | green | lightbulb |
503
+ | WARNING | yellow | warning |
504
+ | CAUTION | red | stop |
505
+ | IMPORTANT | purple | exclamation |
506
+
507
+ ### Cross-References
508
+
509
+ Link to other chapters or headings using standard Markdown relative links.
510
+ ligarb resolves .md file references to internal anchors in the single-page
511
+ output.
512
+
513
+ Syntax:
514
+
515
+ ```markdown
516
+ [link text](other-chapter.md) Link to a chapter
517
+ [link text](other-chapter.md#Heading) Link to a specific heading
518
+ [](other-chapter.md) Auto-fill with chapter title
519
+ [](other-chapter.md#Heading) Auto-fill with heading text
520
+ ```
521
+
522
+ The .md path is resolved relative to the current Markdown file's directory.
523
+ The heading fragment is matched against heading IDs (case-insensitive,
524
+ normalized the same way heading slugs are generated).
525
+
526
+ When the link text is empty, ligarb fills it with the target's display text:
527
+ - Chapter link: the chapter's display title (e.g. "3. Config Guide")
528
+ - Heading link: the heading's display text (e.g. "3.2 Setup")
529
+
530
+ If a referenced chapter or heading does not exist, the build fails with an
531
+ error message indicating the broken reference and its source file.
532
+
533
+ External URLs ending in .md (e.g. https://example.com/README.md) are not
534
+ affected — only relative paths are resolved.
535
+
536
+ ### Index
537
+
538
+ Mark terms for the book index using Markdown link syntax with `#index`:
539
+
540
+ ```markdown
541
+ [Ruby](#index) Index the link text as-is
542
+ [dynamic typing](#index:動的型付け) Index under a specific term
543
+ [Ruby](#index:Ruby,Languages/Ruby) Multiple index entries (comma-separated)
544
+ [Ruby](#index:Languages/Ruby) Hierarchical: Languages > Ruby
545
+ ```
546
+
547
+ The link is rendered as plain text in the output (no link styling).
548
+ An "Index" section is automatically appended at the end of the book,
549
+ with terms sorted alphabetically and grouped by first character.
550
+
551
+ Clicking an index entry navigates to the exact location in the chapter.
552
+
553
+ ### Bibliography
554
+
555
+ Cite references in the text using Markdown link syntax with `#cite`:
556
+
557
+ ```markdown
558
+ [Ruby](#cite:matz1995) Cite by key; rendered as Ruby[Matsumoto, 1995]
559
+ ```
560
+
561
+ Define a bibliography data file in book.yml:
562
+
563
+ ```yaml
564
+ bibliography: references.yml # YAML format
565
+ bibliography: references.bib # BibTeX format
566
+ ```
567
+
568
+ The format is auto-detected by file extension (.bib = BibTeX, otherwise YAML).
569
+
570
+ **YAML format** — maps keys to reference data:
571
+
572
+ ```yaml
573
+ matz1995:
574
+ author: "Yukihiro Matsumoto"
575
+ title: "The Ruby Programming Language"
576
+ year: 1995
577
+ url: "https://www.ruby-lang.org"
578
+ publisher: "O'Reilly"
579
+ doi: "10.1234/example"
580
+ ```
581
+
582
+ **BibTeX format:**
583
+
584
+ ```bibtex
585
+ @book{matz1995,
586
+ author = {Yukihiro Matsumoto},
587
+ title = {The Ruby Programming Language},
588
+ year = {1995},
589
+ publisher = {O'Reilly},
590
+ url = {https://www.ruby-lang.org}
591
+ }
592
+ ```
593
+
594
+ BibTeX notes:
595
+ - Entry types (@book, @article, etc.) are preserved for formatting
596
+ - Field values can use {braces} or "quotes"
597
+ - Nested braces are supported one level deep (`{The {Ruby} Language}`)
598
+ - Lines starting with `%` are comments
599
+
600
+ Supported fields (YAML and BibTeX):
601
+ author, title, year, url, publisher, journal, booktitle, volume,
602
+ number, pages, edition, doi, editor, note.
603
+
604
+ Formatting by type:
605
+
606
+ | Type | Format |
607
+ |------|--------|
608
+ | book | Author. *Title*. Edition. Publisher, Year. |
609
+ | article | Author. "Title". Journal, Volume(Number), Pages, Year. |
610
+ | inproceedings | Author. "Title". In Booktitle, Pages, Year. |
611
+ | other/YAML | Author. Title. Publisher/Journal, Volume, Pages, Year. |
612
+
613
+ If url is present, the title becomes a link. If doi is present, a DOI link
614
+ is appended.
615
+
616
+ The citation is rendered as a superscript [author, year] link that navigates
617
+ to the "Bibliography" section at the end of the book. Hovering the link shows
618
+ the full reference. The bibliography section lists all cited entries sorted by
619
+ author and year.
620
+
621
+ A warning is printed and the citation is rendered as [key?] (highlighted in
622
+ red) if a cite key is not found in the bibliography file.
623
+ If no bibliography file is configured, `#cite:` links are converted to
624
+ plain text spans (the link text is preserved but no citation label is added).
625
+
626
+ ## Fenced Code Blocks
627
+
628
+ The following fenced code block types are automatically detected and
629
+ rendered. Required JS/CSS is auto-downloaded on first build to build/js/
630
+ and build/css/.
631
+
632
+ | Block type | Description |
633
+ |-----------|-------------|
634
+ | `` ```ruby ``, `` ```python ``, etc. | Syntax highlighting (highlight.js, BSD-3-Clause) |
635
+ | `` ```mermaid `` | Diagrams: flowcharts, sequence, bar/line/pie charts, gantt, mindmap, etc. (mermaid, MIT) |
636
+ | `` ```math `` | LaTeX math equations (KaTeX, MIT) |
637
+ | `` ```functionplot `` | Function graphs (function-plot + d3, MIT) |
638
+
639
+ These are rendered visually in the output HTML — use them freely.
640
+
641
+ ### Mermaid syntax checking
642
+
643
+ When a build contains `` ```mermaid `` blocks and Node.js is available, ligarb
644
+ validates each diagram with the bundled mermaid parser at build time. Diagrams
645
+ that fail to parse are reported as warnings with their `file:line` location, for
646
+ example:
647
+
648
+ ```
649
+ Warning: mermaid syntax error in chapters/05-diagrams.md:42
650
+ Parse error on line 2:
651
+ ...
652
+ ```
653
+
654
+ The build still succeeds (the broken diagram renders an error box in the output
655
+ instead of an SVG). The check is skipped silently when Node.js is not installed.
656
+ HTML inside node labels (such as `<br>`) is exempt from the check and never warns.
657
+
658
+ ### Mermaid examples
659
+
660
+ Flowchart:
661
+
662
+ ````markdown
663
+ ```mermaid
664
+ graph TD
665
+ A[Start] --> B{Check}
666
+ B -->|Yes| C[OK]
667
+ B -->|No| D[Retry]
668
+ ```
669
+ ````
670
+
671
+ Sequence diagram:
672
+
673
+ ````markdown
674
+ ```mermaid
675
+ sequenceDiagram
676
+ Client->>Server: Request
677
+ Server-->>Client: Response
678
+ ```
679
+ ````
680
+
681
+ Bar chart:
682
+
683
+ ````markdown
684
+ ```mermaid
685
+ xychart
686
+ title "Monthly Sales"
687
+ x-axis ["Jan", "Feb", "Mar", "Apr", "May"]
688
+ y-axis "Revenue" 0 --> 500
689
+ bar [120, 230, 180, 350, 410]
690
+ line [120, 230, 180, 350, 410]
691
+ ```
692
+ ````
693
+
694
+ Line chart (line only):
695
+
696
+ ````markdown
697
+ ```mermaid
698
+ xychart
699
+ title "Temperature"
700
+ x-axis ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
701
+ y-axis "°C" -5 --> 30
702
+ line [2, 4, 10, 16, 22, 26]
703
+ ```
704
+ ````
705
+
706
+ Pie chart:
707
+
708
+ ````markdown
709
+ ```mermaid
710
+ pie title Browser Share
711
+ "Chrome" : 65
712
+ "Safari" : 19
713
+ "Firefox" : 4
714
+ "Other" : 12
715
+ ```
716
+ ````
717
+
718
+ Gantt chart:
719
+
720
+ ````markdown
721
+ ```mermaid
722
+ gantt
723
+ title Project Plan
724
+ dateFormat YYYY-MM-DD
725
+ section Design
726
+ Requirements :a1, 2025-01-01, 14d
727
+ Architecture :a2, after a1, 10d
728
+ section Dev
729
+ Implementation :b1, after a2, 21d
730
+ Testing :b2, after b1, 14d
731
+ ```
732
+ ````
733
+
734
+ Mindmap:
735
+
736
+ ````markdown
737
+ ```mermaid
738
+ mindmap
739
+ root((Project))
740
+ Frontend
741
+ React
742
+ CSS
743
+ Backend
744
+ API
745
+ Database
746
+ ```
747
+ ````
748
+
749
+ ### Math (KaTeX, LaTeX syntax)
750
+
751
+ ````markdown
752
+ ```math
753
+ E = mc^2
754
+ ```
755
+ ````
756
+
757
+ Inline math uses `$...$` syntax within text:
758
+
759
+ ```
760
+ The equation $E = mc^2$ is well-known.
761
+ ```
762
+
763
+ Rules for inline math:
764
+ - `$$` is not matched (use `` ```math `` for display math)
765
+ - `$` followed by a space is not matched (e.g. $10)
766
+ - `$` preceded by a space is not matched
767
+ - Content inside `<code>` and `<pre>` is not affected
768
+ - The content is rendered with KaTeX (displayMode: false)
769
+
770
+ ### Function plot
771
+
772
+ ````markdown
773
+ ```functionplot
774
+ y = sin(x)
775
+ y = x^2 - 1
776
+ range: [-2pi, 2pi]
777
+ yrange: [-3, 3]
778
+ ```
779
+ ````
780
+
781
+ Function plot syntax:
782
+ - `y = <expr>` — Standard function (e.g. `y = sin(x)`)
783
+ - `r = <expr>` — Polar function (e.g. `r = cos(2*theta)`)
784
+ - `parametric: <x>, <y>` — Parametric curve (e.g. `parametric: cos(t), sin(t)`)
785
+ - Bare expression — Treated as `y = <expr>`
786
+
787
+ Options (one per line):
788
+ - `range` / `xrange: [min, max]` — X-axis range (supports pi, e.g. `[-2pi, 2pi]`)
789
+ - `yrange: [min, max]` — Y-axis range
790
+ - `width: <pixels>` — Plot width (default: 600)
791
+ - `height: <pixels>` — Plot height (default: 400)
792
+ - `title: <text>` — Plot title
793
+ - `grid: true` — Show grid lines
794
+
795
+ ## Output Customization
796
+
797
+ ### Custom CSS
798
+
799
+ Add a `style` field to book.yml to inject custom CSS:
800
+
801
+ ```yaml
802
+ style: "custom.css"
803
+ ```
804
+
805
+ The custom CSS file is read and embedded as-is into a `<style>` tag in the
806
+ generated HTML, after the default styles. No sanitization or escaping is
807
+ applied — this is safe under the trusted authoring model, but means the CSS
808
+ file should be treated as part of the trusted source. You can override any
809
+ CSS custom property (e.g. colors, fonts, sidebar width) or add new rules.
810
+
811
+ Example custom.css:
812
+
813
+ ```css
814
+ :root {
815
+ --color-accent: #e63946;
816
+ --sidebar-width: 320px;
817
+ }
818
+ ```
819
+
820
+ ### Dark Mode
821
+
822
+ The generated HTML includes a dark mode toggle button (moon icon) in the
823
+ sidebar header. The user's preference is saved to localStorage and persists
824
+ across page reloads.
825
+
826
+ Custom CSS can override dark mode colors using the `[data-theme="dark"]`
827
+ selector.
828
+
829
+ ### Edit on GitHub
830
+
831
+ Add a `repository` field to book.yml:
832
+
833
+ ```yaml
834
+ repository: "https://github.com/user/repo"
835
+ ```
836
+
837
+ Each chapter will show a "View on GitHub" link pointing to:
838
+ `{repository}/blob/HEAD/{path-from-git-root}`
839
+
840
+ The chapter path is resolved relative to the Git repository root.
841
+
842
+ ### Previous/Next Navigation
843
+
844
+ Each chapter displays Previous and Next navigation links at the bottom.
845
+ These follow the flat chapter order (including across parts and appendix).
846
+ Cover pages do not show navigation.
847
+
848
+ ## Write Command
849
+
850
+ The `write` command generates a complete book using AI (Claude CLI).
851
+
852
+ ### brief.yml fields
853
+
854
+ | Field | Required | Default | Description |
855
+ |-------|----------|---------|-------------|
856
+ | `title` | required | — | The book title. |
857
+ | `language` | optional | `"ja"` | Language. |
858
+ | `audience` | optional | — | Target audience (used in the prompt). |
859
+ | `notes` | optional | — | Additional instructions for Claude (free text). |
860
+ | `sources` | optional | — | Reference files for AI context. Array of strings or `{path:, label:}` objects. Paths relative to brief.yml. |
861
+ | `author` | optional | — | Passed through to book.yml. |
862
+ | `output_dir` | optional | — | Passed through to book.yml. |
863
+ | `chapter_numbers` | optional | — | Passed through to book.yml. |
864
+ | `style` | optional | — | Passed through to book.yml. |
865
+ | `repository` | optional | — | Passed through to book.yml. |
866
+
867
+ The book is generated in the directory containing brief.yml.
868
+ Example: `ligarb write ruby_book/brief.yml` creates files in ruby_book/.
@@ -41,13 +41,18 @@ module Ligarb
41
41
 
42
42
  bibliography = resolve_citations!(all_chapters)
43
43
 
44
+ FileUtils.mkdir_p(@config.output_path)
45
+
46
+ md_name = "index.md"
47
+ write_markdown_bundle(all_chapters, File.join(@config.output_path, md_name))
48
+
44
49
  html = Template.new.render(config: @config, chapters: all_chapters,
45
50
  structure: structure, assets: assets,
46
51
  index_entries: index_entries,
47
52
  bibliography: bibliography,
48
- github_review: github_review_data(@config))
53
+ github_review: github_review_data(@config),
54
+ ai_md: md_name)
49
55
 
50
- FileUtils.mkdir_p(@config.output_path)
51
56
  output_file = File.join(@config.output_path, "index.html")
52
57
  File.write(output_file, html)
53
58
 
@@ -76,6 +81,14 @@ module Ligarb
76
81
  all_lang_chapters.concat(lang_data[:chapters])
77
82
  end
78
83
 
84
+ FileUtils.mkdir_p(output_path)
85
+
86
+ langs.each do |ld|
87
+ md_name = "index.#{ld[:lang]}.md"
88
+ write_markdown_bundle(ld[:chapters], File.join(output_path, md_name))
89
+ ld[:ai_md] = md_name
90
+ end
91
+
79
92
  assets = AssetManager.new(output_path)
80
93
  assets.detect(all_lang_chapters)
81
94
  assets.provision!
@@ -428,6 +441,14 @@ module Ligarb
428
441
  text.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
429
442
  end
430
443
 
444
+ # Emit a clean Markdown full-text version of the book alongside index.html.
445
+ # AI/LLM readers (and the in-page hint) point here for low-noise content.
446
+ # The bundle is the concatenated chapter sources in flat reading order.
447
+ def write_markdown_bundle(chapters, path)
448
+ body = chapters.map { |ch| ch.source.strip }.reject(&:empty?).join("\n\n")
449
+ File.write(path, body.empty? ? body : "#{body}\n")
450
+ end
451
+
431
452
  def copy_images
432
453
  images_dir = File.join(@config.base_dir, "images")
433
454
  return unless Dir.exist?(images_dir)
@@ -8,7 +8,7 @@ module Ligarb
8
8
  class CrossReferenceError < StandardError; end
9
9
 
10
10
  attr_reader :title, :slug, :html, :headings, :number, :appendix_letter, :index_entries, :cite_entries,
11
- :path, :mermaid_blocks
11
+ :path, :mermaid_blocks, :source
12
12
  attr_accessor :part_title, :cover, :relative_path
13
13
 
14
14
  Heading = Struct.new(:level, :text, :id, :display_text, keyword_init: true)
data/lib/ligarb/config.rb CHANGED
@@ -233,6 +233,9 @@ module Ligarb
233
233
 
234
234
  def load_translations_hub(data)
235
235
  @title = data.fetch("title", "")
236
+ # The hub writes its combined build here; serve needs output_path to work
237
+ # even though hub init returns before the normal output_dir assignment.
238
+ @output_dir = data.fetch("output_dir", "build")
236
239
  @translations = []
237
240
 
238
241
  trans = data["translations"]
@@ -39,7 +39,10 @@ module Ligarb
39
39
  error_count = 0
40
40
  results.each do |result|
41
41
  error = result["error"]
42
- next unless error
42
+ # "environment" errors are limitations of the Node DOM stub (e.g. a node
43
+ # label containing HTML like <br>), not diagram syntax errors, so they
44
+ # would be false positives — skip them. See assets/mermaid_check.mjs.
45
+ next unless error && result["kind"] == "syntax"
43
46
 
44
47
  error_count += 1
45
48
  block = blocks[result["id"]]
@@ -13,7 +13,7 @@ module Ligarb
13
13
  @css_path = File.join(ASSETS_DIR, "style.css")
14
14
  end
15
15
 
16
- def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [], github_review: nil)
16
+ def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [], github_review: nil, ai_md: nil)
17
17
  css = File.read(@css_path)
18
18
  template = File.read(@template_path)
19
19
 
@@ -42,6 +42,7 @@ module Ligarb
42
42
  b.local_variable_set(:og_description, og_description(config, chapters))
43
43
  b.local_variable_set(:og_locale, og_locale(config.language))
44
44
  b.local_variable_set(:og_url, og_url(config))
45
+ b.local_variable_set(:ai_md, ai_md)
45
46
 
46
47
  ERB.new(template, trim_mode: "-").result(b)
47
48
  end
@@ -74,6 +75,7 @@ module Ligarb
74
75
  footer: cfg.effective_footer,
75
76
  index_tree: build_index_tree(ld[:index_entries], ld[:chapters]),
76
77
  bibliography: ld[:bibliography],
78
+ ai_md: ld[:ai_md],
77
79
  }
78
80
  end
79
81
 
@@ -99,6 +101,7 @@ module Ligarb
99
101
  b.local_variable_set(:og_description, og_description(first_config, first[:chapters]))
100
102
  b.local_variable_set(:og_locale, og_locale(first_config.language))
101
103
  b.local_variable_set(:og_url, og_url(first_config))
104
+ b.local_variable_set(:ai_md, nil)
102
105
 
103
106
  ERB.new(template, trim_mode: "-").result(b)
104
107
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ligarb
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -17,6 +17,15 @@
17
17
  <meta property="og:description" content="<%= h(og_description) %>">
18
18
  <%- end -%>
19
19
  <meta name="twitter:card" content="summary">
20
+ <%- if multilang -%>
21
+ <%- langs.each do |ld| -%>
22
+ <%- if ld[:ai_md] -%>
23
+ <link rel="alternate" type="text/markdown" hreflang="<%= h(ld[:language]) %>" href="<%= h(ld[:ai_md]) %>" title="<%= h(ld[:title]) %> (Markdown)">
24
+ <%- end -%>
25
+ <%- end -%>
26
+ <%- elsif ai_md -%>
27
+ <link rel="alternate" type="text/markdown" href="<%= h(ai_md) %>" title="Markdown version">
28
+ <%- end -%>
20
29
  <%- if author && !author.to_s.empty? -%>
21
30
  <meta property="book:author" content="<%= h(author) %>">
22
31
  <%- end -%>
@@ -272,6 +281,15 @@
272
281
  <button class="sidebar-toggle" id="sidebar-toggle" aria-label="Toggle sidebar">&#9776;</button>
273
282
 
274
283
  <main class="content" id="content">
284
+ <%- if multilang -%>
285
+ <%- langs.each do |ld| -%>
286
+ <%- if ld[:ai_md] -%>
287
+ <p class="ai-md-hint lang-content" data-lang="<%= ld[:lang] %>">For AI/LLM readers: a clean Markdown full-text version of this book is at <a href="<%= h(ld[:ai_md]) %>"><%= h(ld[:ai_md]) %></a>.</p>
288
+ <%- end -%>
289
+ <%- end -%>
290
+ <%- elsif ai_md -%>
291
+ <p class="ai-md-hint">For AI/LLM readers: a clean Markdown full-text version of this book is at <a href="<%= h(ai_md) %>"><%= h(ai_md) %></a>.</p>
292
+ <%- end -%>
275
293
  <%- if multilang -%>
276
294
  <%- langs.each do |ld| -%>
277
295
  <%- ld[:chapters].select(&:cover?).each do |chapter| -%>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ligarb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ligarb contributors
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '1.1'
68
+ - !ruby/object:Gem::Dependency
69
+ name: base64
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0.1'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0.1'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: rake
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +121,7 @@ files:
107
121
  - assets/review.js
108
122
  - assets/serve.js
109
123
  - assets/style.css
124
+ - docs/help.md
110
125
  - exe/ligarb
111
126
  - lib/ligarb/asset_manager.rb
112
127
  - lib/ligarb/builder.rb