@segnavia/html 0.37.0 → 0.39.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.
@@ -0,0 +1,33 @@
1
+ import { element, linkChildren, text } from "./dom.js";
2
+ import { isTag } from "domhandler";
3
+ import { resolveDirection } from "@segnavia/format";
4
+ //#region src/book/book-contents.ts
5
+ function contentsList(book, headings, options) {
6
+ const entries = headings.map((heading) => {
7
+ const page = book.contents.pageNumbers ? options.pageNumbers?.[heading.id] ?? "" : "";
8
+ const level = heading.level + (heading.depth ?? 0);
9
+ const attributes = {
10
+ class: "segnavia-contents-entry",
11
+ "data-level": String(level),
12
+ ...heading.depth === void 0 ? {} : { "data-depth": String(heading.depth) }
13
+ };
14
+ return element("li", attributes, [element("a", {
15
+ href: options.crossLinks ? `segnavia:target/${heading.id}` : `#${heading.id}`,
16
+ dir: resolveDirection(book.dir, book.lang, heading.text)
17
+ }, [text(heading.text)]), book.contents.pageNumbers ? element("a", {
18
+ class: "segnavia-contents-page",
19
+ href: `#${heading.id}`,
20
+ "data-ref": "page"
21
+ }, [text(page)]) : element("span", { class: "segnavia-contents-page" }, [])]);
22
+ });
23
+ return element("ol", { class: "segnavia-contents-list" }, entries);
24
+ }
25
+ function addContents(book, section, headings, options) {
26
+ const title = section.info.title;
27
+ const titleNode = section.node.children.find((child) => isTag(child) && child.name === "h1");
28
+ if (title !== void 0 && titleNode && isTag(titleNode)) titleNode.attribs.dir = resolveDirection(book.dir, book.lang, title);
29
+ section.node.children.push(contentsList(book, headings, options));
30
+ linkChildren(section.node);
31
+ }
32
+ //#endregion
33
+ export { addContents };
@@ -0,0 +1,37 @@
1
+ import { elements } from "../sanitize.js";
2
+ import { element, text } from "./dom.js";
3
+ //#region src/book/book-document-nodes.ts
4
+ function targetLinks(sections) {
5
+ const ids = sections.flatMap((section) => [section.info.id, ...elements(section.node.children).flatMap((node) => node.attribs.id ? [node.attribs.id] : [])]);
6
+ return element("div", {
7
+ class: "segnavia-targets",
8
+ "aria-hidden": "true"
9
+ }, [...new Set(ids)].map((id) => element("a", {
10
+ href: `#${id}`,
11
+ tabindex: "-1"
12
+ }, [text("​")])));
13
+ }
14
+ function notesGalley(series, paragraphs) {
15
+ const notes = paragraphs.map((paragraph) => element("div", { "data-segnavia-note": paragraph.attribs.id ?? "" }, [paragraph]));
16
+ return element("aside", {
17
+ "data-segnavia-notes-galley": "",
18
+ "data-segnavia-note-series": series,
19
+ "aria-hidden": "true"
20
+ }, notes);
21
+ }
22
+ function notesGalleys(series, paragraphs) {
23
+ return series.flatMap((item) => {
24
+ const held = paragraphs.get(item.name);
25
+ return item.placement === "page" && held?.length ? [notesGalley(item.name, held)] : [];
26
+ });
27
+ }
28
+ function withBlankPages(sections, blankBefore) {
29
+ const counts = /* @__PURE__ */ new Map();
30
+ for (const id of blankBefore ?? []) counts.set(id, (counts.get(id) ?? 0) + 1);
31
+ return sections.flatMap((section) => [...Array.from({ length: counts.get(section.info.id) ?? 0 }, () => element("div", {
32
+ class: "segnavia-blank",
33
+ "aria-hidden": "true"
34
+ })), section.node]);
35
+ }
36
+ //#endregion
37
+ export { notesGalleys, targetLinks, withBlankPages };
@@ -1,4 +1,6 @@
1
1
  import { BookDocument, BookDocumentOptions } from "./types.js";
2
+ import "./book-held.js";
3
+ import "./notes.js";
2
4
  import { Book } from "@segnavia/format";
3
5
  //#region src/book/book-document.d.ts
4
6
  export declare function bookDocument(book: Book, options?: BookDocumentOptions): BookDocument;
@@ -1,29 +1,16 @@
1
1
  import { elements, parse, serialize } from "../sanitize.js";
2
- import { bookDocumentCss, bookFlowPageGeometry, markBlockPositions, resolveBookBuild } from "./book-flow.js";
3
2
  import { element, linkChildren, text } from "./dom.js";
