create-asciidoc-multi-book-workspace 0.5.1 → 0.7.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.
- package/CHANGELOG.md +31 -0
- package/README.md +0 -1
- package/dist/runtime/adoc-books.mjs +146 -105
- package/package.json +2 -2
- package/templates/default-workspace/README.md +5 -7
- package/templates/default-workspace/books/00-book-anatomy/backmatter/{appendix-a.adoc → appendix-structure-reference.adoc} +1 -1
- package/templates/default-workspace/books/00-book-anatomy/book.adoc +11 -11
- package/templates/default-workspace/books/00-book-anatomy/parts/{01-front-structure/01-frontmatter-map.adoc → 010-front-structure/010-frontmatter-map.adoc} +2 -4
- package/templates/default-workspace/books/00-book-anatomy/parts/010-front-structure/_partintro.adoc +1 -0
- package/templates/default-workspace/books/00-book-anatomy/parts/{02-body-structure/01-body-map.adoc → 020-body-structure/010-body-map.adoc} +0 -1
- package/templates/default-workspace/books/00-book-anatomy/parts/020-body-structure/020-inline-structure.adoc +31 -0
- package/templates/default-workspace/books/01-starter-book/backmatter/{appendix-a.adoc → appendix-minimal-files.adoc} +1 -1
- package/templates/default-workspace/books/01-starter-book/book.adoc +4 -3
- package/templates/default-workspace/books/01-starter-book/chapters/{01-opening.adoc → 010-opening.adoc} +0 -1
- package/templates/default-workspace/books/02-multipart-monograph/backmatter/appendix-part-reference.adoc +5 -0
- package/templates/default-workspace/books/02-multipart-monograph/book.adoc +12 -9
- package/templates/default-workspace/books/02-multipart-monograph/parts/{01-domain/01-problem-world.adoc → 010-domain/010-problem-world.adoc} +0 -1
- package/templates/default-workspace/books/02-multipart-monograph/parts/{01-domain → 010-domain}/_partintro.adoc +0 -1
- package/templates/default-workspace/books/03-technical-book-workflow/book.adoc +3 -3
- package/templates/default-workspace/books/03-technical-book-workflow/chapters/{01-build-flow.adoc → 010-build-flow.adoc} +0 -1
- package/templates/default-workspace/books/04-reference-manual/backmatter/glossary.adoc +1 -2
- package/templates/default-workspace/books/04-reference-manual/book.adoc +4 -4
- package/templates/default-workspace/books/04-reference-manual/chapters/{01-decision-table.adoc → 010-decision-table.adoc} +0 -1
- package/templates/default-workspace/books/04-reference-manual/chapters/{02-path-rules.adoc → 020-path-rules.adoc} +0 -1
- package/templates/default-workspace/books/04-reference-manual/chapters/030-xref-patterns.adoc +19 -0
- package/templates/default-workspace/books/04-reference-manual/chapters/{04-growth-paths.adoc → 040-growth-paths.adoc} +0 -1
- package/templates/default-workspace/books/05-upper-volume/book.adoc +2 -2
- package/templates/default-workspace/books/05-upper-volume/chapters/{01-upper-opening.adoc → 010-upper-opening.adoc} +0 -1
- package/templates/default-workspace/books/05-upper-volume/chapters/020-upper-core-model.adoc +3 -0
- package/templates/default-workspace/books/06-lower-volume/backmatter/bibliography.adoc +1 -1
- package/templates/default-workspace/books/06-lower-volume/book.adoc +2 -2
- package/templates/default-workspace/books/06-lower-volume/chapters/{01-lower-opening.adoc → 010-lower-opening.adoc} +0 -1
- package/templates/default-workspace/books/06-lower-volume/chapters/{02-cross-volume-practice.adoc → 020-cross-volume-practice.adoc} +2 -2
- package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/appendix-decision-trees.adoc +198 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/appendix-quick-reference.adoc +82 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/bibliography.adoc +13 -3
- package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/glossary.adoc +14 -16
- package/templates/default-workspace/books/07-structured-writing-conventions/book.adoc +24 -12
- package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/acknowledgments.adoc +1 -1
- package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/colophon.adoc +1 -3
- package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/conventions.adoc +17 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/preface.adoc +3 -3
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/010-source-surface/010-source-and-projection.adoc +67 -52
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/010-source-surface/020-heading-address-and-xref.adoc +65 -24
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/010-source-surface/030-source-order-notation.adoc +20 -21
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/010-source-surface/_partintro.adoc +1 -1
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/020-names-roles-and-relations/010-names-and-addresses.adoc +35 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/020-names-roles-and-relations/020-heading-roles.adoc +19 -19
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/020-names-roles-and-relations/030-relation-predicate.adoc +23 -14
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/020-names-roles-and-relations/_partintro.adoc +1 -1
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-conventions/010-surface-fields.adoc +51 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-conventions/030-declaring-conventions.adoc +66 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-conventions/_partintro.adoc +1 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/040-book-prose/010-whom-a-book-speaks-to.adoc +64 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/040-book-prose/020-what-a-sentence-does.adoc +57 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/040-book-prose/030-names-and-examples.adoc +53 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/040-book-prose/040-print-conventions.adoc +89 -0
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/040-book-prose/_partintro.adoc +1 -0
- package/templates/default-workspace/catalog.adoc +3 -3
- package/templates/default-workspace/package.json.template +1 -2
- package/templates/solo-workspace/README.md +1 -2
- package/templates/solo-workspace/catalog.adoc +1 -1
- package/templates/default-workspace/books/00-book-anatomy/frontmatter/abstract.adoc +0 -5
- package/templates/default-workspace/books/00-book-anatomy/parts/01-front-structure/_partintro.adoc +0 -1
- package/templates/default-workspace/books/00-book-anatomy/parts/02-body-structure/02-inline-structure.adoc +0 -32
- package/templates/default-workspace/books/02-multipart-monograph/backmatter/appendix-a.adoc +0 -5
- package/templates/default-workspace/books/04-reference-manual/chapters/03-xref-patterns.adoc +0 -20
- package/templates/default-workspace/books/05-upper-volume/chapters/02-upper-core-model.adoc +0 -4
- package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/appendix-a.adoc +0 -82
- package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/appendix-b-decision-trees.adoc +0 -187
- package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/abstract.adoc +0 -3
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/020-names-roles-and-relations/010-names-and-address-labels.adoc +0 -20
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-terminology/010-surface-fields.adoc +0 -72
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-terminology/020-glossary.adoc +0 -6
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-terminology/030-declaring-conventions.adoc +0 -70
- package/templates/default-workspace/books/07-structured-writing-conventions/parts/030-fields-and-terminology/_partintro.adoc +0 -1
- /package/templates/default-workspace/books/00-book-anatomy/parts/{02-body-structure → 020-body-structure}/_partintro.adoc +0 -0
- /package/templates/default-workspace/books/01-starter-book/chapters/{02-main-flow.adoc → 020-main-flow.adoc} +0 -0
- /package/templates/default-workspace/books/02-multipart-monograph/parts/{01-domain/02-boundary.adoc → 010-domain/020-boundary.adoc} +0 -0
- /package/templates/default-workspace/books/02-multipart-monograph/parts/{02-specification/01-book-contract.adoc → 020-specification/010-book-contract.adoc} +0 -0
- /package/templates/default-workspace/books/02-multipart-monograph/parts/{02-specification/02-verification.adoc → 020-specification/020-verification.adoc} +0 -0
- /package/templates/default-workspace/books/02-multipart-monograph/parts/{02-specification → 020-specification}/_partintro.adoc +0 -0
- /package/templates/default-workspace/books/03-technical-book-workflow/chapters/{02-diagrams.adoc → 020-diagrams.adoc} +0 -0
- /package/templates/default-workspace/books/03-technical-book-workflow/chapters/{03-code-includes.adoc → 030-code-includes.adoc} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 - 2026-10-31
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
|
|
7
|
+
- The generated workspace has no `check` command. `pnpm run build` is the only way to render, and `check` ran the same path only so the runtime's checks could be run on their own.
|
|
8
|
+
- The runtime no longer judges a workspace: `XREF_UNRESOLVED`, `XREF_AMBIGUOUS`, `HTML_RESOURCE_MISSING`, `HTML_ANCHOR_MISSING`, `CATALOG_TARGET_MISSING`, `BOOK_MISSING_FROM_CATALOG`, and `MISSING_DOCTYPE` are gone. Whether a reference reaches one heading is read from the `asciidoc-abundant-tree` projection by the tool that reads it; the build fails only where it cannot produce the pages: a missing `catalog.adoc` or book, any Asciidoctor message, an include graph that cannot be parsed within `documentRoot` or an error-level parse diagnostic, or a configuration or include resolver it cannot use.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Generated workspaces depend on `asciidoc-abundant-tree` `^0.6.0`. Its label space is four predicates on the heading nodes of the graph, and an xref whose address several headings own gets an edge to each of them. The runtime rewrites an HTML link only when its xref reaches exactly one section, inside a book and across books; any other xref is left as Asciidoctor wrote it. The document title owns labels but is not a section, so an xref to a title only the document title owns is left as written.
|
|
13
|
+
- The samples no longer say a check reports broken references. After renaming a heading, an author searches the book for the old name, which finds every reference written by it (qualified addresses included), its link text, and the prose. The structured writing conventions sample states where an unbound or ambiguous link lands on the page (an unbound one is broken; one that several headings share lands on the first of them), shows the projection as `asciidoc-abundant-tree` 0.6 writes it (heading nodes named by the projector, addresses as `aat:headline` and `aat:addressPath` values), and lists a title that starts with inline markup among those a reference cannot name by their text.
|
|
14
|
+
|
|
15
|
+
### Upgrading an existing workspace
|
|
16
|
+
|
|
17
|
+
Create a temporary workspace with `pnpm create asciidoc-multi-book-workspace@0.7.0 tmp-books` and copy its `tools/adoc-books.mjs` over yours. In your `package.json`, remove the `check` script, set `"asciidoc-abundant-tree": "^0.6.0"`, and run `pnpm install`. Books need no change. To find references that reach no heading or several, read the projection: an xref edge (`aat:XrefEdge`) carries one `aat:targetHeading` for each heading its address reaches.
|
|
18
|
+
|
|
19
|
+
## 0.6.0 - 2026-10-31
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `pnpm run check` binds a qualified address: when several headings share a title, a book refers to one of them by writing the titles above it, joined with `::`, as in `xref:红烧肉::火候[]` or, across books, `xref:{dish-book}#红烧肉::火候[]`. The HTML link reaches that heading.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Generated workspaces depend on `asciidoc-abundant-tree` `^0.3.0`. Which xrefs exist and what their targets say are now read by Asciidoctor inside it, and the runtime no longer works around its gaps: xrefs inside passthroughs (`pass:[…]`, `+++…+++`, `$$…$$`, `` `+…+` ``) or escaped with `\` are not checked; `xref:` targets with spaces are bound and checked; `xref:#id[]` names `id`; an attribute reference in a target takes its value at the xref's position, where it used to take the value the book held after conversion; `xref:other#x[]` without `.adoc` names the document `other`, as Asciidoctor links it. The projection itself changed in 0.3.0 (fields are `field:<name>`, heading nodes are named by their address); tools that read it follow that release's changelog.
|
|
28
|
+
- The structured writing conventions sample teaches qualified addresses for shared titles, written with link text that joins the titles with an interpunct (《红烧肉·火候》). Two headings that share a title under one parent are renamed so each title says what its section is (糖醋排骨's two 炸 become 初炸 and 复炸); a custom ID is the second address only for a title that cannot serve, with rules for naming it. After a rename, an author also searches the book for the old name in link text and prose, which the check does not report. The projection example shows heading nodes named by their address and the field predicate `field:难度`, and the fields chapter states that a field name in an attrlist uses only letters, digits, `_`, and `-`. `00-book-anatomy` and the `04-reference-manual` glossary say the same about custom IDs.
|
|
29
|
+
|
|
30
|
+
### Upgrading an existing workspace
|
|
31
|
+
|
|
32
|
+
A generated workspace owns its `tools/adoc-books.mjs`, and this release changes it. Create a temporary workspace with `pnpm create asciidoc-multi-book-workspace@0.6.0 tmp-books`, copy its `tools/adoc-books.mjs` over yours, set `"asciidoc-abundant-tree": "^0.3.0"` in your `package.json`, and run `pnpm install`. Books need no change; a heading that declares an ID only to tell it apart from others with the same title can drop the ID once its references use the qualified address.
|
|
33
|
+
|
|
3
34
|
## 0.5.1 - 2026-10-23
|
|
4
35
|
|
|
5
36
|
### Changed
|
package/README.md
CHANGED
|
@@ -5,13 +5,15 @@ import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promi
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
import asciidoctorFactory from "@asciidoctor/core";
|
|
8
|
-
import { parseAbundantTree } from "asciidoc-abundant-tree";
|
|
8
|
+
import { parseAbundantTree, rdf12 } from "asciidoc-abundant-tree";
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const DIAGRAM_BLOCK_PATTERN = /^\[(?:actdiag|blockdiag|bpmn|bytefield|c4plantuml|d2|dbml|ditaa|erd|excalidraw|graphviz|mermaid|nomnoml|nwdiag|packetdiag|pikchr|plantuml|rackdiag|seqdiag|svgbob|symbolator|umlet|vega|vegalite|wavedrom|structurizr|diagramsnet|wireviz)(?:,|\])/m;
|
|
11
|
-
const CATALOG_BOOK_XREF_PATTERN = /xref:books\/([^/\]]+)\/book\.adoc(?:#[^\[]+)?\[/g;
|
|
12
|
-
const LOCAL_TARGET_PATTERN = /\b(href|src)="([^"]+)"/g;
|
|
13
11
|
const HTML_ANCHOR_PATTERN = /\b(?:id|name)="([^"]+)"/g;
|
|
14
|
-
const
|
|
12
|
+
const AAT_NAMESPACE = "https://micheng.dev/ns/asciidoc-abundant-tree#";
|
|
13
|
+
// 标题投影的 label space:aat 图上标题节点的这四个谓词(aat 06 号书第 2 章「地址与 Label 空间」)。
|
|
14
|
+
const LABEL_PREDICATES = ["headline", "addressPath", "addressLabel", "generatedAddressLabel"];
|
|
15
|
+
const HTML_FRAGMENT_LINK_PATTERN = /<a href="#([^"]*)"([^>]*)>([\s\S]*?)<\/a>/g;
|
|
16
|
+
const HTML_DOCUMENT_LINK_PATTERN = /<a href="([^"#]+\.html)#([^"]*)"([^>]*)>([\s\S]*?)<\/a>/g;
|
|
15
17
|
const HOME_MARKER = "data-multi-book-home";
|
|
16
18
|
const CONTROLS_MARKER = "data-multi-book-controls";
|
|
17
19
|
const FETCH_DIAGRAMS_ENV = "ADOC_BOOKS_FETCH_DIAGRAMS";
|
|
@@ -322,8 +324,9 @@ function convertFile(asciidoctor, extensionRegistry, input, outputFile, baseDir,
|
|
|
322
324
|
const defaultLogger = loggerManager.getLogger();
|
|
323
325
|
const memoryLogger = asciidoctor.MemoryLogger.create();
|
|
324
326
|
loggerManager.setLogger(memoryLogger);
|
|
327
|
+
let document;
|
|
325
328
|
try {
|
|
326
|
-
asciidoctor.convertFile(input, {
|
|
329
|
+
document = asciidoctor.convertFile(input, {
|
|
327
330
|
safe: "unsafe",
|
|
328
331
|
base_dir: baseDir,
|
|
329
332
|
to_file: outputFile,
|
|
@@ -339,6 +342,7 @@ function convertFile(asciidoctor, extensionRegistry, input, outputFile, baseDir,
|
|
|
339
342
|
if (messages.length > 0) {
|
|
340
343
|
throw new Error(`AsciiDoc strict check failed for ${input}\n${messages.map(formatAsciidoctorMessage).join("\n")}`);
|
|
341
344
|
}
|
|
345
|
+
return document;
|
|
342
346
|
}
|
|
343
347
|
async function buildHtml(rootDir, books, asciidoctor, extensionRegistry, useKroki, fetchDiagrams) {
|
|
344
348
|
await mkdir(path.join(rootDir, "build", "html"), { recursive: true });
|
|
@@ -462,16 +466,18 @@ function sourceBundleFromDocument(document) {
|
|
|
462
466
|
}).join("\n");
|
|
463
467
|
}
|
|
464
468
|
// 解析失败就让构建失败,不退回只有封面的阅读页:退回时 include 越过 documentRoot 只剩一条 warning,
|
|
465
|
-
// 构建照常成功,没有人发现阅读页坏了(DESIGN.adoc 的
|
|
469
|
+
// 构建照常成功,没有人发现阅读页坏了(DESIGN.adoc 的 Rendering Failures)。
|
|
466
470
|
async function buildReaderBookData(config, book) {
|
|
471
|
+
let parsed;
|
|
467
472
|
let document;
|
|
468
473
|
try {
|
|
469
|
-
|
|
474
|
+
parsed = await parseAbundantTree({
|
|
470
475
|
sourcePath: book.input,
|
|
471
476
|
mode: "book-entry",
|
|
472
477
|
documentRoot: config.documentRoot,
|
|
473
478
|
includeResolvers: config.includeResolvers
|
|
474
479
|
});
|
|
480
|
+
document = parsed;
|
|
475
481
|
}
|
|
476
482
|
catch (error) {
|
|
477
483
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -490,7 +496,9 @@ async function buildReaderBookData(config, book) {
|
|
|
490
496
|
const sections = (Array.isArray(document.children) ? document.children : []).filter((child) => {
|
|
491
497
|
return typeof child === "object" && child !== null && child.kind === "section";
|
|
492
498
|
});
|
|
493
|
-
|
|
499
|
+
// abstract 不在其中:它是 article 的样式,book 里的 [abstract] 被 Asciidoctor 当作编号的一章,阅读器照它渲染的样子
|
|
500
|
+
// 当章节显示(docs/design/book-dual-reader-view.adoc 的 Page Map)。
|
|
501
|
+
const frontmatterStyles = new Set(["colophon", "dedication", "preface", "acknowledgments"]);
|
|
494
502
|
const backmatterStyles = new Set(["appendix", "glossary", "bibliography", "index"]);
|
|
495
503
|
const frontmatter = sections.filter((section) => section.level === 1 && frontmatterStyles.has(sectionStyle(section) ?? ""));
|
|
496
504
|
if (frontmatter.length > 0) {
|
|
@@ -543,8 +551,125 @@ async function buildReaderBookData(config, book) {
|
|
|
543
551
|
};
|
|
544
552
|
return {
|
|
545
553
|
pageMap,
|
|
546
|
-
sourceBundle: sourceBundleFromDocument(document)
|
|
554
|
+
sourceBundle: sourceBundleFromDocument(document),
|
|
555
|
+
references: bindReferences(config, parsed)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
// 书内 xref 落在哪一节,读 aat 的投影(DESIGN.adoc 的 Reference Binding):哪些是 xref、目标写的是什么(透传与转义
|
|
559
|
+
// 不算,属性已展开,指向别的文档的另记 targetDocument)由 aat 里的 Asciidoctor 读出,一处引用对上几节就有几条
|
|
560
|
+
// targetHeading。链接只能跳到一处,所以恰好一节才改写;零节或几节保持 Asciidoctor 写下的样子,不替人挑,也不报:
|
|
561
|
+
// 引用对不对是投影的读者(bookgraph)的解释,不是渲染的事(DESIGN.adoc 的 Division of Labour)。
|
|
562
|
+
function bindReferences(config, document) {
|
|
563
|
+
const projection = rdf12(document, { documentRoot: config.documentRoot });
|
|
564
|
+
const graph = projection.graph;
|
|
565
|
+
const values = (subject, name) => {
|
|
566
|
+
return graph.match({ subject, predicate: { termType: "iri", value: `${AAT_NAMESPACE}${name}` } })
|
|
567
|
+
.map((triple) => triple.object.termType === "triple" ? "" : triple.object.value);
|
|
547
568
|
};
|
|
569
|
+
// 只数节:书名也是图上的一个标题节点、也拥有标签,可它不是书里的一节,网页上没有落点(nodeIndex 里它不是 section)。
|
|
570
|
+
const sections = new Map(projection.nodeIndex.entries()
|
|
571
|
+
.filter((entry) => entry.kind === "section")
|
|
572
|
+
.map((entry) => [entry.iri.value, entry]));
|
|
573
|
+
const located = (iris) => [...iris].flatMap((iri) => {
|
|
574
|
+
const entry = sections.get(iri);
|
|
575
|
+
const id = entry?.node.ids?.[0];
|
|
576
|
+
if (entry === undefined || id === undefined)
|
|
577
|
+
return [];
|
|
578
|
+
return [{ id, title: entry.node.title ?? id }];
|
|
579
|
+
});
|
|
580
|
+
// 别的书指过来的引用在这本书的 label space 里找:图上四个标签谓词的值,按整串相等找拥有者。
|
|
581
|
+
const ownersByLabel = new Map();
|
|
582
|
+
for (const name of LABEL_PREDICATES) {
|
|
583
|
+
for (const triple of graph.match({ predicate: { termType: "iri", value: `${AAT_NAMESPACE}${name}` } })) {
|
|
584
|
+
if (triple.object.termType !== "literal")
|
|
585
|
+
continue;
|
|
586
|
+
const iris = ownersByLabel.get(triple.object.value) ?? new Set();
|
|
587
|
+
iris.add(triple.subject.value);
|
|
588
|
+
ownersByLabel.set(triple.object.value, iris);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const owners = (selector) => located(ownersByLabel.get(selector) ?? []);
|
|
592
|
+
const headingsByFragment = new Map();
|
|
593
|
+
const crossBook = [];
|
|
594
|
+
// 指向整本书的 xref(没有片段)没有 targetSelector:没有可改写的片段。
|
|
595
|
+
for (const triple of graph.match({ predicate: { termType: "iri", value: `${AAT_NAMESPACE}targetSelector` } })) {
|
|
596
|
+
const edge = triple.subject;
|
|
597
|
+
const fragment = triple.object.termType === "triple" ? "" : triple.object.value;
|
|
598
|
+
const document = values(edge, "targetDocument")[0];
|
|
599
|
+
if (document !== undefined) {
|
|
600
|
+
crossBook.push({ document, fragment });
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const found = located(values(edge, "targetHeading"));
|
|
604
|
+
if (found.length !== 1)
|
|
605
|
+
continue;
|
|
606
|
+
headingsByFragment.set(fragment, found[0]);
|
|
607
|
+
headingsByFragment.set(escapeHtmlAttribute(fragment), found[0]);
|
|
608
|
+
}
|
|
609
|
+
return { headingsByFragment, crossBook, owners };
|
|
610
|
+
}
|
|
611
|
+
// 跨书 xref 同样跟着投影走,只是 label space 在目标书里(DESIGN.adoc 的 Reference Binding)。aat 不打开别的文档,
|
|
612
|
+
// 工作区解析了每一本书,所以在这里找。路径按书目录解析:Asciidoctor 把它照写进 HTML,而 HTML 的输出目录与 books/ 下的
|
|
613
|
+
// 书目录一一对应。指向的不是工作区里的书、只指向整本书(没有片段)、或在目标书里对上零节或几节的,保持原样。
|
|
614
|
+
async function bindCrossBookReferences(book, references, bindingsByInput, anchorCache) {
|
|
615
|
+
const linksByHref = new Map();
|
|
616
|
+
for (const xref of references.crossBook) {
|
|
617
|
+
const { fragment } = xref;
|
|
618
|
+
// Asciidoctor 把 xref:other#x[] 与 xref:other.adoc#x[] 都读成文档 other:去掉 .adoc、没有扩展名的照写,
|
|
619
|
+
// 再加 .html 写进 href。书的入口按 .adoc 登记,链接按 .html 改写,两边都从这个文档名出发。
|
|
620
|
+
const documentName = path.extname(xref.document) === ".adoc" ? xref.document.slice(0, -".adoc".length) : xref.document;
|
|
621
|
+
const target = bindingsByInput.get(path.resolve(book.bookDir, `${documentName}.adoc`));
|
|
622
|
+
if (fragment === "" || target === undefined)
|
|
623
|
+
continue;
|
|
624
|
+
const found = target.references.owners(fragment);
|
|
625
|
+
if (found.length !== 1)
|
|
626
|
+
continue;
|
|
627
|
+
const heading = found[0];
|
|
628
|
+
// 指向本书自己的 book.adoc:Asciidoctor 把它当书内引用,写成 #片段。
|
|
629
|
+
if (target.book.bookId === book.bookId) {
|
|
630
|
+
references.headingsByFragment.set(fragment, heading);
|
|
631
|
+
references.headingsByFragment.set(escapeHtmlAttribute(fragment), heading);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const anchors = await anchorsForHtmlFile(path.join(target.book.htmlOutputDir, "book.html"), anchorCache);
|
|
635
|
+
if (anchors.has(fragment))
|
|
636
|
+
continue;
|
|
637
|
+
const htmlPath = `${documentName}.html`;
|
|
638
|
+
const link = { href: `${htmlPath}#${heading.id}`, title: heading.title };
|
|
639
|
+
linksByHref.set(`${htmlPath}#${fragment}`, link);
|
|
640
|
+
linksByHref.set(escapeHtmlAttribute(`${htmlPath}#${fragment}`), link);
|
|
641
|
+
}
|
|
642
|
+
return linksByHref;
|
|
643
|
+
}
|
|
644
|
+
// 在目标书里恰好对上一节、而目标书的 HTML 里没有这个片段的跨书链接,改成指向那一节的标题。Asciidoctor 对没写显示文字的跨书 xref
|
|
645
|
+
// 显示文档路径;改写时换成标题。
|
|
646
|
+
function rewriteCrossBookXrefs(html, linksByHref) {
|
|
647
|
+
if (linksByHref.size === 0)
|
|
648
|
+
return html;
|
|
649
|
+
return html.replace(HTML_DOCUMENT_LINK_PATTERN, (whole, htmlPath, fragment, rest, text) => {
|
|
650
|
+
const link = linksByHref.get(`${htmlPath}#${fragment}`) ?? linksByHref.get(`${htmlPath}#${decodeUrlFragment(fragment)}`);
|
|
651
|
+
if (link === undefined)
|
|
652
|
+
return whole;
|
|
653
|
+
const shown = text === htmlPath ? escapeHtmlAttribute(link.title) : text;
|
|
654
|
+
return `<a href="${escapeHtmlAttribute(link.href)}"${rest}>${shown}</a>`;
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
// Asciidoctor 把它解析不了的书内 xref 写成 <a href="#选择器">[选择器]</a>;aat 的投影里恰好对上一节的,改成指向那一节的标题,
|
|
658
|
+
// 显示标题。片段已经是 HTML 里某个元素的 id 时,保持 Asciidoctor 写下的样子。
|
|
659
|
+
function rewriteBoundXrefs(html, headingsByFragment) {
|
|
660
|
+
if (headingsByFragment.size === 0)
|
|
661
|
+
return html;
|
|
662
|
+
const anchors = htmlAnchors(html);
|
|
663
|
+
return html.replace(HTML_FRAGMENT_LINK_PATTERN, (whole, fragment, rest, text) => {
|
|
664
|
+
const decoded = decodeUrlFragment(fragment);
|
|
665
|
+
if (anchors.has(fragment) || anchors.has(decoded))
|
|
666
|
+
return whole;
|
|
667
|
+
const heading = headingsByFragment.get(fragment) ?? headingsByFragment.get(decoded);
|
|
668
|
+
if (heading === undefined)
|
|
669
|
+
return whole;
|
|
670
|
+
const shown = text === `[${fragment}]` ? escapeHtmlAttribute(heading.title) : text;
|
|
671
|
+
return `<a href="#${escapeHtmlAttribute(heading.id)}"${rest}>${shown}</a>`;
|
|
672
|
+
});
|
|
548
673
|
}
|
|
549
674
|
function readerStyles() {
|
|
550
675
|
return `
|
|
@@ -1061,12 +1186,20 @@ ${readerStyles()}
|
|
|
1061
1186
|
}
|
|
1062
1187
|
async function addHomeLinks(rootDir, books, config) {
|
|
1063
1188
|
const catalog = path.join(rootDir, "build", "html", "catalog.html");
|
|
1189
|
+
// 跨书 xref 在目标书的 label space 里找,所以先把每一本书都解析完。
|
|
1190
|
+
const readerData = new Map();
|
|
1191
|
+
for (const book of books) {
|
|
1192
|
+
readerData.set(book.bookId, await buildReaderBookData(config, book));
|
|
1193
|
+
}
|
|
1194
|
+
const bindingsByInput = new Map(books.map((book) => [book.input, { book, references: readerData.get(book.bookId).references }]));
|
|
1195
|
+
const anchorCache = new Map();
|
|
1064
1196
|
for (const book of books) {
|
|
1065
1197
|
const htmlFile = path.join(book.htmlOutputDir, "book.html");
|
|
1066
|
-
const html = await readFile(htmlFile, "utf8");
|
|
1067
1198
|
const href = path.relative(path.dirname(htmlFile), catalog);
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1199
|
+
const data = readerData.get(book.bookId);
|
|
1200
|
+
const crossBookLinks = await bindCrossBookReferences(book, data.references, bindingsByInput, anchorCache);
|
|
1201
|
+
const html = rewriteCrossBookXrefs(rewriteBoundXrefs(await readFile(htmlFile, "utf8"), data.references.headingsByFragment), crossBookLinks);
|
|
1202
|
+
await writeFile(htmlFile, addBookControlsToBookHtml(html, href, config.homeLink, data.sourceBundle, data.pageMap, book.bookId), "utf8");
|
|
1070
1203
|
}
|
|
1071
1204
|
}
|
|
1072
1205
|
async function writeRootIndex(rootDir, rootIndex) {
|
|
@@ -1086,56 +1219,6 @@ async function writeRootIndex(rootDir, rootIndex) {
|
|
|
1086
1219
|
</html>
|
|
1087
1220
|
`, "utf8");
|
|
1088
1221
|
}
|
|
1089
|
-
function extractCatalogBookIds(catalogSource) {
|
|
1090
|
-
const ids = new Set();
|
|
1091
|
-
for (const match of catalogSource.matchAll(CATALOG_BOOK_XREF_PATTERN))
|
|
1092
|
-
ids.add(match[1]);
|
|
1093
|
-
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
1094
|
-
}
|
|
1095
|
-
function issue(code, detail) {
|
|
1096
|
-
return { code, detail };
|
|
1097
|
-
}
|
|
1098
|
-
// xref 的目标与锚点不在这里查:Asciidoctor 把它们写成 HTML 里的链接与 ID,missingLocalResources 在生成的 HTML 上
|
|
1099
|
-
// 核对(DESIGN.adoc 的 Workspace Contract Checks)。ID 有 [#id]、[.role#id]、[[id]]、[id=id] 与标题生成等写法,
|
|
1100
|
-
// 只有 Asciidoctor 认得全。
|
|
1101
|
-
async function workspaceContractIssues(rootDir, books) {
|
|
1102
|
-
const issues = [];
|
|
1103
|
-
const catalogSource = await readIfExists(path.join(rootDir, "catalog.adoc"));
|
|
1104
|
-
const catalogBookIds = extractCatalogBookIds(catalogSource);
|
|
1105
|
-
const bookSet = new Set(books.map((book) => book.bookId));
|
|
1106
|
-
const catalogBookSet = new Set(catalogBookIds);
|
|
1107
|
-
for (const bookId of catalogBookIds) {
|
|
1108
|
-
if (!bookSet.has(bookId))
|
|
1109
|
-
issues.push(issue("CATALOG_TARGET_MISSING", bookId));
|
|
1110
|
-
}
|
|
1111
|
-
for (const book of books) {
|
|
1112
|
-
if (!catalogBookSet.has(book.bookId))
|
|
1113
|
-
issues.push(issue("BOOK_MISSING_FROM_CATALOG", book.bookId));
|
|
1114
|
-
}
|
|
1115
|
-
for (const book of books) {
|
|
1116
|
-
const bookSource = await readFile(book.input, "utf8");
|
|
1117
|
-
if (!/^:doctype:\s+book$/m.test(bookSource))
|
|
1118
|
-
issues.push(issue("MISSING_DOCTYPE", book.bookId));
|
|
1119
|
-
}
|
|
1120
|
-
return issues;
|
|
1121
|
-
}
|
|
1122
|
-
async function collectHtmlFiles(dir) {
|
|
1123
|
-
return collectFiles(dir, (filePath) => filePath.endsWith(".html"));
|
|
1124
|
-
}
|
|
1125
|
-
function extractLocalTargets(html) {
|
|
1126
|
-
const targets = [];
|
|
1127
|
-
for (const match of html.matchAll(LOCAL_TARGET_PATTERN)) {
|
|
1128
|
-
const [, attribute, rawTarget] = match;
|
|
1129
|
-
if (rawTarget === "" || rawTarget === "#" || rawTarget.startsWith("//") || SCHEME_PATTERN.test(rawTarget)) {
|
|
1130
|
-
continue;
|
|
1131
|
-
}
|
|
1132
|
-
const targetWithoutFragment = rawTarget.split("#", 1)[0].split("?", 1)[0];
|
|
1133
|
-
const fragment = rawTarget.includes("#") ? rawTarget.split("#", 2)[1].split("?", 1)[0] : "";
|
|
1134
|
-
if (targetWithoutFragment !== "" || fragment !== "")
|
|
1135
|
-
targets.push({ attribute, rawTarget });
|
|
1136
|
-
}
|
|
1137
|
-
return targets;
|
|
1138
|
-
}
|
|
1139
1222
|
function decodeUrlFragment(fragment) {
|
|
1140
1223
|
try {
|
|
1141
1224
|
return decodeURIComponent(fragment);
|
|
@@ -1158,39 +1241,6 @@ async function anchorsForHtmlFile(filePath, cache) {
|
|
|
1158
1241
|
cache.set(filePath, anchors);
|
|
1159
1242
|
return anchors;
|
|
1160
1243
|
}
|
|
1161
|
-
async function missingLocalResources(rootDir) {
|
|
1162
|
-
const htmlDir = path.join(rootDir, "build", "html");
|
|
1163
|
-
const htmlFiles = await collectHtmlFiles(htmlDir);
|
|
1164
|
-
const issues = [];
|
|
1165
|
-
const anchorCache = new Map();
|
|
1166
|
-
for (const htmlFile of htmlFiles) {
|
|
1167
|
-
const html = await readFile(htmlFile, "utf8");
|
|
1168
|
-
for (const target of extractLocalTargets(html)) {
|
|
1169
|
-
const targetPath = target.rawTarget.split("#", 1)[0].split("?", 1)[0];
|
|
1170
|
-
const fragment = target.rawTarget.includes("#") ? target.rawTarget.split("#", 2)[1].split("?", 1)[0] : "";
|
|
1171
|
-
const resolved = targetPath === "" ? htmlFile : path.resolve(path.dirname(htmlFile), targetPath);
|
|
1172
|
-
if (targetPath !== "" && !await existsFile(resolved)) {
|
|
1173
|
-
issues.push(issue("HTML_RESOURCE_MISSING", `${path.relative(htmlDir, htmlFile)} -> ${target.rawTarget}`));
|
|
1174
|
-
continue;
|
|
1175
|
-
}
|
|
1176
|
-
if (target.attribute === "href" && fragment !== "" && (targetPath === "" || resolved.endsWith(".html"))) {
|
|
1177
|
-
const anchors = await anchorsForHtmlFile(resolved, anchorCache);
|
|
1178
|
-
const decodedFragment = decodeUrlFragment(fragment);
|
|
1179
|
-
if (!anchors.has(fragment) && !anchors.has(decodedFragment)) {
|
|
1180
|
-
issues.push(issue("HTML_ANCHOR_MISSING", `${path.relative(htmlDir, htmlFile)} -> ${target.rawTarget}`));
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
return issues;
|
|
1186
|
-
}
|
|
1187
|
-
async function assertNoIssues(label, issues) {
|
|
1188
|
-
if (issues.length === 0)
|
|
1189
|
-
return;
|
|
1190
|
-
for (const entry of issues)
|
|
1191
|
-
console.error(`${entry.code}: ${entry.detail}`);
|
|
1192
|
-
throw new Error(`${label} failed with ${issues.length} issue(s)`);
|
|
1193
|
-
}
|
|
1194
1244
|
export async function buildWorkspace(rootDir = process.cwd()) {
|
|
1195
1245
|
const catalog = path.join(rootDir, "catalog.adoc");
|
|
1196
1246
|
if (!await existsFile(catalog))
|
|
@@ -1206,11 +1256,6 @@ export async function buildWorkspace(rootDir = process.cwd()) {
|
|
|
1206
1256
|
await copyAssets(rootDir, books);
|
|
1207
1257
|
await addHomeLinks(rootDir, books, config);
|
|
1208
1258
|
await writeRootIndex(rootDir, config.rootIndex);
|
|
1209
|
-
await assertNoIssues("HTML local resource check", await missingLocalResources(rootDir));
|
|
1210
|
-
await assertNoIssues("workspace contract check", await workspaceContractIssues(rootDir, books));
|
|
1211
|
-
}
|
|
1212
|
-
export async function checkWorkspace(rootDir = process.cwd()) {
|
|
1213
|
-
await buildWorkspace(rootDir);
|
|
1214
1259
|
}
|
|
1215
1260
|
export async function cleanWorkspace(rootDir = process.cwd()) {
|
|
1216
1261
|
await rm(path.join(rootDir, "build"), { force: true, recursive: true });
|
|
@@ -1221,15 +1266,11 @@ async function main() {
|
|
|
1221
1266
|
await buildWorkspace();
|
|
1222
1267
|
return;
|
|
1223
1268
|
}
|
|
1224
|
-
if (command === "check") {
|
|
1225
|
-
await checkWorkspace();
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
1269
|
if (command === "clean") {
|
|
1229
1270
|
await cleanWorkspace();
|
|
1230
1271
|
return;
|
|
1231
1272
|
}
|
|
1232
|
-
console.error("Usage: node tools/adoc-books.mjs <build|
|
|
1273
|
+
console.error("Usage: node tools/adoc-books.mjs <build|clean>");
|
|
1233
1274
|
process.exitCode = 1;
|
|
1234
1275
|
}
|
|
1235
1276
|
const executedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-asciidoc-multi-book-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Create a complete AsciiDoc multi-book workspace.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@asciidoctor/core": "^3.0.4",
|
|
56
56
|
"@types/node": "^24.10.1",
|
|
57
|
-
"asciidoc-abundant-tree": "
|
|
57
|
+
"asciidoc-abundant-tree": "link:../asciidoc-abundant-tree",
|
|
58
58
|
"asciidoctor-kroki": "^0.18.1",
|
|
59
59
|
"playwright": "^1.59.1",
|
|
60
60
|
"typescript": "^5.9.3"
|
|
@@ -29,13 +29,11 @@ To fetch diagram images into the local HTML output, run:
|
|
|
29
29
|
ADOC_BOOKS_FETCH_DIAGRAMS=1 pnpm run build
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## When The Build Fails
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
pnpm run check
|
|
36
|
-
```
|
|
34
|
+
The build fails only when it cannot produce the pages: Asciidoctor reports a message while rendering, a book's includes leave `documentRoot` (see Configure), or an include resolver returns something the build cannot use.
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
It does not judge your books. Whether an xref reaches the heading you meant, whether a local link resolves, and whether `catalog.adoc` lists every book are left to tools that read the RDF projection `asciidoc-abundant-tree` makes of each book. An xref whose address one heading owns links to that heading; one that no heading owns, or several do, keeps the link Asciidoctor wrote, and the build still succeeds.
|
|
39
37
|
|
|
40
38
|
## Configure
|
|
41
39
|
|
|
@@ -52,7 +50,7 @@ export default {
|
|
|
52
50
|
};
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
`documentRoot` matters when this workspace lives inside a project repository. Set it to the project root (`".."` when the workspace is a direct subdirectory) and books can include the project's own code instead of a copy of it. Source paths in the reader and the `// file:` markers of copied source then start at that root. It must contain this workspace. An include that leaves `documentRoot` fails
|
|
53
|
+
`documentRoot` matters when this workspace lives inside a project repository. Set it to the project root (`".."` when the workspace is a direct subdirectory) and books can include the project's own code instead of a copy of it. Source paths in the reader and the `// file:` markers of copied source then start at that root. It must contain this workspace. An include that leaves `documentRoot` fails the build and names the book.
|
|
56
54
|
|
|
57
55
|
`includeResolvers` lets books include lines chosen by your own code, for example one function out of a source file:
|
|
58
56
|
|
|
@@ -95,7 +93,7 @@ Delete `books/<book-id>/`, then remove the matching `xref:books/<book-id>/book.a
|
|
|
95
93
|
Run:
|
|
96
94
|
|
|
97
95
|
```bash
|
|
98
|
-
pnpm run
|
|
96
|
+
pnpm run build
|
|
99
97
|
```
|
|
100
98
|
|
|
101
99
|
## Add A Book
|
|
@@ -5,16 +5,16 @@ v0.1, 2026-05
|
|
|
5
5
|
:toc: left
|
|
6
6
|
:toclevels: 3
|
|
7
7
|
:sectnums:
|
|
8
|
+
:partnums:
|
|
9
|
+
:part-signifier: Part
|
|
8
10
|
:icons: font
|
|
9
11
|
:experimental:
|
|
12
|
+
:appendix-caption: 附录
|
|
10
13
|
:idprefix:
|
|
11
14
|
:idseparator: -
|
|
12
15
|
|
|
13
16
|
include::../../shared/attributes.adoc[]
|
|
14
17
|
|
|
15
|
-
[abstract]
|
|
16
|
-
include::frontmatter/abstract.adoc[]
|
|
17
|
-
|
|
18
18
|
[colophon]
|
|
19
19
|
include::frontmatter/colophon.adoc[]
|
|
20
20
|
|
|
@@ -27,24 +27,24 @@ include::frontmatter/preface.adoc[]
|
|
|
27
27
|
[acknowledgments]
|
|
28
28
|
include::frontmatter/acknowledgments.adoc[]
|
|
29
29
|
|
|
30
|
-
=
|
|
30
|
+
= 前置结构
|
|
31
31
|
|
|
32
32
|
[partintro]
|
|
33
|
-
include::parts/
|
|
33
|
+
include::parts/010-front-structure/_partintro.adoc[]
|
|
34
34
|
|
|
35
|
-
include::parts/
|
|
35
|
+
include::parts/010-front-structure/010-frontmatter-map.adoc[]
|
|
36
36
|
|
|
37
|
-
=
|
|
37
|
+
= 正文结构
|
|
38
38
|
|
|
39
39
|
[partintro]
|
|
40
|
-
include::parts/
|
|
40
|
+
include::parts/020-body-structure/_partintro.adoc[]
|
|
41
41
|
|
|
42
|
-
include::parts/
|
|
42
|
+
include::parts/020-body-structure/010-body-map.adoc[]
|
|
43
43
|
|
|
44
|
-
include::parts/
|
|
44
|
+
include::parts/020-body-structure/020-inline-structure.adoc[]
|
|
45
45
|
|
|
46
46
|
[appendix]
|
|
47
|
-
include::backmatter/appendix-
|
|
47
|
+
include::backmatter/appendix-structure-reference.adoc[]
|
|
48
48
|
|
|
49
49
|
[glossary]
|
|
50
50
|
include::backmatter/glossary.adoc[]
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
[#frontmatter-map]
|
|
2
1
|
== 前置内容地图
|
|
3
2
|
|
|
4
3
|
前置内容用于建立读者进入正文之前需要知道的说明、来源和阅读位置。
|
|
@@ -9,9 +8,6 @@
|
|
|
9
8
|
|===
|
|
10
9
|
|结构 |职责
|
|
11
10
|
|
|
12
|
-
|`[abstract]`
|
|
13
|
-
|说明整本书覆盖什么。
|
|
14
|
-
|
|
15
11
|
|`[colophon]`
|
|
16
12
|
|记录版本、出版或来源信息。
|
|
17
13
|
|
|
@@ -24,3 +20,5 @@
|
|
|
24
20
|
|`[acknowledgments]`
|
|
25
21
|
|放置致谢。
|
|
26
22
|
|===
|
|
23
|
+
|
|
24
|
+
书没有摘要。`[abstract]` 是 article 的 section style:摘要概述一篇文章,书的前置内容里没有这一项。在 book 里写 `[abstract]`,Asciidoctor 会把那一节当作普通的一章编号,它后面每一章的编号都跟着多一。一本书覆盖什么、怎样读,写在前言里。
|
package/templates/default-workspace/books/00-book-anatomy/parts/010-front-structure/_partintro.adoc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
本部展示一本书进入正文之前的结构位置。版本说明、献辞、前言和致谢都属于书籍前置内容。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
== 内联结构标记
|
|
2
|
+
|
|
3
|
+
内联结构标记给正文中的术语、路径和索引入口标出它们的身份,并写出正文对别的章节的引用。它不改变 book 的章节层级,但会提高正文片段的可维护性和可检索性。
|
|
4
|
+
|
|
5
|
+
[cols="1,2", options="header"]
|
|
6
|
+
|===
|
|
7
|
+
|写法 |结构作用
|
|
8
|
+
|
|
9
|
+
|`xref:正文结构地图[]`
|
|
10
|
+
|按标题引用同一本书里的一节:标题就是这一节的地址。
|
|
11
|
+
|
|
12
|
+
|`[#id]`
|
|
13
|
+
|custom ID:给标题再加一个地址。同名的标题先写上级标题、以 `::` 隔开来引用(`pass:[xref:上级标题::本节标题[]]`);同一个上级下也同名,或标题里有 `#`、`::` 时才写。
|
|
14
|
+
|
|
15
|
+
|`indexterm2:[term]`
|
|
16
|
+
|把正文中已经出现的词作为可见索引词。
|
|
17
|
+
|
|
18
|
+
|`indexterm:[term, detail]`
|
|
19
|
+
|正文没有出现该词时,补充隐藏索引入口。
|
|
20
|
+
|
|
21
|
+
|`[.term]#inline role#`
|
|
22
|
+
|给术语片段提供语义身份。
|
|
23
|
+
|
|
24
|
+
|`[.path]` + monospace path
|
|
25
|
+
|给路径文本提供语义身份。
|
|
26
|
+
|
|
27
|
+
|`{series-name}`
|
|
28
|
+
|引用共享属性,减少多处重复文本。
|
|
29
|
+
|===
|
|
30
|
+
|
|
31
|
+
语义 inline role 提供结构身份;具体视觉效果取决于输出样式。本书使用 [.term]#inline role# 标记术语片段,使用 [.path]`parts/020-body-structure/` 标记路径片段。
|
|
@@ -7,6 +7,7 @@ v0.1, 2026-05
|
|
|
7
7
|
:sectnums:
|
|
8
8
|
:icons: font
|
|
9
9
|
:experimental:
|
|
10
|
+
:appendix-caption: 附录
|
|
10
11
|
:idprefix:
|
|
11
12
|
:idseparator: -
|
|
12
13
|
|
|
@@ -15,12 +16,12 @@ include::../../shared/attributes.adoc[]
|
|
|
15
16
|
[preface]
|
|
16
17
|
include::frontmatter/preface.adoc[]
|
|
17
18
|
|
|
18
|
-
include::chapters/
|
|
19
|
+
include::chapters/010-opening.adoc[]
|
|
19
20
|
|
|
20
|
-
include::chapters/
|
|
21
|
+
include::chapters/020-main-flow.adoc[]
|
|
21
22
|
|
|
22
23
|
[appendix]
|
|
23
|
-
include::backmatter/appendix-
|
|
24
|
+
include::backmatter/appendix-minimal-files.adoc[]
|
|
24
25
|
|
|
25
26
|
[bibliography]
|
|
26
27
|
include::backmatter/bibliography.adoc[]
|