@segnavia/html 0.37.0 → 0.38.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)]), element("span", {
18
+ class: "segnavia-contents-page",
19
+ "data-target": heading.id,
20
+ dir: resolveDirection(book.dir, book.lang, page)
21
+ }, [text(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,30 @@
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(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
+ "aria-hidden": "true"
19
+ }, notes);
20
+ }
21
+ function withBlankPages(sections, blankBefore) {
22
+ const counts = /* @__PURE__ */ new Map();
23
+ for (const id of blankBefore ?? []) counts.set(id, (counts.get(id) ?? 0) + 1);
24
+ return sections.flatMap((section) => [...Array.from({ length: counts.get(section.info.id) ?? 0 }, () => element("div", {
25
+ class: "segnavia-blank",
26
+ "aria-hidden": "true"
27
+ })), section.node]);
28
+ }
29
+ //#endregion
30
+ export { notesGalley, 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,15 @@
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 { notesGalley, 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 { duplicateIdIssues, reservedIdIssues } from "./book-issues.js";
8
+ import { addPart, attachPendingPartHeadings, partNames } from "./book-part-build.js";
9
+ import { noteState, transformNotes } from "./notes.js";
6
10
  import { bookPrintPart } from "./types.js";
7
- import { isTag } from "domhandler";
8
- import { isBookContents, isDesignedSection, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
11
+ import { bookSections, isBookContents, isDesignedSection, isPart, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
9
12
  //#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
13
  function contentsSection(entry, build) {
28
14
  const id = "segnavia-contents";
29
15
  const title = entry.title;
@@ -35,13 +21,14 @@ function contentsSection(entry, build) {
35
21
  info: {
36
22
  id,
37
23
  kind: "contents",
24
+ division: "front",
38
25
  title,
39
26
  runningHead: title
40
27
  },
41
28
  node: sectionNode("contents", id, children, build, title, flowAttribs(entry.opens, entry.parents))
42
29
  };
43
30
  }
44
- function chapterSection(chapter, index, build) {
31
+ function chapterSection(chapter, index, division, build) {
45
32
  const id = `segnavia-chapter-${index}`;
46
33
  const document = parse(chapter.html);
47
34
  const runningHead = chapter.runningHead ?? chapter.title;
@@ -49,80 +36,40 @@ function chapterSection(chapter, index, build) {
49
36
  info: {
50
37
  id,
51
38
  kind: "chapter",
39
+ division,
52
40
  title: chapter.title,
53
41
  runningHead
54
42
  },
55
43
  node: sectionNode("chapter", id, document.children, build, runningHead, flowAttribs(chapter.opens, chapter.parents))
56
44
  };
57
45
  }
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);
46
+ function appendDesigned(entry, id, kind, book, build, sectionEntry) {
47
+ const held = designedSection(entry, id, kind, sectionEntry.division, build.build, book.contents.depth, build.build === "flow" ? bookFlowPageGeometry(book) : void 0);
48
+ build.sections.push(held.built);
49
+ decorateSection(sectionEntry, build);
50
+ build.issues.push(...reservedIdIssues(held.built), ...columnIssues(held.built));
51
+ build.headings.push(...withSectionDepth(held.headings, sectionEntry.depth));
52
+ const context = noteContext(sectionEntry, held.built.info.id, 0, entry.title, build);
53
+ transformNotes(held.built.node, book, context, build.notes, build.build === "galley");
63
54
  }
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);
55
+ function appendTemplated(entry, id, kind, book, build, sectionEntry) {
56
+ const held = templatedSection(entry, id, kind, sectionEntry.division, build.build, book.contents.depth, build.templates, build.build === "flow" ? bookFlowPageGeometry(book) : void 0);
57
+ build.sections.push(held.built);
58
+ decorateSection(sectionEntry, build);
59
+ build.issues.push(...reservedIdIssues(held.built), ...columnIssues(held.built), ...templatedSectionIssues(entry, id, build.templates));
60
+ build.headings.push(...withSectionDepth(held.headings, sectionEntry.depth));
61
+ const context = noteContext(sectionEntry, held.built.info.id, 0, entry.title, build);
62
+ transformNotes(held.built.node, book, context, build.notes, build.build === "galley");
67
63
  }
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);
64
+ function withSectionDepth(headings, depth) {
65
+ return depth > 0 ? headings.map((heading) => ({
66
+ ...heading,
67
+ depth
68
+ })) : headings;
71
69
  }
72
70
  function chapterHeadings(section, chapter, depth) {
73
71
  return collectHeadings(section.node.children, section.info.id, depth, 2, (n) => `segnavia-h-${chapter}-${n}`, { n: 0 });
74
72
  }
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
73
  function columnsValueOk(value) {
127
74
  if (!/^[0-9]+$/.test(value)) return false;
128
75
  const count = Number(value);
@@ -136,112 +83,159 @@ function columnIssues(section) {
136
83
  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
84
  });
138
85
  }
139
- function endnotesHeading(book) {
140
- const titleValue = book.endnotes.title;
86
+ function notesHeading(book, notes) {
141
87
  return element("h1", {
142
- class: "segnavia-endnotes-title",
143
- dir: direction(book, titleValue)
144
- }, [text(titleValue)]);
88
+ class: notes.series.titleClass,
89
+ dir: resolveDirection(book.dir, book.lang, notes.series.title)
90
+ }, [text(notes.series.title)]);
145
91
  }
146
- function endnotesSection(book, paragraphs, build, id, runningHead, opens) {
92
+ function notesSection(book, notes, build, id, runningHead, parts, opens) {
93
+ const scope = notes.scopeTitle !== void 0 && (notes.series.placement === "chapter" || notes.series.placement === "part") ? [element("p", {
94
+ class: "segnavia-notes-scope",
95
+ dir: resolveDirection(book.dir, book.lang, notes.scopeTitle)
96
+ }, [text(notes.scopeTitle)])] : [];
147
97
  return {
148
98
  info: {
149
99
  id,
150
100
  kind: "endnotes",
151
- title: book.endnotes.title,
101
+ division: notes.targetId === "book" ? "body" : notes.division,
102
+ title: notes.series.title,
152
103
  runningHead
153
104
  },
154
- node: sectionNode("endnotes", id, [endnotesHeading(book), ...paragraphs], build, runningHead, opens ? flowAttribs(opens) : {})
105
+ node: sectionNode("endnotes", id, [
106
+ notesHeading(book, notes),
107
+ ...scope,
108
+ ...notes.paragraphs
109
+ ], build, runningHead, {
110
+ ...opens ? flowAttribs(opens) : {},
111
+ ...parts ? { "data-segnavia-parts": JSON.stringify(parts) } : {}
112
+ })
155
113
  };
156
114
  }
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]);
115
+ function decorateSection(sectionEntry, build) {
116
+ const section = build.sections.at(-1);
117
+ if (!section) return;
118
+ if (sectionEntry.ancestry.length > 0) section.node.attribs["data-segnavia-parts"] = JSON.stringify(partNames(sectionEntry.ancestry));
119
+ attachPendingPartHeadings(build, sectionEntry, section.info.id);
171
120
  }
