@segnavia/html 0.36.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.
- package/dist/book/book-contents.js +33 -0
- package/dist/book/book-document-nodes.js +30 -0
- package/dist/book/book-document.d.ts +2 -2
- package/dist/book/book-document.js +162 -167
- package/dist/book/book-flow.d.ts +13 -0
- package/dist/book/book-flow.js +96 -0
- package/dist/book/book-held.d.ts +4 -0
- package/dist/book/book-held.js +94 -13
- package/dist/book/book-issues.js +24 -0
- package/dist/book/book-part-build.js +50 -0
- package/dist/book/book-view.js +8 -2
- package/dist/book/dom.d.ts +1 -0
- package/dist/book/notes.d.ts +2 -0
- package/dist/book/notes.js +101 -47
- package/dist/book/paginate-book.d.ts +2 -1
- package/dist/book/paginate-book.js +34 -26
- package/dist/book/paginate-keeps.js +3 -2
- package/dist/book/paginate-sequence.d.ts +1 -1
- package/dist/book/paginate-sequence.js +60 -28
- package/dist/book/paginate-types.d.ts +10 -5
- package/dist/book/region-fit.d.ts +16 -0
- package/dist/book/region-fit.js +111 -0
- package/dist/book/types.d.ts +6 -2
- package/dist/book/types.js +2 -2
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -4
- package/dist/motifs.d.ts +2 -0
- package/dist/motifs.js +5 -2
- package/dist/printed-pages.d.ts +2 -7
- package/dist/printed-pages.js +1 -1
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.js +78 -43
- package/package.json +2 -2
|
@@ -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,7 +1,7 @@
|
|
|
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
|
-
export declare const BOOK_COLUMN_MIN = 2;
|
|
5
|
-
export declare const BOOK_COLUMN_MAX = 4;
|
|
6
6
|
export declare function bookDocument(book: Book, options?: BookDocumentOptions): BookDocument;
|
|
7
7
|
//#endregion
|
|
@@ -1,32 +1,16 @@
|
|
|
1
1
|
import { elements, parse, serialize } from "../sanitize.js";
|
|
2
2
|
import { element, linkChildren, text } from "./dom.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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";
|
|
5
10
|
import { bookPrintPart } from "./types.js";
|
|
6
|
-
import {
|
|
7
|
-
import { isBookContents, isDesignedSection, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
|
|
11
|
+
import { bookSections, isBookContents, isDesignedSection, isPart, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
|
|
8
12
|
//#region src/book/book-document.ts
|
|
9
|
-
|
|
10
|
-
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%)}}`;
|
|
11
|
-
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}`;
|
|
12
|
-
function direction(book, value) {
|
|
13
|
-
return resolveDirection(book.dir, book.lang, value);
|
|
14
|
-
}
|
|
15
|
-
function authoredSection(section, id, galley = false) {
|
|
16
|
-
const document = parse(section.html);
|
|
17
|
-
const runningHead = section.title;
|
|
18
|
-
const node = sectionNode("front", id, document.children, galley, runningHead, flowAttribs(section.opens, section.parents));
|
|
19
|
-
return {
|
|
20
|
-
info: {
|
|
21
|
-
id,
|
|
22
|
-
kind: "front",
|
|
23
|
-
title: section.title,
|
|
24
|
-
runningHead
|
|
25
|
-
},
|
|
26
|
-
node
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function contentsSection(entry, galley = false) {
|
|
13
|
+
function contentsSection(entry, build) {
|
|
30
14
|
const id = "segnavia-contents";
|
|
31
15
|
const title = entry.title;
|
|
32
16
|
const children = title !== void 0 ? [element("h1", {
|
|
@@ -37,13 +21,14 @@ function contentsSection(entry, galley = false) {
|
|
|
37
21
|
info: {
|
|
38
22
|
id,
|
|
39
23
|
kind: "contents",
|
|
24
|
+
division: "front",
|
|
40
25
|
title,
|
|
41
26
|
runningHead: title
|
|
42
27
|
},
|
|
43
|
-
node: sectionNode("contents", id, children,
|
|
28
|
+
node: sectionNode("contents", id, children, build, title, flowAttribs(entry.opens, entry.parents))
|
|
44
29
|
};
|
|
45
30
|
}
|
|
46
|
-
function chapterSection(chapter, index,
|
|
31
|
+
function chapterSection(chapter, index, division, build) {
|
|
47
32
|
const id = `segnavia-chapter-${index}`;
|
|
48
33
|
const document = parse(chapter.html);
|
|
49
34
|
const runningHead = chapter.runningHead ?? chapter.title;
|
|
@@ -51,82 +36,40 @@ function chapterSection(chapter, index, galley = false) {
|
|
|
51
36
|
info: {
|
|
52
37
|
id,
|
|
53
38
|
kind: "chapter",
|
|
39
|
+
division,
|
|
54
40
|
title: chapter.title,
|
|
55
41
|
runningHead
|
|
56
42
|
},
|
|
57
|
-
node: sectionNode("chapter", id, document.children,
|
|
43
|
+
node: sectionNode("chapter", id, document.children, build, runningHead, flowAttribs(chapter.opens, chapter.parents))
|
|
58
44
|
};
|
|
59
45
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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");
|
|
65
54
|
}
|
|
66
|
-
function
|
|
67
|
-
const held =
|
|
68
|
-
|
|
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");
|
|
69
63
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
function withSectionDepth(headings, depth) {
|
|
65
|
+
return depth > 0 ? headings.map((heading) => ({
|
|
66
|
+
...heading,
|
|
67
|
+
depth
|
|
68
|
+
})) : headings;
|
|
73
69
|
}
|
|
74
70
|
function chapterHeadings(section, chapter, depth) {
|
|
75
71
|
return collectHeadings(section.node.children, section.info.id, depth, 2, (n) => `segnavia-h-${chapter}-${n}`, { n: 0 });
|
|
76
72
|
}
|
|
77
|
-
function contentsList(book, headings, options) {
|
|
78
|
-
const entries = headings.map((heading) => {
|
|
79
|
-
const page = book.contents.pageNumbers ? options.pageNumbers?.[heading.id] ?? "" : "";
|
|
80
|
-
return element("li", {
|
|
81
|
-
class: "segnavia-contents-entry",
|
|
82
|
-
"data-level": String(heading.level)
|
|
83
|
-
}, [element("a", {
|
|
84
|
-
href: options.crossLinks ? `segnavia:target/${heading.id}` : `#${heading.id}`,
|
|
85
|
-
dir: direction(book, heading.text)
|
|
86
|
-
}, [text(heading.text)]), element("span", {
|
|
87
|
-
class: "segnavia-contents-page",
|
|
88
|
-
"data-target": heading.id,
|
|
89
|
-
dir: direction(book, page)
|
|
90
|
-
}, [text(page)])]);
|
|
91
|
-
});
|
|
92
|
-
return element("ol", { class: "segnavia-contents-list" }, entries);
|
|
93
|
-
}
|
|
94
|
-
function addContents(book, section, headings, options) {
|
|
95
|
-
const title = section.info.title;
|
|
96
|
-
const titleNode = section.node.children.find((child) => isTag(child) && child.name === "h1");
|
|
97
|
-
if (title !== void 0 && titleNode && isTag(titleNode)) titleNode.attribs.dir = direction(book, title);
|
|
98
|
-
section.node.children.push(contentsList(book, headings, options));
|
|
99
|
-
linkChildren(section.node);
|
|
100
|
-
}
|
|
101
|
-
function targetLinks(sections) {
|
|
102
|
-
const ids = sections.flatMap((section) => [section.info.id, ...elements(section.node.children).flatMap((node) => node.attribs.id ? [node.attribs.id] : [])]);
|
|
103
|
-
return element("div", {
|
|
104
|
-
class: "segnavia-targets",
|
|
105
|
-
"aria-hidden": "true"
|
|
106
|
-
}, [...new Set(ids)].map((id) => element("a", { href: `#${id}` }, [text("")])));
|
|
107
|
-
}
|
|
108
|
-
function duplicateIdIssues(sections, headings) {
|
|
109
|
-
const listed = new Set(headings.map((heading) => heading.id));
|
|
110
|
-
const occurrences = /* @__PURE__ */ new Map();
|
|
111
|
-
for (const section of sections) {
|
|
112
|
-
const ids = [section.info.id, ...elements(section.node.children).flatMap((node) => node.attribs.id ? [node.attribs.id] : [])];
|
|
113
|
-
for (const id of ids) occurrences.set(id, [...occurrences.get(id) ?? [], section.info]);
|
|
114
|
-
}
|
|
115
|
-
return [...occurrences].flatMap(([id, owners]) => {
|
|
116
|
-
if (!listed.has(id) || owners.length < 2) return [];
|
|
117
|
-
const named = owners.slice(0, 2).map((owner) => `${owner.title ?? owner.id} (${owner.id})`);
|
|
118
|
-
return [`destination id ${id} is used in both ${named[0]} and ${named[1]}; listed heading and generated destination ids must be unique`];
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
function reservedIdIssues(section) {
|
|
122
|
-
return elements(section.node.children).flatMap((node) => {
|
|
123
|
-
const id = node.attribs.id;
|
|
124
|
-
if (!id?.startsWith("segnavia-")) return [];
|
|
125
|
-
return [`authored id ${id} in ${section.info.title ?? section.info.id} (${section.info.id}) uses the reserved segnavia- prefix`];
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
const BOOK_COLUMN_MIN = 2;
|
|
129
|
-
const BOOK_COLUMN_MAX = 4;
|
|
130
73
|
function columnsValueOk(value) {
|
|
131
74
|
if (!/^[0-9]+$/.test(value)) return false;
|
|
132
75
|
const count = Number(value);
|
|
@@ -140,117 +83,165 @@ function columnIssues(section) {
|
|
|
140
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`];
|
|
141
84
|
});
|
|
142
85
|
}
|
|
143
|
-
function
|
|
144
|
-
const titleValue = book.endnotes.title;
|
|
86
|
+
function notesHeading(book, notes) {
|
|
145
87
|
return element("h1", {
|
|
146
|
-
class:
|
|
147
|
-
dir:
|
|
148
|
-
}, [text(
|
|
88
|
+
class: notes.series.titleClass,
|
|
89
|
+
dir: resolveDirection(book.dir, book.lang, notes.series.title)
|
|
90
|
+
}, [text(notes.series.title)]);
|
|
149
91
|
}
|
|
150
|
-
function
|
|
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)])] : [];
|
|
151
97
|
return {
|
|
152
98
|
info: {
|
|
153
99
|
id,
|
|
154
100
|
kind: "endnotes",
|
|
155
|
-
|
|
101
|
+
division: notes.targetId === "book" ? "body" : notes.division,
|
|
102
|
+
title: notes.series.title,
|
|
156
103
|
runningHead
|
|
157
104
|
},
|
|
158
|
-
node: sectionNode("endnotes", id, [
|
|
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
|
+
})
|
|
159
113
|
};
|
|
160
114
|
}
|
|
161
|
-
function
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}, notes);
|
|
167
|
-
}
|
|
168
|
-
function withBlankPages(sections, blankBefore) {
|
|
169
|
-
const counts = /* @__PURE__ */ new Map();
|
|
170
|
-
for (const id of blankBefore ?? []) counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
171
|
-
return sections.flatMap((section) => [...Array.from({ length: counts.get(section.info.id) ?? 0 }, () => element("div", {
|
|
172
|
-
class: "segnavia-blank",
|
|
173
|
-
"aria-hidden": "true"
|
|
174
|
-
})), 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);
|
|
175
120
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
};
|
|
186
135
|
}
|
|
187
136
|
function addFrontMatter(book, build) {
|
|
188
137
|
let contents;
|
|
189
138
|
let frontNumber = 0;
|
|
190
|
-
for (const
|
|
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
|
+
}
|
|
191
146
|
if (isBookContents(entry)) {
|
|
192
|
-
contents = contentsSection(entry, build.
|
|
147
|
+
contents = contentsSection(entry, build.build);
|
|
193
148
|
build.sections.push(contents);
|
|
149
|
+
decorateSection(sectionEntry, build);
|
|
194
150
|
continue;
|
|
195
151
|
}
|
|
196
152
|
if (isDesignedSection(entry)) {
|
|
197
153
|
build.designedNumber += 1;
|
|
198
|
-
appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build
|
|
154
|
+
appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build, sectionEntry);
|
|
199
155
|
continue;
|
|
200
156
|
}
|
|
201
157
|
if (isTemplatedSection(entry)) {
|
|
202
158
|
build.templatedNumber += 1;
|
|
203
|
-
appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build
|
|
159
|
+
appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build, sectionEntry);
|
|
204
160
|
continue;
|
|
205
161
|
}
|
|
206
162
|
frontNumber += 1;
|
|
207
|
-
const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.
|
|
163
|
+
const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.build);
|
|
208
164
|
build.sections.push(section);
|
|
165
|
+
decorateSection(sectionEntry, build);
|
|
209
166
|
build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
|
|
210
|
-
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([{
|
|
211
168
|
id: section.info.id,
|
|
212
169
|
level: 1,
|
|
213
170
|
text: entry.title,
|
|
214
171
|
sectionId: section.info.id
|
|
215
|
-
});
|
|
216
|
-
transformNotes(section.node, book, 0, build.notes, build.galley);
|
|
172
|
+
}], sectionEntry.depth));
|
|
173
|
+
transformNotes(section.node, book, noteContext(sectionEntry, section.info.id, 0, entry.title, build), build.notes, build.build === "galley");
|
|
217
174
|
}
|
|
218
175
|
return contents;
|
|
219
176
|
}
|
|
220
|
-
function addChapters(book, build) {
|
|
221
|
-
|
|
222
|
-
|
|
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;
|
|
223
186
|
if (isDesignedSection(entry)) {
|
|
224
187
|
build.designedNumber += 1;
|
|
225
|
-
|
|
226
|
-
appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build.galley, build.notes, build.sections, build.headings, build.issues, owner);
|
|
227
|
-
pushChapterEndnotes(book, build, `segnavia-endnotes-designed-${build.designedNumber}`, owner);
|
|
188
|
+
appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build, sectionEntry);
|
|
228
189
|
continue;
|
|
229
190
|
}
|
|
230
191
|
if (isTemplatedSection(entry)) {
|
|
231
192
|
build.templatedNumber += 1;
|
|
232
|
-
|
|
233
|
-
appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build.galley, build.notes, build.templates, build.sections, build.headings, build.issues, owner);
|
|
234
|
-
pushChapterEndnotes(book, build, `segnavia-endnotes-templated-${build.templatedNumber}`, owner);
|
|
193
|
+
appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build, sectionEntry);
|
|
235
194
|
continue;
|
|
236
195
|
}
|
|
237
|
-
|
|
238
|
-
|
|
196
|
+
if (!("html" in entry)) continue;
|
|
197
|
+
build.chapterNumber += 1;
|
|
198
|
+
const section = chapterSection(entry, build.chapterNumber, sectionEntry.division, build.build);
|
|
239
199
|
build.sections.push(section);
|
|
200
|
+
decorateSection(sectionEntry, build);
|
|
240
201
|
build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
|
|
241
|
-
build.headings.push({
|
|
202
|
+
if (entry.title !== void 0) build.headings.push(...withSectionDepth([{
|
|
242
203
|
id: section.info.id,
|
|
243
204
|
level: 1,
|
|
244
205
|
text: entry.title,
|
|
245
206
|
sectionId: section.info.id
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
|
|
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));
|
|
249
239
|
}
|
|
250
240
|
}
|
|
251
241
|
function bookDocument(book, options = {}) {
|
|
242
|
+
const { build: requestedBuild, issue: buildIssue } = resolveBookBuild(book, options.build);
|
|
252
243
|
const build = {
|
|
253
|
-
|
|
244
|
+
build: requestedBuild,
|
|
254
245
|
notes: noteState(),
|
|
255
246
|
sections: [],
|
|
256
247
|
headings: [],
|
|
@@ -258,24 +249,29 @@ function bookDocument(book, options = {}) {
|
|
|
258
249
|
templates: new Map(book.templates.map((template) => [template.name, template])),
|
|
259
250
|
designedNumber: 0,
|
|
260
251
|
templatedNumber: 0,
|
|
261
|
-
|
|
252
|
+
chapterNumber: 0,
|
|
253
|
+
partTargets: /* @__PURE__ */ new Map(),
|
|
254
|
+
pendingPartHeadings: []
|
|
262
255
|
};
|
|
263
256
|
const contents = addFrontMatter(book, build);
|
|
264
257
|
addChapters(book, build);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (leftover.length > 0) sections.push(endnotesSection(book, leftover, galley, "segnavia-endnotes", book.endnotes.title));
|
|
269
|
-
} else if (notes.endnotes) sections.push(endnotesSection(book, endnotesInOrder(notes), galley, "segnavia-endnotes", book.endnotes.title));
|
|
258
|
+
addChapters(book, build, "end");
|
|
259
|
+
const { notes, sections, headings, issues } = build;
|
|
260
|
+
insertCollectedSections(book, build);
|
|
270
261
|
if (contents) addContents(book, contents, headings, options);
|
|
271
|
-
|
|
262
|
+
if (requestedBuild !== "print") markBlockPositions(sections);
|
|
263
|
+
const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.division) === options.part);
|
|
272
264
|
const document = parse("");
|
|
273
|
-
document.children =
|
|
274
|
-
|
|
275
|
-
...
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
] : [
|
|
274
|
+
element("style", {}, [text(bookDocumentCss(book, requestedBuild))]),
|
|
279
275
|
...options.targets ? [targetLinks(visible)] : [],
|
|
280
276
|
...withBlankPages(visible, options.blankBefore)
|
|
281
277
|
];
|
|
@@ -285,16 +281,15 @@ function bookDocument(book, options = {}) {
|
|
|
285
281
|
sections: sections.map((section) => section.info),
|
|
286
282
|
headings,
|
|
287
283
|
issues: [
|
|
288
|
-
...
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}),
|
|
284
|
+
...buildIssue ? [buildIssue] : [],
|
|
285
|
+
...noteMarkerIssues(book.notes, notes.used),
|
|
286
|
+
...notes.issues,
|
|
292
287
|
...markPlacementIssues(book.parentPages),
|
|
293
|
-
...
|
|
288
|
+
...templateRegionIssues(book.templates),
|
|
294
289
|
...issues,
|
|
295
290
|
...duplicateIdIssues(sections, headings)
|
|
296
291
|
]
|
|
297
292
|
};
|
|
298
293
|
}
|
|
299
294
|
//#endregion
|
|
300
|
-
export {
|
|
295
|
+
export { bookDocument };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Element } from "domhandler";
|
|
2
|
+
import { Book } from "@segnavia/format";
|
|
3
|
+
//#region src/book/book-flow.d.ts
|
|
4
|
+
export declare const BOOK_FLOW_COLUMN_MIN_PX = 240;
|
|
5
|
+
export declare const BOOK_COLUMN_MIN = 2;
|
|
6
|
+
export declare const BOOK_COLUMN_MAX = 4;
|
|
7
|
+
export interface BookPositionKey {
|
|
8
|
+
flow: string;
|
|
9
|
+
index: number;
|
|
10
|
+
suffix?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseBookPositionKey(value: string): BookPositionKey | undefined;
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isTag } from "domhandler";
|
|
2
|
+
import { MAX_PART_DEPTH, PX_PER_MM, bookSections, isPart, sheetMm } from "@segnavia/format";
|
|
3
|
+
//#region src/book/book-flow.ts
|
|
4
|
+
const BOOK_FLOW_COLUMN_MIN_PX = 240;
|
|
5
|
+
const BOOK_COLUMN_MIN = 2;
|
|
6
|
+
const BOOK_COLUMN_MAX = 4;
|
|
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
|
+
}
|
|
22
|
+
function bookFlowPageGeometry(book) {
|
|
23
|
+
if (!book.page) return void 0;
|
|
24
|
+
const paper = sheetMm(book.page);
|
|
25
|
+
return {
|
|
26
|
+
paperInline: paper.width * PX_PER_MM,
|
|
27
|
+
paperBlock: paper.height * PX_PER_MM,
|
|
28
|
+
textInline: (paper.width - book.page.margin.inner - book.page.margin.outer) * PX_PER_MM,
|
|
29
|
+
textBlock: (paper.height - book.page.margin.top - book.page.margin.bottom) * PX_PER_MM,
|
|
30
|
+
marginBlockStart: book.page.margin.top * PX_PER_MM,
|
|
31
|
+
marginInlineStartRecto: book.page.margin.inner * PX_PER_MM,
|
|
32
|
+
marginInlineStartVerso: book.page.margin.outer * PX_PER_MM
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function columnFallbacks(gap) {
|
|
36
|
+
return Array.from({ length: 3 }, (_, index) => index + 2).map((count) => {
|
|
37
|
+
return `@container (max-width:${count * 240 + (count - 1) * gap}px){:where([data-columns="${count}"]){column-count:1}}`;
|
|
38
|
+
}).join("");
|
|
39
|
+
}
|
|
40
|
+
function flowCss(book) {
|
|
41
|
+
const gap = (book.page?.columnGap ?? 0) * PX_PER_MM;
|
|
42
|
+
const columns = Array.from({ length: 3 }, (_, index) => index + 2).map((count) => `:where([data-columns="${count}"]){column-count:${count}}`).join("");
|
|
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)}`;
|
|
44
|
+
}
|
|
45
|
+
function resolveBookBuild(book, requested) {
|
|
46
|
+
const build = requested ?? (book.mode === "paginated" ? "galley" : "print");
|
|
47
|
+
if (book.mode === "paginated" || build === "print") return { build };
|
|
48
|
+
return {
|
|
49
|
+
build,
|
|
50
|
+
issue: `a continuous book cannot use the ${build} build; request the print build, or make the book paginated with a page to clear this`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function bookDocumentCss(book, build) {
|
|
54
|
+
if (build === "galley") return `${contentsCss(book)}${GALLEY_SUFFIX}`;
|
|
55
|
+
return build === "flow" ? flowCss(book) : printCss(book);
|
|
56
|
+
}
|
|
57
|
+
function flowPageStyle(geometry, recto) {
|
|
58
|
+
const marginInlineStart = recto ? geometry.marginInlineStartRecto : geometry.marginInlineStartVerso;
|
|
59
|
+
return `--segnavia-paper-inline:${geometry.paperInline}px;--segnavia-paper-block:${geometry.paperBlock}px;--segnavia-paper-ratio:${geometry.paperBlock / geometry.paperInline};--segnavia-text-inline:${geometry.textInline}px;--segnavia-text-block:${geometry.textBlock}px;--segnavia-text-block-start:${geometry.marginBlockStart}px;--segnavia-text-inline-start:${marginInlineStart}px`;
|
|
60
|
+
}
|
|
61
|
+
function bookBlockPosition(flowId, block, suffix) {
|
|
62
|
+
return `${flowId}:${block}${suffix ? `:${suffix}` : ""}`;
|
|
63
|
+
}
|
|
64
|
+
function parseBookPositionKey(value) {
|
|
65
|
+
const match = /^(.*):(\d+)(?::([^:]+))?$/.exec(value);
|
|
66
|
+
if (!match?.[1] || !match[2]) return void 0;
|
|
67
|
+
return {
|
|
68
|
+
flow: match[1],
|
|
69
|
+
index: Number(match[2]),
|
|
70
|
+
...match[3] ? { suffix: match[3] } : {}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isFootnoteBlock(node) {
|
|
74
|
+
return (node.attribs.class ?? "").split(/\s+/).includes("segnavia-footnotes");
|
|
75
|
+
}
|
|
76
|
+
function isNotesScope(node) {
|
|
77
|
+
return (node.attribs.class ?? "").split(/\s+/).includes("segnavia-notes-scope");
|
|
78
|
+
}
|
|
79
|
+
function markBlockPositions(sections) {
|
|
80
|
+
for (const section of sections) {
|
|
81
|
+
let position = 0;
|
|
82
|
+
for (const child of section.node.children) {
|
|
83
|
+
if (!isTag(child)) continue;
|
|
84
|
+
if (child.name === "style" || child.name === "script") continue;
|
|
85
|
+
if (isNotesScope(child)) continue;
|
|
86
|
+
if (isFootnoteBlock(child)) {
|
|
87
|
+
child.attribs["data-segnavia-position"] = bookBlockPosition(section.info.id, position, "footnotes");
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
position += 1;
|
|
91
|
+
child.attribs["data-segnavia-position"] = bookBlockPosition(section.info.id, position);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, bookBlockPosition, bookDocumentCss, bookFlowPageGeometry, flowPageStyle, markBlockPositions, parseBookPositionKey, resolveBookBuild };
|