create-asciidoc-multi-book-workspace 0.7.0 → 0.8.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 - 2026-10-31
4
+
5
+ ### Changed
6
+
7
+ - A link with no link text shows the name of its address. When the xref's address is the heading's title or one of its qualified addresses, the link shows it as written, its titles joined with an interpunct: `xref:红烧肉::火候[]` shows 红烧肉·火候 and `xref:家常菜::红烧肉::火候[]` shows all three titles, inside a book and across books. It used to show the last title alone (火候), so a book that referred to one of several 火候 wrote its address twice, once as the target and once as link text. An xref whose address is an ID still shows the heading's title, as Asciidoctor does for a section it resolves by ID. Link text the author writes is shown as written.
8
+ - The structured writing conventions sample writes a qualified address once (《xref:红烧肉::火候[]》) and writes link text only where it says something other than the address's name. Its addressing chapter, print conventions, decision trees, and quick reference say so.
9
+
10
+ ### Upgrading an existing workspace
11
+
12
+ Create a temporary workspace with `pnpm create asciidoc-multi-book-workspace@0.8.0 tmp-books` and copy its `tools/adoc-books.mjs` over yours. The `asciidoc-abundant-tree` range stays `^0.6.0`. Books need no change: link text written as the address's name, such as `xref:红烧肉::火候[红烧肉·火候]`, still shows the same, and can be removed.
13
+
3
14
  ## 0.7.0 - 2026-10-31
4
15
 
5
16
  ### Removed
@@ -570,12 +570,16 @@ function bindReferences(config, document) {
570
570
  const sections = new Map(projection.nodeIndex.entries()
571
571
  .filter((entry) => entry.kind === "section")
572
572
  .map((entry) => [entry.iri.value, entry]));
573
- const located = (iris) => [...iris].flatMap((iri) => {
573
+ // 地址的名字:选择器是这一节的标题或某个限定地址时,照写下的样子显示,各级标题之间用间隔号;是 ID(作者声明的或生成的)
574
+ // 时显示标题——ID 是给引用用的第二个地址,不是给读者看的名字。按图上是哪个标签判,不按字符串里有没有 :: 猜。
575
+ const located = (iris, selector) => [...iris].flatMap((iri) => {
574
576
  const entry = sections.get(iri);
575
577
  const id = entry?.node.ids?.[0];
576
578
  if (entry === undefined || id === undefined)
577
579
  return [];
578
- return [{ id, title: entry.node.title ?? id }];
580
+ const node = { termType: "iri", value: iri };
581
+ const named = values(node, "headline").includes(selector) || values(node, "addressPath").includes(selector);
582
+ return [{ id, shown: named ? selector.split("::").join("·") : entry.node.title ?? id }];
579
583
  });
580
584
  // 别的书指过来的引用在这本书的 label space 里找:图上四个标签谓词的值,按整串相等找拥有者。
581
585
  const ownersByLabel = new Map();
@@ -588,7 +592,7 @@ function bindReferences(config, document) {
588
592
  ownersByLabel.set(triple.object.value, iris);
589
593
  }
590
594
  }
591
- const owners = (selector) => located(ownersByLabel.get(selector) ?? []);
595
+ const owners = (selector) => located(ownersByLabel.get(selector) ?? [], selector);
592
596
  const headingsByFragment = new Map();
593
597
  const crossBook = [];
594
598
  // 指向整本书的 xref(没有片段)没有 targetSelector:没有可改写的片段。
@@ -600,7 +604,7 @@ function bindReferences(config, document) {
600
604
  crossBook.push({ document, fragment });
601
605
  continue;
602
606
  }
603
- const found = located(values(edge, "targetHeading"));
607
+ const found = located(values(edge, "targetHeading"), fragment);
604
608
  if (found.length !== 1)
605
609
  continue;
606
610
  headingsByFragment.set(fragment, found[0]);
@@ -635,14 +639,14 @@ async function bindCrossBookReferences(book, references, bindingsByInput, anchor
635
639
  if (anchors.has(fragment))
636
640
  continue;
637
641
  const htmlPath = `${documentName}.html`;
638
- const link = { href: `${htmlPath}#${heading.id}`, title: heading.title };
642
+ const link = { href: `${htmlPath}#${heading.id}`, shown: heading.shown };
639
643
  linksByHref.set(`${htmlPath}#${fragment}`, link);
640
644
  linksByHref.set(escapeHtmlAttribute(`${htmlPath}#${fragment}`), link);
641
645
  }
642
646
  return linksByHref;
643
647
  }
644
- // 在目标书里恰好对上一节、而目标书的 HTML 里没有这个片段的跨书链接,改成指向那一节的标题。Asciidoctor 对没写显示文字的跨书 xref
645
- // 显示文档路径;改写时换成标题。
648
+ // 在目标书里恰好对上一节、而目标书的 HTML 里没有这个片段的跨书链接,改成指向那一节的标题。Asciidoctor 对没有显示文字的跨书 xref
649
+ // 显示文档路径;改写时换成地址的名字。
646
650
  function rewriteCrossBookXrefs(html, linksByHref) {
647
651
  if (linksByHref.size === 0)
648
652
  return html;
@@ -650,12 +654,12 @@ function rewriteCrossBookXrefs(html, linksByHref) {
650
654
  const link = linksByHref.get(`${htmlPath}#${fragment}`) ?? linksByHref.get(`${htmlPath}#${decodeUrlFragment(fragment)}`);
651
655
  if (link === undefined)
652
656
  return whole;
653
- const shown = text === htmlPath ? escapeHtmlAttribute(link.title) : text;
657
+ const shown = text === htmlPath ? escapeHtmlAttribute(link.shown) : text;
654
658
  return `<a href="${escapeHtmlAttribute(link.href)}"${rest}>${shown}</a>`;
655
659
  });
656
660
  }