172
- function takeHeldNotesOwner(build) {
173
- build.heldNotesOwner -= 1;
174
- return build.heldNotesOwner;
175
- }
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"));
121
+ function noteContext(sectionEntry, targetId, chapter, title, build) {
122
+ const ancestry = sectionEntry.ancestry.at(-1);
123
+ for (const part of sectionEntry.ancestry) build.partTargets.set(part.id, targetId);
124
+ return {
125
+ chapter,
126
+ title,
127
+ division: sectionEntry.division,
128
+ targetId,
129
+ ...chapter === 0 && sectionEntry.division !== "front" ? { placementKey: targetId.replace(/^segnavia-/, "") } : {},
130
+ ...ancestry ? { part: {
131
+ id: ancestry.id,
132
+ title: ancestry.title
133
+ } } : {}
134
+ };
182
135
  }
183
136
  function addFrontMatter(book, build) {
184
137
  let contents;
185
138
  let frontNumber = 0;
186
- for (const entry of book.frontMatter) {
139
+ for (const sectionEntry of bookSections(book)) {
140
+ if (sectionEntry.division !== "front") continue;
141
+ const { entry } = sectionEntry;
142
+ if (isPart(entry)) {
143
+ addPart(sectionEntry, build);
144
+ continue;
145
+ }
187
146
  if (isBookContents(entry)) {
188
147
  contents = contentsSection(entry, build.build);
189
148
  build.sections.push(contents);
149
+ decorateSection(sectionEntry, build);
190
150
  continue;
191
151
  }
192
152
  if (isDesignedSection(entry)) {
193
153
  build.designedNumber += 1;
194
- appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build.build, build.notes, build.sections, build.headings, build.issues);
154
+ appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build, sectionEntry);
195
155
  continue;
196
156
  }
197
157
  if (isTemplatedSection(entry)) {
198
158
  build.templatedNumber += 1;
199
- appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build.build, build.notes, build.templates, build.sections, build.headings, build.issues);
159
+ appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build, sectionEntry);
200
160
  continue;