4
- import { collectHeadings, designedSection, flowAttribs, markPlacementIssues, sectionNode, templateHolders, templateRegionNameIssues, templatedSection, undeclaredRegionIssues } from "./book-held.js";
5
- import { endnotesInOrder, noteState, transformNotes } from "./notes.js";
3
+ import { addContents } from "./book-contents.js";
4
+ import { notesGalleys, targetLinks, withBlankPages } from "./book-document-nodes.js";
5
+ import { bookDocumentCss, bookFlowPageGeometry, markBlockPositions, resolveBookBuild } from "./book-flow.js";
6
+ import { authoredSection, collectHeadings, designedSection, flowAttribs, markPlacementIssues, sectionNode, templateHolders, templateRegionIssues, templatedSection, templatedSectionIssues } from "./book-held.js";
7
+ import { pageReferenceNodes, pageReferenceTarget, pageReferenceTargets } from "./book-page-reference.js";
8
+ import { duplicateIdIssues, pageReferenceIssues, reservedIdIssues } from "./book-issues.js";
9
+ import { addPart, attachPendingPartHeadings, partNames } from "./book-part-build.js";
10
+ import { noteState, transformNotes } from "./notes.js";
6
11
  import { bookPrintPart } from "./types.js";
7
- import { isTag } from "domhandler";
8
- import { isBookContents, isDesignedSection, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
12
+ import { bookSections, isBookContents, isDesignedSection, isPart, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
9
13
  //#region src/book/book-document.ts
10
- function direction(book, value) {
11
- return resolveDirection(book.dir, book.lang, value);
12
- }
13
- function authoredSection(section, id, build) {
14
- const document = parse(section.html);
15
- const runningHead = section.title;
16
- const node = sectionNode("front", id, document.children, build, runningHead, flowAttribs(section.opens, section.parents));
17
- return {
18
- info: {
19
- id,
20
- kind: "front",
21
- title: section.title,
22
- runningHead
23
- },
24
- node
25
- };
26
- }
27
14
  function contentsSection(entry, build) {
28
15
  const id = "segnavia-contents";
29
16
  const title = entry.title;
@@ -35,13 +22,14 @@ function contentsSection(entry, build) {
35
22
  info: {
36
23
  id,
37
24
  kind: "contents",
25
+ division: "front",
38
26
  title,
39
27
  runningHead: title
40
28
  },
41
29
  node: sectionNode("contents", id, children, build, title, flowAttribs(entry.opens, entry.parents))
42
30
  };
43
31
  }
44
- function chapterSection(chapter, index, build) {
32
+ function chapterSection(chapter, index, division, build) {
45
33
  const id = `segnavia-chapter-${index}`;
46
34
  const document = parse(chapter.html);
47
35
  const runningHead = chapter.runningHead ?? chapter.title;
@@ -49,80 +37,40 @@ function chapterSection(chapter, index, build) {
49
37
  info: {
50
38
  id,
51
39
  kind: "chapter",
40
+ division,
52
41
  title: chapter.title,
53
42
  runningHead
54
43
  },
55
44
  node: sectionNode("chapter", id, document.children, build, runningHead, flowAttribs(chapter.opens, chapter.parents))
56
45
  };
57
46
  }
58
- function appendHeld(built, heldHeadings, extraIssues, book, notes, build, sections, headings, issues, notesOwner = 0) {
59
- sections.push(built);
60
- issues.push(...reservedIdIssues(built), ...columnIssues(built), ...extraIssues);
61
- headings.push(...heldHeadings);
62
- transformNotes(built.node, book, 0, notes, build === "galley", notesOwner);
47
+ function appendDesigned(entry, id, kind, book, build, sectionEntry) {
48
+ const held = designedSection(entry, id, kind, sectionEntry.division, build.build, book.contents.depth, build.build === "flow" ? bookFlowPageGeometry(book) : void 0);
49
+ build.sections.push(held.built);
50
+ decorateSection(sectionEntry, build);
51
+ build.issues.push(...reservedIdIssues(held.built), ...columnIssues(held.built));
52
+ build.headings.push(...withSectionDepth(held.headings, sectionEntry.depth));
53
+ const context = noteContext(sectionEntry, held.built.info.id, 0, entry.title, build);
54
+ transformNotes(held.built.node, book, context, build.notes, build.build === "galley");
63
55
  }
64
- function appendDesigned(entry, id, kind, book, build, notes, sections, headings, issues, notesOwner = 0) {
65
- const held = designedSection(entry, id, kind, build, book.contents.depth, build === "flow" ? bookFlowPageGeometry(book) : void 0);
66
- appendHeld(held.built, held.headings, [], book, notes, build, sections, headings, issues, notesOwner);
56
+ function appendTemplated(entry, id, kind, book, build, sectionEntry) {
57
+ const held = templatedSection(entry, id, kind, sectionEntry.division, build.build, book.contents.depth, build.templates, build.build === "flow" ? bookFlowPageGeometry(book) : void 0);
58
+ build.sections.push(held.built);
59
+ decorateSection(sectionEntry, build);
60
+ build.issues.push(...reservedIdIssues(held.built), ...columnIssues(held.built), ...templatedSectionIssues(entry, id, build.templates));
61
+ build.headings.push(...withSectionDepth(held.headings, sectionEntry.depth));
62
+ const context = noteContext(sectionEntry, held.built.info.id, 0, entry.title, build);
63
+ transformNotes(held.built.node, book, context, build.notes, build.build === "galley");
67
64
  }
68
- function appendTemplated(entry, id, kind, book, build, notes, templates, sections, headings, issues, notesOwner = 0) {
69
- const held = templatedSection(entry, id, kind, build, book.contents.depth, templates, build === "flow" ? bookFlowPageGeometry(book) : void 0);
70
- appendHeld(held.built, held.headings, undeclaredRegionIssues(entry, id, templates), book, notes, build, sections, headings, issues, notesOwner);
65
+ function withSectionDepth(headings, depth) {
66
+ return depth > 0 ? headings.map((heading) => ({
67
+ ...heading,
68
+ depth
69
+ })) : headings;
71
70
  }
72
71
  function chapterHeadings(section, chapter, depth) {
73
72
  return collectHeadings(section.node.children, section.info.id, depth, 2, (n) => `segnavia-h-${chapter}-${n}`, { n: 0 });
74
73
  }
75
- function contentsList(book, headings, options) {
76
- const entries = headings.map((heading) => {
77
- const page = book.contents.pageNumbers ? options.pageNumbers?.[heading.id] ?? "" : "";
78
- return element("li", {
79
- class: "segnavia-contents-entry",
80
- "data-level": String(heading.level)
81
- }, [element("a", {
82
- href: options.crossLinks ? `segnavia:target/${heading.id}` : `#${heading.id}`,
83
- dir: direction(book, heading.text)
84
- }, [text(heading.text)]), element("span", {
85
- class: "segnavia-contents-page",
86
- "data-target": heading.id,
87
- dir: direction(book, page)
88
- }, [text(page)])]);
89
- });
90
- return element("ol", { class: "segnavia-contents-list" }, entries);
91
- }
92
- function addContents(book, section, headings, options) {
93
- const title = section.info.title;
94
- const titleNode = section.node.children.find((child) => isTag(child) && child.name === "h1");
95
- if (title !== void 0 && titleNode && isTag(titleNode)) titleNode.attribs.dir = direction(book, title);
96
- section.node.children.push(contentsList(book, headings, options));
97
- linkChildren(section.node);
98
- }
99
- function targetLinks(sections) {
100
- const ids = sections.flatMap((section) => [section.info.id, ...elements(section.node.children).flatMap((node) => node.attribs.id ? [node.attribs.id] : [])]);
101
- return element("div", {
102
- class: "segnavia-targets",
103
- "aria-hidden": "true"
104
- }, [...new Set(ids)].map((id) => element("a", { href: `#${id}` }, [text("​")])));
105
- }
106
- function duplicateIdIssues(sections, headings) {
107
- const listed = new Set(headings.map((heading) => heading.id));
108
- const occurrences = /* @__PURE__ */ new Map();
109
- for (const section of sections) {
110
- const ids = [section.info.id, ...elements(section.node.children).flatMap((node) => node.attribs.id ? [node.attribs.id] : [])];
111
- for (const id of ids) occurrences.set(id, [...occurrences.get(id) ?? [], section.info]);
112
- }
113
- return [...occurrences].flatMap(([id, owners]) => {
114
- if (!listed.has(id) || owners.length < 2) return [];
115
- const named = owners.slice(0, 2).map((owner) => `${owner.title ?? owner.id} (${owner.id})`);
116
- return [`destination id ${id} is used in both ${named[0]} and ${named[1]}; listed heading and generated destination ids must be unique`];
117
- });
118
- }
119
- function reservedIdIssues(section) {
120
- return elements(section.node.children).flatMap((node) => {
121
- const id = node.attribs.id;
122
- if (!id?.startsWith("segnavia-")) return [];
123
- return [`authored id ${id} in ${section.info.title ?? section.info.id} (${section.info.id}) uses the reserved segnavia- prefix`];
124
- });
125
- }
126
74
  function columnsValueOk(value) {
127
75
  if (!/^[0-9]+$/.test(value)) return false;
128
76
  const count = Number(value);
@@ -136,112 +84,171 @@ function columnIssues(section) {
136
84
  return [`The ${node.name} in ${where} could not establish data-columns ${JSON.stringify(value)}; set it to an integer from 2 to 4 to clear this`];
137
85
  });
138
86
  }
139
- function endnotesHeading(book) {
140
- const titleValue = book.endnotes.title;
87
+ function notesHeading(book, notes) {
141
88
  return element("h1", {
142
- class: "segnavia-endnotes-title",
143
- dir: direction(book, titleValue)
144
- }, [text(titleValue)]);
89
+ class: notes.series.titleClass,
90
+ dir: resolveDirection(book.dir, book.lang, notes.series.title)
91
+ }, [text(notes.series.title)]);
145
92
  }
146
- function endnotesSection(book, paragraphs, build, id, runningHead, opens) {
93
+ function notesSection(book, notes, build, id, runningHead, parts, opens) {
94
+ const scope = notes.scopeTitle !== void 0 && (notes.series.placement === "chapter" || notes.series.placement === "part") ? [element("p", {
95
+ class: "segnavia-notes-scope",
96
+ dir: resolveDirection(book.dir, book.lang, notes.scopeTitle)
97
+ }, [text(notes.scopeTitle)])] : [];
147
98
  return {
148
99
  info: {
149
100
  id,
150
101
  kind: "endnotes",
151
- title: book.endnotes.title,
102
+ division: notes.targetId === "book" ? "body" : notes.division,
103
+ title: notes.series.title,
152
104
  runningHead
153
105
  },
154
- node: sectionNode("endnotes", id, [endnotesHeading(book), ...paragraphs], build, runningHead, opens ? flowAttribs(opens) : {})
106
+ node: sectionNode("endnotes", id, [
107
+ notesHeading(book, notes),
108
+ ...scope,
109
+ ...notes.paragraphs
110
+ ], build, runningHead, {
111
+ ...opens ? flowAttribs(opens) : {},
112
+ ...parts ? { "data-segnavia-parts": JSON.stringify(parts) } : {}
113
+ })
155
114
  };
156
115
  }
157
- function notesGalley(paragraphs) {
158
- const notes = paragraphs.map((paragraph) => element("div", { "data-segnavia-note": paragraph.attribs.id ?? "" }, [paragraph]));
159
- return element("aside", {
160
- "data-segnavia-notes-galley": "",
161
- "aria-hidden": "true"
162
- }, notes);
163
- }
164
- function withBlankPages(sections, blankBefore) {
165
- const counts = /* @__PURE__ */ new Map();
166
- for (const id of blankBefore ?? []) counts.set(id, (counts.get(id) ?? 0) + 1);
167
- return sections.flatMap((section) => [...Array.from({ length: counts.get(section.info.id) ?? 0 }, () => element("div", {
168
- class: "segnavia-blank",
169
- "aria-hidden": "true"
170
- })), section.node]);
171
- }
172
- function takeHeldNotesOwner(build) {
173
- build.heldNotesOwner -= 1;
174
- return build.heldNotesOwner;
116
+ function decorateSection(sectionEntry, build) {
117
+ const section = build.sections.at(-1);
118
+ if (!section) return;
119
+ if (sectionEntry.ancestry.length > 0) section.node.attribs["data-segnavia-parts"] = JSON.stringify(partNames(sectionEntry.ancestry));
120
+ attachPendingPartHeadings(build, sectionEntry, section.info.id);
175
121
  }
176
- function pushChapterEndnotes(book, build, id, owner) {
177
- if (book.endnotes.placement !== "chapter") return;
178
- const paragraphs = build.notes.chapterEndnotes.get(owner) ?? [];
179
- if (paragraphs.length === 0) return;
180
- const runningHead = build.sections.at(-1)?.info.runningHead;
181
- build.sections.push(endnotesSection(book, paragraphs, build.build, id, runningHead, "any"));
122
+ function noteContext(sectionEntry, targetId, chapter, title, build) {
123
+ const ancestry = sectionEntry.ancestry.at(-1);
124
+ for (const part of sectionEntry.ancestry) build.partTargets.set(part.id, targetId);
125
+ return {
126
+ chapter,
127
+ title,
128
+ division: sectionEntry.division,
129
+ targetId,
130
+ ...chapter === 0 && sectionEntry.division !== "front" ? { placementKey: targetId.replace(/^segnavia-/, "") } : {},
131
+ ...ancestry ? { part: {
132
+ id: ancestry.id,
133
+ title: ancestry.title
134
+ } } : {}
135
+ };
182
136
  }
183
137
  function addFrontMatter(book, build) {
184
138
  let contents;
185
139
  let frontNumber = 0;
186
- for (const entry of book.frontMatter) {
140
+ for (const sectionEntry of bookSections(book)) {
141
+ if (sectionEntry.division !== "front") continue;
142
+ const { entry } = sectionEntry;
143
+ if (isPart(entry)) {
144
+ addPart(sectionEntry, build);
145
+ continue;
146
+ }
187
147
  if (isBookContents(entry)) {
188
148
  contents = contentsSection(entry, build.build);
189
149
  build.sections.push(contents);
150
+ decorateSection(sectionEntry, build);
190
151
  continue;
191
152
  }
192
153
  if (isDesignedSection(entry)) {
193
154
  build.designedNumber += 1;
194
- appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build.build, build.notes, build.sections, build.headings, build.issues);
155
+ appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build, sectionEntry);
195
156
  continue;
196
157
  }
197
158
  if (isTemplatedSection(entry)) {
198
159
  build.templatedNumber += 1;
199
- appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build.build, build.notes, build.templates, build.sections, build.headings, build.issues);
160
+ appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build, sectionEntry);
200
161
  continue;
201
162
  }
163
+ if (!("html" in entry)) continue;
202
164
  frontNumber += 1;
203
165
  const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.build);
204
166
  build.sections.push(section);
167
+ decorateSection(sectionEntry, build);
205
168
  build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
206
- if (entry.contents && entry.title !== void 0) build.headings.push({
169
+ if ("contents" in entry && entry.contents && entry.title !== void 0) build.headings.push(...withSectionDepth([{
207
170
  id: section.info.id,
208
171
  level: 1,
209
172
  text: entry.title,
210
173
  sectionId: section.info.id
211
- });
212
- transformNotes(section.node, book, 0, build.notes, build.build === "galley");
174
+ }], sectionEntry.depth));
175
+ transformNotes(section.node, book, noteContext(sectionEntry, section.info.id, 0, entry.title, build), build.notes, build.build === "galley");
213
176
  }
214
177
  return contents;
215
178
  }
216
- function addChapters(book, build) {
217
- let chapterNumber = 0;
218
- for (const entry of book.chapters) {
179
+ function addChapters(book, build, division = "body") {
180
+ for (const sectionEntry of bookSections(book)) {
181
+ if (sectionEntry.division !== division) continue;
182
+ const { entry } = sectionEntry;
183
+ if (isPart(entry)) {
184
+ addPart(sectionEntry, build);
185
+ continue;
186
+ }
187
+ if (isBookContents(entry)) continue;
219
188
  if (isDesignedSection(entry)) {
220
189
  build.designedNumber += 1;
221
- const owner = takeHeldNotesOwner(build);
222
- appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build.build, build.notes, build.sections, build.headings, build.issues, owner);
223
- pushChapterEndnotes(book, build, `segnavia-endnotes-designed-${build.designedNumber}`, owner);
190
+ appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build, sectionEntry);
224
191
  continue;
225
192
  }
226
193
  if (isTemplatedSection(entry)) {
227
194
  build.templatedNumber += 1;
228
- const owner = takeHeldNotesOwner(build);
229
- appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build.build, build.notes, build.templates, build.sections, build.headings, build.issues, owner);
230
- pushChapterEndnotes(book, build, `segnavia-endnotes-templated-${build.templatedNumber}`, owner);
195
+ appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build, sectionEntry);
231
196
  continue;
232
197
  }
233
- chapterNumber += 1;
234
- const section = chapterSection(entry, chapterNumber, build.build);
198
+ if (!("html" in entry)) continue;
199
+ build.chapterNumber += 1;
200
+ const section = chapterSection(entry, build.chapterNumber, sectionEntry.division, build.build);
235
201
  build.sections.push(section);
202
+ decorateSection(sectionEntry, build);
236
203
  build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
237
- build.headings.push({
204
+ if (entry.title !== void 0) build.headings.push(...withSectionDepth([{
238
205
  id: section.info.id,
239
206
  level: 1,
240
207
  text: entry.title,
241
208
  sectionId: section.info.id
242
- }, ...chapterHeadings(section, chapterNumber, book.contents.depth));
243
- transformNotes(section.node, book, chapterNumber, build.notes, build.build === "galley");
244
- pushChapterEndnotes(book, build, `segnavia-endnotes-${chapterNumber}`, chapterNumber);
209
+ }], sectionEntry.depth));
210
+ build.headings.push(...withSectionDepth(chapterHeadings(section, build.chapterNumber, book.contents.depth), sectionEntry.depth));
211
+ transformNotes(section.node, book, noteContext(sectionEntry, section.info.id, build.chapterNumber, entry.title, build), build.notes, build.build === "galley");
212
+ }
213
+ }
214
+ function collectedSectionId(notes) {
215
+ const prefix = notes.series.sectionIdPrefix;
216
+ if (notes.targetId === "book") return prefix;
217
+ if (notes.series.placement === "part") return `${prefix}-${notes.placementKey.replace(/^segnavia-/, "")}`;
218
+ if (notes.series.placement === "chapter" && notes.targetId.startsWith("segnavia-chapter-")) return `${prefix}-${notes.targetId.slice(17)}`;
219
+ return `${prefix}-${notes.targetId.replace(/^segnavia-/, "")}`;
220
+ }
221
+ function insertCollectedSections(book, build) {
222
+ const order = new Map(book.notes.map((series, index) => [series.name, index]));
223
+ const collected = [...build.notes.collected.values()].sort((left, right) => (order.get(left.series.name) ?? 0) - (order.get(right.series.name) ?? 0));
224
+ for (const notes of [...collected].reverse()) {
225
+ if (notes.targetId === "book") continue;
226
+ const targetId = notes.series.placement === "part" ? build.partTargets.get(notes.placementKey) ?? notes.targetId : notes.targetId;
227
+ notes.targetId = targetId;
228
+ const index = build.sections.findIndex(({ info }) => info.id === targetId);
229
+ if (index < 0) {
230
+ build.issues.push(`note series ${notes.series.name} could not find placement target ${targetId}; keep the owning section in the book`);
231
+ continue;
232
+ }
233
+ const owner = build.sections[index];
234
+ const parts = owner?.node.attribs["data-segnavia-parts"];
235
+ build.sections.splice(index + 1, 0, notesSection(book, notes, build.build, collectedSectionId(notes), owner?.info.runningHead, parts ? JSON.parse(parts) : void 0, "any"));
236
+ }
237
+ for (const notes of collected) {
238
+ if (notes.targetId !== "book") continue;
239
+ const index = build.sections.findIndex(({ info }) => info.division === "end");
240
+ build.sections.splice(index < 0 ? build.sections.length : index, 0, notesSection(book, notes, build.build, collectedSectionId(notes), notes.series.title));
241
+ }
242
+ }
243
+ function writePageReferenceNumbers(book, roots, options) {
244
+ for (const node of pageReferenceNodes(roots)) {
245
+ const id = pageReferenceTarget(node.attribs);
246
+ if (!id) continue;
247
+ const page = options.pageNumbers?.[id] ?? "";
248
+ node.children = [text(page)];
249
+ node.attribs.dir = resolveDirection(book.dir, book.lang, page);
250
+ if (options.crossLinks) node.attribs.href = `segnavia:target/${id}`;
251
+ linkChildren(node);
245
252
  }
246
253
  }
247
254
  function bookDocument(book, options = {}) {
@@ -255,24 +262,40 @@ function bookDocument(book, options = {}) {
255
262
  templates: new Map(book.templates.map((template) => [template.name, template])),
256
263
  designedNumber: 0,
257
264
  templatedNumber: 0,
258
- heldNotesOwner: 0
265
+ chapterNumber: 0,
266
+ partTargets: /* @__PURE__ */ new Map(),
267
+ pendingPartHeadings: []
259
268
  };
260
269
  const contents = addFrontMatter(book, build);
261
270
  addChapters(book, build);
271
+ addChapters(book, build, "end");
262
272
  const { notes, sections, headings, issues } = build;
263
- if (book.endnotes.placement === "chapter") {
264
- const leftover = notes.chapterEndnotes.get(0) ?? [];
265
- if (leftover.length > 0) sections.push(endnotesSection(book, leftover, requestedBuild, "segnavia-endnotes", book.endnotes.title));
266
- } else if (notes.endnotes) sections.push(endnotesSection(book, endnotesInOrder(notes), requestedBuild, "segnavia-endnotes", book.endnotes.title));
273
+ insertCollectedSections(book, build);
267
274
  if (contents) addContents(book, contents, headings, options);
275
+ const noteGalleys = notesGalleys(book.notes, notes.pageParagraphs);
276
+ const scopes = [...sections.map((section) => ({
277
+ owner: `${section.info.title ?? section.info.id} (${section.info.id})`,
278
+ roots: [section.node]
279
+ })), ...noteGalleys.map((galley) => ({
280
+ owner: `page-placed note series ${galley.attribs["data-segnavia-note-series"] ?? ""}`,
281
+ roots: [galley]
282
+ }))];
283
+ const referenceIssues = pageReferenceIssues(sections, scopes);
284
+ const referenceRoots = scopes.flatMap((scope) => scope.roots);
285
+ const referenceTargets = pageReferenceTargets(referenceRoots);
286
+ writePageReferenceNumbers(book, referenceRoots, options);
268
287
  if (requestedBuild !== "print") markBlockPositions(sections);
269
- const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.kind) === options.part);
288
+ const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.division) === options.part);
270
289
  const document = parse("");