657
661
  // Asciidoctor 把它解析不了的书内 xref 写成 <a href="#选择器">[选择器]</a>;aat 的投影里恰好对上一节的,改成指向那一节的标题,
658
- // 显示标题。片段已经是 HTML 里某个元素的 id 时,保持 Asciidoctor 写下的样子。
662
+ // 显示地址的名字。片段已经是 HTML 里某个元素的 id 时,保持 Asciidoctor 写下的样子。
659
663
  function rewriteBoundXrefs(html, headingsByFragment) {
660
664
  if (headingsByFragment.size === 0)
661
665
  return html;
@@ -667,7 +671,7 @@ function rewriteBoundXrefs(html, headingsByFragment) {
667
671
  const heading = headingsByFragment.get(fragment) ?? headingsByFragment.get(decoded);
668
672
  if (heading === undefined)
669
673
  return whole;
670
- const shown = text === `[${fragment}]` ? escapeHtmlAttribute(heading.title) : text;
674
+ const shown = text === `[${fragment}]` ? escapeHtmlAttribute(heading.shown) : text;
671
675
  return `<a href="#${escapeHtmlAttribute(heading.id)}"${rest}>${shown}</a>`;
672
676
  });
673
677
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-asciidoc-multi-book-workspace",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Create a complete AsciiDoc multi-book workspace.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -91,8 +91,8 @@
91
91
  └─ 否:同一本书里经 include 拼起来的各个文件之间,xref 直接写标题。看第 2 步。
92
92
 
93
93
  2. 目标标题在它所在的书里与别的标题同名吗?
94
- ├─ 是:把上级标题由近及远用 `::` 接在前面,补到候选只剩一个,如 `红烧肉::火候`,
95
- │ 并写上显示文本,各级之间用间隔号,如 `[红烧肉·火候]`。
94
+ ├─ 是:把上级标题由近及远用 `::` 接在前面,补到候选只剩一个,如 `红烧肉::火候`。
95
+ │ 不必写显示文本:网页显示地址的名字,各级之间用间隔号,如《红烧肉·火候》。
96
96
  │ 补到书名之下最高的一级仍分不开(同一个上级下同名)时,先把标题改得说得清这一节讲什么;