201
161
  }
202
162
  frontNumber += 1;
203
163
  const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.build);
204
164
  build.sections.push(section);
165
+ decorateSection(sectionEntry, build);
205
166
  build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
206
- if (entry.contents && entry.title !== void 0) build.headings.push({
167
+ if ("contents" in entry && entry.contents && entry.title !== void 0) build.headings.push(...withSectionDepth([{
207
168
  id: section.info.id,
208
169
  level: 1,
209
170
  text: entry.title,
210
171
  sectionId: section.info.id
211
- });
212
- transformNotes(section.node, book, 0, build.notes, build.build === "galley");
172
+ }], sectionEntry.depth));
173
+ transformNotes(section.node, book, noteContext(sectionEntry, section.info.id, 0, entry.title, build), build.notes, build.build === "galley");
213
174
  }
214
175
  return contents;
215
176
  }
216
- function addChapters(book, build) {
217
- let chapterNumber = 0;
218
- for (const entry of book.chapters) {
177
+ function addChapters(book, build, division = "body") {
178
+ for (const sectionEntry of bookSections(book)) {
179
+ if (sectionEntry.division !== division) continue;
180
+ const { entry } = sectionEntry;
181
+ if (isPart(entry)) {
182
+ addPart(sectionEntry, build);
183
+ continue;
184
+ }
185
+ if (isBookContents(entry)) continue;
219
186
  if (isDesignedSection(entry)) {
220
187
  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);
188
+ appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build, sectionEntry);
224
189
  continue;
225
190
  }
226
191
  if (isTemplatedSection(entry)) {
227
192
  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);
193
+ appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build, sectionEntry);
231
194
  continue;
232
195
  }
233
- chapterNumber += 1;
234
- const section = chapterSection(entry, chapterNumber, build.build);
196
+ if (!("html" in entry)) continue;
197
+ build.chapterNumber += 1;
198
+ const section = chapterSection(entry, build.chapterNumber, sectionEntry.division, build.build);
235
199
  build.sections.push(section);
200
+ decorateSection(sectionEntry, build);
236
201
  build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