271
- document.children = requestedBuild === "galley" ? [element("style", {}, [text(bookDocumentCss(book, requestedBuild))]), element("div", { "data-segnavia-galleys": "" }, [
272
- ...templateHolders(book.templates),
273
- ...visible.map((section) => section.node),
274
- ...notes.footnoteParagraphs.length > 0 ? [notesGalley(notes.footnoteParagraphs)] : []
275
- ])] : [
290
+ document.children = requestedBuild === "galley" ? [
291
+ element("style", {}, [text(bookDocumentCss(book, requestedBuild))]),
292
+ ...options.targets ? [targetLinks(visible)] : [],
293
+ element("div", { "data-segnavia-galleys": "" }, [
294
+ ...templateHolders(book.templates),
295
+ ...visible.map((section) => section.node),
296
+ ...noteGalleys
297
+ ])
298
+ ] : [
276
299
  element("style", {}, [text(bookDocumentCss(book, requestedBuild))]),
277
300
  ...options.targets ? [targetLinks(visible)] : [],
278
301
  ...withBlankPages(visible, options.blankBefore)
@@ -282,16 +305,16 @@ function bookDocument(book, options = {}) {
282
305
  html: serialize(document),
283
306
  sections: sections.map((section) => section.info),
284
307
  headings,
308
+ pageReferenceTargets: referenceTargets,
285
309
  issues: [
286
310
  ...buildIssue ? [buildIssue] : [],
287
- ...noteMarkerIssues(book, {
288
- footnotes: notes.footnotes,
289
- endnotes: notes.endnotes
290
- }),
311
+ ...noteMarkerIssues(book.notes, notes.used),
312
+ ...notes.issues,
291
313
  ...markPlacementIssues(book.parentPages),
292
- ...templateRegionNameIssues(book.templates),
314
+ ...templateRegionIssues(book.templates),
293
315
  ...issues,
294
- ...duplicateIdIssues(sections, headings)
316
+ ...duplicateIdIssues(sections, headings),
317
+ ...referenceIssues
295
318
  ]
296
319
  };
297
320
  }
@@ -1,12 +1,24 @@
1
1
  import { isTag } from "domhandler";
2
- import { PX_PER_MM, sheetMm } from "@segnavia/format";
2
+ import { MAX_PART_DEPTH, PX_PER_MM, bookSections, isPart, sheetMm } from "@segnavia/format";
3
3
  //#region src/book/book-flow.ts
4
4
  const BOOK_FLOW_COLUMN_MIN_PX = 240;
5
5
  const BOOK_COLUMN_MIN = 2;
6
6
  const BOOK_COLUMN_MAX = 4;
7
- const CONTENTS_CSS = ":where(.segnavia-contents-list){list-style:none;padding:0;margin:0}:where(.segnavia-contents-entry){display:flex}:where(.segnavia-contents-page){margin-inline-start:auto}:where(.segnavia-contents-entry[data-level=\"2\"]){padding-inline-start:1em}:where(.segnavia-contents-entry[data-level=\"3\"]){padding-inline-start:2em}:where(.segnavia-contents-entry[data-level=\"4\"]){padding-inline-start:3em}:where(.segnavia-contents-entry[data-level=\"5\"]){padding-inline-start:4em}:where(.segnavia-contents-entry[data-level=\"6\"]){padding-inline-start:5em}";
8
- const PRINT_CSS = `${CONTENTS_CSS}@media print{:where(.segnavia-front,.segnavia-contents)~:where(.segnavia-front,.segnavia-contents),:where(.segnavia-chapter,.segnavia-endnotes,.segnavia-blank){break-before:page}:where(.segnavia-blank){break-after:page}:where(.segnavia-targets){position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}}`;
9
- const GALLEY_CSS = `${CONTENTS_CSS}:where([data-segnavia-galleys]){position:relative}:where([data-segnavia-flow]){orphans:inherit;widows:inherit}:where([data-segnavia-kind="fixed"],[data-segnavia-kind="templated"],[data-segnavia-template]){display:none}:where([data-segnavia-notes-galley]){border-block-start:1px solid;padding-block-start:8px}:where([data-segnavia-note]){break-inside:avoid}:where([data-segnavia-galleys] img,[data-segnavia-window] img,[data-segnavia-page-note] img){display:block;max-block-size:var(--segnavia-text-height);max-inline-size:100%;block-size:auto;inline-size:auto}`;
7
+ const CONTENTS_INDENTS = Array.from({ length: 5 }, (_, index) => index + 2).map((level) => `:where(.segnavia-contents-entry[data-level="${level}"]){padding-inline-start:${level - 1}em}`).join("");
8
+ const DEEP_CONTENTS_INDENTS = Array.from({ length: MAX_PART_DEPTH }, (_, index) => index + 7).map((level) => `:where(.segnavia-contents-entry[data-level="${level}"]){padding-inline-start:${level - 1}em}`).join("");
9
+ const CONTENTS_PREFIX = `:where(.segnavia-contents-list){list-style:none;padding:0;margin:0}:where(.segnavia-contents-entry){display:flex}:where(.segnavia-contents-page){margin-inline-start:auto}:where([data-ref="page"]){unicode-bidi:isolate}${CONTENTS_INDENTS}`;
10
+ const CONTENTS_SUFFIX = ":where(.segnavia-targets){position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}";
11
+ const GALLEY_SUFFIX = ":where([data-segnavia-galleys]){position:relative}:where([data-segnavia-flow]){orphans:inherit;widows:inherit}:where([data-segnavia-kind=\"fixed\"],[data-segnavia-kind=\"templated\"],[data-segnavia-template]){display:none}:where([data-segnavia-notes-galley]){border-block-start:1px solid;padding-block-start:8px}:where([data-segnavia-note]){break-inside:avoid}:where([data-segnavia-galleys] img,[data-segnavia-window] img,[data-segnavia-page-note] img){display:block;max-block-size:var(--segnavia-text-height);max-inline-size:100%;block-size:auto;inline-size:auto}";
12
+ function hasParts(book) {
13
+ return bookSections(book).some(({ entry }) => isPart(entry));
14
+ }
15
+ function contentsCss(book) {
16
+ return `${CONTENTS_PREFIX}${hasParts(book) ? DEEP_CONTENTS_INDENTS : ""}${CONTENTS_SUFFIX}`;
17
+ }
18
+ function printCss(book) {
19
+ const breaks = hasParts(book) ? ".segnavia-part,.segnavia-chapter,.segnavia-endnotes,.segnavia-blank" : ".segnavia-chapter,.segnavia-endnotes,.segnavia-blank";
20
+ return `${contentsCss(book)}@media print{:where(.segnavia-front,.segnavia-contents)~:where(.segnavia-front,.segnavia-contents),:where(${breaks}){break-before:page}:where(.segnavia-blank){break-after:page}}`;
21
+ }
10
22
  function bookFlowPageGeometry(book) {
11
23
  if (!book.page) return void 0;
12
24
  const paper = sheetMm(book.page);
@@ -28,7 +40,7 @@ function columnFallbacks(gap) {
28
40
  function flowCss(book) {
29
41
  const gap = (book.page?.columnGap ?? 0) * PX_PER_MM;
30
42
  const columns = Array.from({ length: 3 }, (_, index) => index + 2).map((count) => `:where([data-columns="${count}"]){column-count:${count}}`).join("");
31
- return `${CONTENTS_CSS}:where([data-segnavia-flow]){container-type:inline-size}:where([data-columns]){column-gap:${gap}px}${columns}:where([data-segnavia-flow-page]){inline-size:100%;container-type:inline-size;margin-inline:0}:where([data-segnavia-flow-page-size]){position:relative;inline-size:100cqi;block-size:calc(100cqi * var(--segnavia-paper-ratio));overflow:hidden}:where([data-segnavia-flow-paper]){position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:var(--segnavia-paper-inline);block-size:var(--segnavia-paper-block);overflow:hidden;zoom:var(--segnavia-flow-scale,calc(100cqi / var(--segnavia-paper-inline)))}:where([data-segnavia-flow-text]){position:absolute;inset-block-start:var(--segnavia-text-block-start);inset-inline-start:var(--segnavia-text-inline-start);inline-size:var(--segnavia-text-inline);block-size:var(--segnavia-text-block);container-type:inline-size}${columnFallbacks(gap)}`;
43
+ return `${contentsCss(book)}:where([data-segnavia-flow]){container-type:inline-size}:where([data-columns]){column-gap:${gap}px}${columns}:where([data-segnavia-flow-page]){inline-size:100%;container-type:inline-size;margin-inline:0}:where([data-segnavia-flow-page-size]){position:relative;inline-size:100cqi;block-size:calc(100cqi * var(--segnavia-paper-ratio));overflow:hidden}:where([data-segnavia-flow-paper]){position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:var(--segnavia-paper-inline);block-size:var(--segnavia-paper-block);overflow:hidden;zoom:var(--segnavia-flow-scale,calc(100cqi / var(--segnavia-paper-inline)))}:where([data-segnavia-flow-text]){position:absolute;inset-block-start:var(--segnavia-text-block-start);inset-inline-start:var(--segnavia-text-inline-start);inline-size:var(--segnavia-text-inline);block-size:var(--segnavia-text-block);container-type:inline-size}${columnFallbacks(gap)}`;
32
44
  }
33
45
  function resolveBookBuild(book, requested) {
34
46
  const build = requested ?? (book.mode === "paginated" ? "galley" : "print");
@@ -39,8 +51,8 @@ function resolveBookBuild(book, requested) {
39
51
  };
40
52
  }
41
53
  function bookDocumentCss(book, build) {
42
- if (build === "galley") return GALLEY_CSS;
43
- return build === "flow" ? flowCss(book) : PRINT_CSS;
54
+ if (build === "galley") return `${contentsCss(book)}${GALLEY_SUFFIX}`;
55
+ return build === "flow" ? flowCss(book) : printCss(book);
44
56
  }
45
57
  function flowPageStyle(geometry, recto) {
46
58
  const marginInlineStart = recto ? geometry.marginInlineStartRecto : geometry.marginInlineStartVerso;
@@ -61,12 +73,16 @@ function parseBookPositionKey(value) {
61
73
  function isFootnoteBlock(node) {
62
74
  return (node.attribs.class ?? "").split(/\s+/).includes("segnavia-footnotes");
63
75
  }
76
+ function isNotesScope(node) {
77
+ return (node.attribs.class ?? "").split(/\s+/).includes("segnavia-notes-scope");
78
+ }
64
79
  function markBlockPositions(sections) {
65
80
  for (const section of sections) {
66
81
  let position = 0;
67
82
  for (const child of section.node.children) {
68
83
  if (!isTag(child)) continue;
69
84
  if (child.name === "style" || child.name === "script") continue;
85
+ if (isNotesScope(child)) continue;
70
86
  if (isFootnoteBlock(child)) {
71
87
  child.attribs["data-segnavia-position"] = bookBlockPosition(section.info.id, position, "footnotes");
72
88
  continue;
@@ -0,0 +1,4 @@
1
+ import "./book-flow.js";
2
+ import "./dom.js";
3
+ import { Element } from "domhandler";
4
+ import { Book, BookSectionParents } from "@segnavia/format";