97
97
  │ 改不了时,在那一节标题上方加 custom ID,xref 写这个 ID,
98
98
  │ 并写上能分清是哪一节的显示文本。看第 3 步。
@@ -34,8 +34,8 @@
34
34
  |`[.dish]`
35
35
  |role:这一节在书里是什么。
36
36
 
37
- |`pass:[xref:红烧肉::火候[红烧肉·火候]]`
38
- |限定地址:几个标题同名时,把上级标题用 `::` 接在前面,指明其中一个;显示文本用间隔号。
37
+ |`pass:[xref:红烧肉::火候[]]`
38
+ |限定地址:几个标题同名时,把上级标题用 `::` 接在前面,指明其中一个;网页显示《红烧肉·火候》,各级之间用间隔号。
39
39
 
40
40
  |`[#…]`
41
41
  |custom ID:标题当不了地址时(同一个上级下同名,或标题里有 `#`、`::`)给它的第二个地址。
@@ -74,7 +74,7 @@
74
74
  写引用时,应符合下列规定:
75
75
 
76
76
  * 同一本书里经 include 拼起来的各个文件之间,引用直接写标题;
77
- * 几个标题同名时,引用写限定地址,例如 `pass:[xref:红烧肉::火候[红烧肉·火候]]`;
77
+ * 几个标题同名时,引用写限定地址,例如 `pass:[xref:红烧肉::火候[]]`,不必再写显示文本;
78
78
  * 引用另一本书时,在书头定义一个文档属性,存那本书的入口路径(如 `:snack-book: ../02-home-snacks/book.adoc`),引用写成 `pass:[xref:{snack-book}#糖油饼[]]`,`#` 后面写那本书里的标题。
79
79
 
80
80
  反引号只改字体,反引号里的行内宏(如 xref)和属性引用照样会被替换。要原样展示它们,应把它们包在 `pass:[pass:[…]]` 里,外面再加反引号得到等宽字体。`= 书名`、`[.dish]` 这类块写法出现在行内时不会被当作结构,用反引号即可。
@@ -55,17 +55,14 @@ Asciidoctor 还会从标题生成一个 ID,生成时去掉标点,同名的
55
55
 
56
56
  [source,asciidoc]
57
57
  ----
58
- 糖色下锅以后怎样转火,见《xref:红烧肉::火候[红烧肉·火候]》。
58
+ 糖色下锅以后怎样转火,见《xref:红烧肉::火候[]》。
59
59
  ----
60
60
 
61
- `红烧肉::火候` 是这一节的限定地址。写限定地址时,应符合下列规定:
61
+ `红烧肉::火候` 是这一节的限定地址。上级标题由近及远逐级补上,补到候选只剩一个为止。
62
62
 
63
- * 上级标题由近及远逐级补上,补到候选只剩一个为止;
64
- * 写上显示文本,各级标题之间用间隔号,例如 `红烧肉·火候`(见《xref:篇名用书名号[]》)。
63
+ 限定地址只写一遍,不必再写显示文本。链接没有显示文本时,网页上显示地址的名字,各级标题之间用间隔号(见《xref:篇名用书名号[]》),上面这句读者看到的是“见《红烧肉·火候》”。作者补上的上级标题,读者也要靠它分清是哪道菜的火候,所以显示时一级不少。想在链接上说别的话时,才写显示文本,例如 `pass:[xref:红烧肉::火候[那一锅的火候]]`,网页显示写下的这句。
65
64
 