237
- build.headings.push({
202
+ if (entry.title !== void 0) build.headings.push(...withSectionDepth([{
238
203
  id: section.info.id,
239
204
  level: 1,
240
205
  text: entry.title,
241
206
  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);
207
+ }], sectionEntry.depth));
208
+ build.headings.push(...withSectionDepth(chapterHeadings(section, build.chapterNumber, book.contents.depth), sectionEntry.depth));
209
+ transformNotes(section.node, book, noteContext(sectionEntry, section.info.id, build.chapterNumber, entry.title, build), build.notes, build.build === "galley");
210
+ }
211
+ }
212
+ function collectedSectionId(notes) {
213
+ const prefix = notes.series.sectionIdPrefix;
214
+ if (notes.targetId === "book") return prefix;
215
+ if (notes.series.placement === "part") return `${prefix}-${notes.placementKey.replace(/^segnavia-/, "")}`;
216
+ if (notes.series.placement === "chapter" && notes.targetId.startsWith("segnavia-chapter-")) return `${prefix}-${notes.targetId.slice(17)}`;
217
+ return `${prefix}-${notes.targetId.replace(/^segnavia-/, "")}`;
218
+ }
219
+ function insertCollectedSections(book, build) {
220
+ const order = new Map(book.notes.map((series, index) => [series.name, index]));
221
+ const collected = [...build.notes.collected.values()].sort((left, right) => (order.get(left.series.name) ?? 0) - (order.get(right.series.name) ?? 0));
222
+ for (const notes of [...collected].reverse()) {
223
+ if (notes.targetId === "book") continue;
224
+ const targetId = notes.series.placement === "part" ? build.partTargets.get(notes.placementKey) ?? notes.targetId : notes.targetId;
225
+ notes.targetId = targetId;
226
+ const index = build.sections.findIndex(({ info }) => info.id === targetId);
227
+ if (index < 0) {
228
+ build.issues.push(`note series ${notes.series.name} could not find placement target ${targetId}; keep the owning section in the book`);
229
+ continue;
230
+ }
231
+ const owner = build.sections[index];
232
+ const parts = owner?.node.attribs["data-segnavia-parts"];
233
+ build.sections.splice(index + 1, 0, notesSection(book, notes, build.build, collectedSectionId(notes), owner?.info.runningHead, parts ? JSON.parse(parts) : void 0, "any"));
234
+ }
235
+ for (const notes of collected) {
236
+ if (notes.targetId !== "book") continue;
237
+ const index = build.sections.findIndex(({ info }) => info.division === "end");
238
+ build.sections.splice(index < 0 ? build.sections.length : index, 0, notesSection(book, notes, build.build, collectedSectionId(notes), notes.series.title));
245
239
  }
246
240
  }
247
241
  function bookDocument(book, options = {}) {
@@ -255,24 +249,28 @@ function bookDocument(book, options = {}) {
255
249
  templates: new Map(book.templates.map((template) => [template.name, template])),
256
250
  designedNumber: 0,
257
251
  templatedNumber: 0,
258
- heldNotesOwner: 0
252
+ chapterNumber: 0,
253
+ partTargets: /* @__PURE__ */ new Map(),
254
+ pendingPartHeadings: []
259
255
  };
260
256
  const contents = addFrontMatter(book, build);
261
257
  addChapters(book, build);
258
+ addChapters(book, build, "end");
262
259
  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));
260
+ insertCollectedSections(book, build);
267
261
  if (contents) addContents(book, contents, headings, options);
268
262
  if (requestedBuild !== "print") markBlockPositions(sections);
269
- const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.kind) === options.part);
263
+ const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.division) === options.part);
270
264
  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
- ])] : [
265
+ document.children = requestedBuild === "galley" ? [
266
+ element("style", {}, [text(bookDocumentCss(book, requestedBuild))]),
267
+ ...options.targets ? [targetLinks(visible)] : [],
268
+ element("div", { "data-segnavia-galleys": "" }, [
269
+ ...templateHolders(book.templates),
270
+ ...visible.map((section) => section.node),
271
+ ...notes.pageParagraphs.length > 0 ? [notesGalley(notes.pageParagraphs)] : []
272
+ ])
273
+ ] : [
276
274
  element("style", {}, [text(bookDocumentCss(book, requestedBuild))]),
277
275
  ...options.targets ? [targetLinks(visible)] : [],
278
276
  ...withBlankPages(visible, options.blankBefore)
@@ -284,12 +282,10 @@ function bookDocument(book, options = {}) {
284
282
  headings,
285
283
  issues: [
286
284
  ...buildIssue ? [buildIssue] : [],
287
- ...noteMarkerIssues(book, {
288
- footnotes: notes.footnotes,
289
- endnotes: notes.endnotes
290
- }),
285
+ ...noteMarkerIssues(book.notes, notes.used),
286
+ ...notes.issues,
291
287
  ...markPlacementIssues(book.parentPages),
292
- ...templateRegionNameIssues(book.templates),
288
+ ...templateRegionIssues(book.templates),
293
289
  ...issues,
294
290
  ...duplicateIdIssues(sections, headings)
295
291
  ]
@@ -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}${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";