66
- 不写显示文本时,链接上只显示最后一级标题“火候”,读者看不出是哪道菜的。
67
-
68
- 补一级不够时,再往上补一级。例如菜谱分了《家常菜》《宴客菜》两部,两部里都有《红烧肉》,引用家常菜里那一道的火候时,写 `pass:[xref:家常菜::红烧肉::火候[家常菜·红烧肉·火候]]`。补到书名之下最高的一级(分部的书是部,不分部的书是章),就是这一节完整的路径;书名全书只有一个,不必写。
65
+ 补一级不够时,再往上补一级。例如菜谱分了《家常菜》《宴客菜》两部,两部里都有《红烧肉》,引用家常菜里那一道的火候时,写 `pass:[xref:家常菜::红烧肉::火候[]]`,网页显示《家常菜·红烧肉·火候》。补到书名之下最高的一级(分部的书是部,不分部的书是章),就是这一节完整的路径;书名全书只有一个,不必写。
69
66
 
70
67
  同一个上级下有两个同名的标题时,完整的路径也分不开它们,例如糖醋排骨要炸两遍,《糖醋排骨》下写了两节《炸》。两节同名,多半是标题没有说清这一节讲什么。这时宜先改标题,例如改成《初炸》与《复炸》,改好以后,两节的标题各是各的地址。
71
68
 
@@ -74,7 +71,7 @@ Asciidoctor 还会从标题生成一个 ID,生成时去掉标点,同名的
74
71
  * 名字说清这一节讲什么,不写序号和位置词,例如不写 `炸-2`、`第二个炸`,因为序号和位置会随编排变化;
75
72
  * 名字不与书里的任何标题相同。例如给《卤味》下讲糖色的一节加了 `[#炒糖色]`,书里又有一节《炒糖色》,这个地址就对上两节;Asciidoctor 先按 ID 去找,网页上 `pass:[xref:炒糖色[]]` 落到加了 ID 的那一节,原来指《炒糖色》的引用便不声不响地指到了别处;
76
73
  * 同一个上级下同名的几节,要引用哪一节,就给哪一节加;只给其中一节加时,另一节的标题和限定地址仍然有两个候选;
77
- * 引用写上显示文本,写出能让读者分清是哪一节的说法。
74
+ * 引用写上显示文本,写出能让读者分清是哪一节的说法:引用 custom ID 的链接显示的是标题,同名的几节显示得一样。
78
75
 
79
76
  custom ID 是同一个标题的第二个地址,不替代标题。作者是在要引用某一节时才发现标题当不了地址的,custom ID 也宜在那时再加,不必预先写下。
80
77
 
@@ -18,7 +18,7 @@ ____
18
18
  做法见《红烧肉》。
19
19
  ____
20
20
 
21
- 书名号标书名、卷名和篇名(4.15.3.1);题目是一部特定的作品时用书名号,只是谈论的话题时才用引号(B.1.8)。书里的一节是这本书里的一篇,提到它时应加书名号。几篇并列时,书名号应紧挨着写,中间不加顿号(A.13)。提到的一节带链接时,xref 应写在书名号里面,例如 `《pass:[xref:红烧肉[]]》`,读者看到的是《红烧肉》,链接落在书名号里的标题上。书名和其中的篇名连着写时,中间用间隔号,例如《随园食单·须知单》;限定地址的显示文本也这样写,例如《红烧肉·火候》。
21
+ 书名号标书名、卷名和篇名(4.15.3.1);题目是一部特定的作品时用书名号,只是谈论的话题时才用引号(B.1.8)。书里的一节是这本书里的一篇,提到它时应加书名号。几篇并列时,书名号应紧挨着写,中间不加顿号(A.13)。提到的一节带链接时,xref 应写在书名号里面,例如 `《pass:[xref:红烧肉[]]》`,读者看到的是《红烧肉》,链接落在书名号里的标题上。书名和其中的篇名连着写时,中间用间隔号,例如《随园食单·须知单》;链接没有显示文本时,网页把限定地址也这样显示,`《pass:[xref:红烧肉::火候[]]》` 读者看到的是《红烧肉·火候》。
22
22
 
23
23
  [.touchstone]
24
24
  === 中西文混排照规范