@segnavia/html 0.27.0 → 0.28.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/bindings.d.ts +1 -1
- package/dist/bindings.js +4 -1
- package/dist/book/book-document.js +146 -65
- package/dist/book/book-held.js +182 -0
- package/dist/book/book-view.d.ts +5 -0
- package/dist/book/book-view.js +26 -0
- package/dist/book/furniture.d.ts +14 -0
- package/dist/book/furniture.js +36 -0
- package/dist/book/notes.js +18 -9
- package/dist/book/paginate-book.d.ts +11 -0
- package/dist/book/paginate-book.js +195 -0
- package/dist/book/paginate-columns.d.ts +8 -0
- package/dist/book/paginate-columns.js +119 -0
- package/dist/book/paginate-keeps.d.ts +4 -0
- package/dist/book/paginate-keeps.js +80 -0
- package/dist/book/paginate-marks.d.ts +7 -0
- package/dist/book/paginate-marks.js +34 -0
- package/dist/book/paginate-notes.d.ts +21 -0
- package/dist/book/paginate-notes.js +105 -0
- package/dist/book/paginate-sequence.d.ts +4 -0
- package/dist/book/paginate-sequence.js +267 -0
- package/dist/book/paginate-target.d.ts +13 -0
- package/dist/book/paginate-target.js +36 -0
- package/dist/book/paginate-types.d.ts +170 -0
- package/dist/book/types.d.ts +1 -1
- package/dist/book-css.d.ts +5 -0
- package/dist/book-css.js +14 -0
- package/dist/css-tokens.js +189 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +7 -2
- package/dist/motifs.d.ts +1 -0
- package/dist/motifs.js +5 -1
- package/dist/printed-pages.d.ts +18 -4
- package/dist/printed-pages.js +41 -19
- package/dist/sanitize-css.d.ts +6 -0
- package/dist/sanitize-css.js +308 -0
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.js +37 -15
- package/package.json +2 -2
package/dist/bindings.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { SanitizeOptions } from "./sanitize.js";
|
|
|
3
3
|
export declare const BINDINGS: readonly ["value", "href", "src", "each", "when", "present", "width"];
|
|
4
4
|
export declare function bindingRoot(path: string): string;
|
|
5
5
|
export declare function bindingNames(prefix: string): Record<(typeof BINDINGS)[number], string>;
|
|
6
|
-
export interface FillOptions extends Pick<SanitizeOptions, 'linkOrigins'> {
|
|
6
|
+
export interface FillOptions extends Pick<SanitizeOptions, 'linkOrigins' | 'dataFonts'> {
|
|
7
7
|
prefix: string;
|
|
8
8
|
data: Record<string, unknown>;
|
|
9
9
|
unavailable?: Iterable<string>;
|
package/dist/bindings.js
CHANGED
|
@@ -77,7 +77,10 @@ function fill(html, options) {
|
|
|
77
77
|
const unavailable = new Set(options.unavailable ?? []);
|
|
78
78
|
const values = [];
|
|
79
79
|
const deferred = new Set(options.deferPaths ?? []);
|
|
80
|
-
const document = parse(sanitize(html, {
|
|
80
|
+
const document = parse(sanitize(html, {
|
|
81
|
+
linkOrigins: options.linkOrigins,
|
|
82
|
+
dataFonts: options.dataFonts
|
|
83
|
+
}));
|
|
81
84
|
const resolve = (path, stack) => {
|
|
82
85
|
const { context, rest } = contextAt(stack, path);
|
|
83
86
|
const value = atPath(context, rest);
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import { elements, parse, serialize } from "../sanitize.js";
|
|
2
|
-
import { element, linkChildren, text
|
|
3
|
-
import {
|
|
2
|
+
import { element, linkChildren, text } from "./dom.js";
|
|
3
|
+
import { collectHeadings, designedSection, flowAttribs, markPlacementIssues, sectionNode, templateHolders, templateRegionNameIssues, templatedSection, undeclaredRegionIssues } from "./book-held.js";
|
|
4
|
+
import { endnotesInOrder, noteState, transformNotes } from "./notes.js";
|
|
4
5
|
import { bookPrintPart } from "./types.js";
|
|
5
6
|
import { isTag } from "domhandler";
|
|
6
|
-
import { isBookContents, noteMarkerIssues, resolveDirection } from "@segnavia/format";
|
|
7
|
+
import { isBookContents, isDesignedSection, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
|
|
7
8
|
//#region src/book/book-document.ts
|
|
8
|
-
const
|
|
9
|
+
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}";
|
|
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}`;
|
|
9
12
|
function direction(book, value) {
|
|
10
13
|
return resolveDirection(book.dir, book.lang, value);
|
|
11
14
|
}
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}, children);
|
|
17
|
-
}
|
|
18
|
-
function authoredSection(section, id) {
|
|
19
|
-
const node = sectionNode("front", id, parse(section.html).children);
|
|
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));
|
|
20
19
|
return {
|
|
21
20
|
info: {
|
|
22
21
|
id,
|
|
23
22
|
kind: "front",
|
|
24
23
|
title: section.title,
|
|
25
|
-
runningHead
|
|
24
|
+
runningHead
|
|
26
25
|
},
|
|
27
26
|
node
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
|
-
function contentsSection(
|
|
29
|
+
function contentsSection(entry, galley = false) {
|
|
31
30
|
const id = "segnavia-contents";
|
|
31
|
+
const title = entry.title;
|
|
32
32
|
const children = title !== void 0 ? [element("h1", {
|
|
33
33
|
class: "segnavia-contents-title",
|
|
34
34
|
dir: "ltr"
|
|
@@ -40,39 +40,39 @@ function contentsSection(title) {
|
|
|
40
40
|
title,
|
|
41
41
|
runningHead: title
|
|
42
42
|
},
|
|
43
|
-
node: sectionNode("contents", id, children)
|
|
43
|
+
node: sectionNode("contents", id, children, galley, title, flowAttribs(entry.opens, entry.parents))
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function chapterSection(chapter, index) {
|
|
46
|
+
function chapterSection(chapter, index, galley = false) {
|
|
47
47
|
const id = `segnavia-chapter-${index}`;
|
|
48
48
|
const document = parse(chapter.html);
|
|
49
|
+
const runningHead = chapter.runningHead ?? chapter.title;
|
|
49
50
|
return {
|
|
50
51
|
info: {
|
|
51
52
|
id,
|
|
52
53
|
kind: "chapter",
|
|
53
54
|
title: chapter.title,
|
|
54
|
-
runningHead
|
|
55
|
+
runningHead
|
|
55
56
|
},
|
|
56
|
-
node: sectionNode("chapter", id, document.children)
|
|
57
|
+
node: sectionNode("chapter", id, document.children, galley, runningHead, flowAttribs(chapter.opens, chapter.parents))
|
|
57
58
|
};
|
|
58
59
|
}
|
|
60
|
+
function appendHeld(built, heldHeadings, extraIssues, book, notes, galley, sections, headings, issues, notesOwner = 0) {
|
|
61
|
+
sections.push(built);
|
|
62
|
+
issues.push(...reservedIdIssues(built), ...columnIssues(built), ...extraIssues);
|
|
63
|
+
headings.push(...heldHeadings);
|
|
64
|
+
transformNotes(built.node, book, 0, notes, galley, notesOwner);
|
|
65
|
+
}
|
|
66
|
+
function appendDesigned(entry, id, kind, book, galley, notes, sections, headings, issues, notesOwner = 0) {
|
|
67
|
+
const held = designedSection(entry, id, kind, galley, book.contents.depth);
|
|
68
|
+
appendHeld(held.built, held.headings, [], book, notes, galley, sections, headings, issues, notesOwner);
|
|
69
|
+
}
|
|
70
|
+
function appendTemplated(entry, id, kind, book, galley, notes, templates, sections, headings, issues, notesOwner = 0) {
|
|
71
|
+
const held = templatedSection(entry, id, kind, galley, book.contents.depth);
|
|
72
|
+
appendHeld(held.built, held.headings, undeclaredRegionIssues(entry, id, templates), book, notes, galley, sections, headings, issues, notesOwner);
|
|
73
|
+
}
|
|
59
74
|
function chapterHeadings(section, chapter, depth) {
|
|
60
|
-
|
|
61
|
-
return elements(section.node.children).flatMap((heading) => {
|
|
62
|
-
const match = /^h([2-6])$/.exec(heading.name);
|
|
63
|
-
if (!match || heading.attribs["data-contents"] === "exclude") return [];
|
|
64
|
-
const level = Number(match[1]);
|
|
65
|
-
if (level > depth) return [];
|
|
66
|
-
generated += 1;
|
|
67
|
-
const id = heading.attribs.id ?? `segnavia-h-${chapter}-${generated}`;
|
|
68
|
-
heading.attribs.id = id;
|
|
69
|
-
return [{
|
|
70
|
-
id,
|
|
71
|
-
level,
|
|
72
|
-
text: textContent(heading, true).replace(/\s+/g, " ").trim(),
|
|
73
|
-
sectionId: section.info.id
|
|
74
|
-
}];
|
|
75
|
-
});
|
|
75
|
+
return collectHeadings(section.node.children, section.info.id, depth, 2, (n) => `segnavia-h-${chapter}-${n}`, { n: 0 });
|
|
76
76
|
}
|
|
77
77
|
function contentsList(book, headings, options) {
|
|
78
78
|
const entries = headings.map((heading) => {
|
|
@@ -125,23 +125,44 @@ function reservedIdIssues(section) {
|
|
|
125
125
|
return [`authored id ${id} in ${section.info.title ?? section.info.id} (${section.info.id}) uses the reserved segnavia- prefix`];
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
128
|
+
function columnsValueOk(value) {
|
|
129
|
+
if (!/^[0-9]+$/.test(value)) return false;
|
|
130
|
+
const count = Number(value);
|
|
131
|
+
return count >= 2 && count <= 4;
|
|
132
|
+
}
|
|
133
|
+
function columnIssues(section) {
|
|
134
|
+
const where = `${section.info.title ?? section.info.id} (${section.info.id})`;
|
|
135
|
+
return elements(section.node.children).flatMap((node) => {
|
|
136
|
+
const value = node.attribs["data-columns"];
|
|
137
|
+
if (value === void 0 || columnsValueOk(value)) return [];
|
|
138
|
+
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`];
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function endnotesHeading(book) {
|
|
130
142
|
const titleValue = book.endnotes.title;
|
|
131
|
-
|
|
143
|
+
return element("h1", {
|
|
132
144
|
class: "segnavia-endnotes-title",
|
|
133
145
|
dir: direction(book, titleValue)
|
|
134
146
|
}, [text(titleValue)]);
|
|
147
|
+
}
|
|
148
|
+
function endnotesSection(book, paragraphs, galley, id, runningHead, opens) {
|
|
135
149
|
return {
|
|
136
150
|
info: {
|
|
137
151
|
id,
|
|
138
152
|
kind: "endnotes",
|
|
139
|
-
title:
|
|
140
|
-
runningHead
|
|
153
|
+
title: book.endnotes.title,
|
|
154
|
+
runningHead
|
|
141
155
|
},
|
|
142
|
-
node: sectionNode("endnotes", id, [
|
|
156
|
+
node: sectionNode("endnotes", id, [endnotesHeading(book), ...paragraphs], galley, runningHead, opens ? flowAttribs(opens) : {})
|
|
143
157
|
};
|
|
144
158
|
}
|
|
159
|
+
function notesGalley(paragraphs) {
|
|
160
|
+
const notes = paragraphs.map((paragraph) => element("div", { "data-segnavia-note": paragraph.attribs.id ?? "" }, [paragraph]));
|
|
161
|
+
return element("aside", {
|
|
162
|
+
"data-segnavia-notes-galley": "",
|
|
163
|
+
"aria-hidden": "true"
|
|
164
|
+
}, notes);
|
|
165
|
+
}
|
|
145
166
|
function withBlankPages(sections, blankBefore) {
|
|
146
167
|
const counts = /* @__PURE__ */ new Map();
|
|
147
168
|
for (const id of blankBefore ?? []) counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
@@ -150,50 +171,108 @@ function withBlankPages(sections, blankBefore) {
|
|
|
150
171
|
"aria-hidden": "true"
|
|
151
172
|
})), section.node]);
|
|
152
173
|
}
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
function takeHeldNotesOwner(build) {
|
|
175
|
+
build.heldNotesOwner -= 1;
|
|
176
|
+
return build.heldNotesOwner;
|
|
177
|
+
}
|
|
178
|
+
function pushChapterEndnotes(book, build, id, owner) {
|
|
179
|
+
if (book.endnotes.placement !== "chapter") return;
|
|
180
|
+
const paragraphs = build.notes.chapterEndnotes.get(owner) ?? [];
|
|
181
|
+
if (paragraphs.length === 0) return;
|
|
182
|
+
const runningHead = build.sections.at(-1)?.info.runningHead;
|
|
183
|
+
build.sections.push(endnotesSection(book, paragraphs, build.galley, id, runningHead, "any"));
|
|
184
|
+
}
|
|
185
|
+
function addFrontMatter(book, build) {
|
|
159
186
|
let contents;
|
|
187
|
+
let frontNumber = 0;
|
|
160
188
|
for (const entry of book.frontMatter) {
|
|
161
189
|
if (isBookContents(entry)) {
|
|
162
|
-
contents = contentsSection(entry.
|
|
163
|
-
sections.push(contents);
|
|
190
|
+
contents = contentsSection(entry, build.galley);
|
|
191
|
+
build.sections.push(contents);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (isDesignedSection(entry)) {
|
|
195
|
+
build.designedNumber += 1;
|
|
196
|
+
appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build.galley, build.notes, build.sections, build.headings, build.issues);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (isTemplatedSection(entry)) {
|
|
200
|
+
build.templatedNumber += 1;
|
|
201
|
+
appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build.galley, build.notes, build.templates, build.sections, build.headings, build.issues);
|
|
164
202
|
continue;
|
|
165
203
|
}
|
|
166
204
|
frontNumber += 1;
|
|
167
|
-
const section = authoredSection(entry, `segnavia-front-${frontNumber}
|
|
168
|
-
sections.push(section);
|
|
169
|
-
issues.push(...reservedIdIssues(section));
|
|
170
|
-
if (entry.contents && entry.title !== void 0) headings.push({
|
|
205
|
+
const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.galley);
|
|
206
|
+
build.sections.push(section);
|
|
207
|
+
build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
|
|
208
|
+
if (entry.contents && entry.title !== void 0) build.headings.push({
|
|
171
209
|
id: section.info.id,
|
|
172
210
|
level: 1,
|
|
173
211
|
text: entry.title,
|
|
174
212
|
sectionId: section.info.id
|
|
175
213
|
});
|
|
176
|
-
transformNotes(section.node, book, 0, notes);
|
|
214
|
+
transformNotes(section.node, book, 0, build.notes, build.galley);
|
|
177
215
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
216
|
+
return contents;
|
|
217
|
+
}
|
|
218
|
+
function addChapters(book, build) {
|
|
219
|
+
let chapterNumber = 0;
|
|
220
|
+
for (const entry of book.chapters) {
|
|
221
|
+
if (isDesignedSection(entry)) {
|
|
222
|
+
build.designedNumber += 1;
|
|
223
|
+
const owner = takeHeldNotesOwner(build);
|
|
224
|
+
appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build.galley, build.notes, build.sections, build.headings, build.issues, owner);
|
|
225
|
+
pushChapterEndnotes(book, build, `segnavia-endnotes-designed-${build.designedNumber}`, owner);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (isTemplatedSection(entry)) {
|
|
229
|
+
build.templatedNumber += 1;
|
|
230
|
+
const owner = takeHeldNotesOwner(build);
|
|
231
|
+
appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build.galley, build.notes, build.templates, build.sections, build.headings, build.issues, owner);
|
|
232
|
+
pushChapterEndnotes(book, build, `segnavia-endnotes-templated-${build.templatedNumber}`, owner);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
chapterNumber += 1;
|
|
236
|
+
const section = chapterSection(entry, chapterNumber, build.galley);
|
|
237
|
+
build.sections.push(section);
|
|
238
|
+
build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
|
|
239
|
+
build.headings.push({
|
|
184
240
|
id: section.info.id,
|
|
185
241
|
level: 1,
|
|
186
|
-
text:
|
|
242
|
+
text: entry.title,
|
|
187
243
|
sectionId: section.info.id
|
|
188
|
-
}, ...chapterHeadings(section,
|
|
189
|
-
transformNotes(section.node, book,
|
|
244
|
+
}, ...chapterHeadings(section, chapterNumber, book.contents.depth));
|
|
245
|
+
transformNotes(section.node, book, chapterNumber, build.notes, build.galley);
|
|
246
|
+
pushChapterEndnotes(book, build, `segnavia-endnotes-${chapterNumber}`, chapterNumber);
|
|
190
247
|
}
|
|
191
|
-
|
|
192
|
-
|
|
248
|
+
}
|
|
249
|
+
function bookDocument(book, options = {}) {
|
|
250
|
+
const build = {
|
|
251
|
+
galley: book.mode === "paginated",
|
|
252
|
+
notes: noteState(),
|
|
253
|
+
sections: [],
|
|
254
|
+
headings: [],
|
|
255
|
+
issues: [],
|
|
256
|
+
templates: new Map(book.templates.map((template) => [template.name, template])),
|
|
257
|
+
designedNumber: 0,
|
|
258
|
+
templatedNumber: 0,
|
|
259
|
+
heldNotesOwner: 0
|
|
260
|
+
};
|
|
261
|
+
const contents = addFrontMatter(book, build);
|
|
262
|
+
addChapters(book, build);
|
|
263
|
+
const { galley, notes, sections, headings, issues } = build;
|
|
264
|
+
if (book.endnotes.placement === "chapter") {
|
|
265
|
+
const leftover = notes.chapterEndnotes.get(0) ?? [];
|
|
266
|
+
if (leftover.length > 0) sections.push(endnotesSection(book, leftover, galley, "segnavia-endnotes", book.endnotes.title));
|
|
267
|
+
} else if (notes.endnotes) sections.push(endnotesSection(book, endnotesInOrder(notes), galley, "segnavia-endnotes", book.endnotes.title));
|
|
193
268
|
if (contents) addContents(book, contents, headings, options);
|
|
194
269
|
const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.kind) === options.part);
|
|
195
270
|
const document = parse("");
|
|
196
|
-
document.children = [
|
|
271
|
+
document.children = galley ? [element("style", {}, [text(GALLEY_CSS)]), element("div", { "data-segnavia-galleys": "" }, [
|
|
272
|
+
...templateHolders(book.templates),
|
|
273
|
+
...visible.map((section) => section.node),
|
|
274
|
+
...notes.footnoteParagraphs.length > 0 ? [notesGalley(notes.footnoteParagraphs)] : []
|
|
275
|
+
])] : [
|
|
197
276
|
element("style", {}, [text(PRINT_CSS)]),
|
|
198
277
|
...options.targets ? [targetLinks(visible)] : [],
|
|
199
278
|
...withBlankPages(visible, options.blankBefore)
|
|
@@ -208,6 +287,8 @@ function bookDocument(book, options = {}) {
|
|
|
208
287
|
footnotes: notes.footnotes,
|
|
209
288
|
endnotes: notes.endnotes
|
|
210
289
|
}),
|
|
290
|
+
...markPlacementIssues(book.parentPages),
|
|
291
|
+
...templateRegionNameIssues(book.templates),
|
|
211
292
|
...issues,
|
|
212
293
|
...duplicateIdIssues(sections, headings)
|
|
213
294
|
]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { elements, parse } from "../sanitize.js";
|
|
2
|
+
import { element, textContent } from "./dom.js";
|
|
3
|
+
import { BOOK_FURNITURE, MOTIF_ATTRIBUTE, REGION_NAME, motifFieldAttribute } from "@segnavia/format";
|
|
4
|
+
//#region src/book/book-held.ts
|
|
5
|
+
function flowAttribs(opens, parents, extra = {}) {
|
|
6
|
+
const attribs = { ...extra };
|
|
7
|
+
if (opens) attribs["data-opens"] = opens;
|
|
8
|
+
if (!parents) return attribs;
|
|
9
|
+
for (const role of [
|
|
10
|
+
"opener",
|
|
11
|
+
"recto",
|
|
12
|
+
"verso",
|
|
13
|
+
"blank",
|
|
14
|
+
"fixed"
|
|
15
|
+
]) {
|
|
16
|
+
const name = parents[role];
|
|
17
|
+
if (name) attribs[`data-parent-${role}`] = name;
|
|
18
|
+
}
|
|
19
|
+
return attribs;
|
|
20
|
+
}
|
|
21
|
+
function sectionNode(kind, id, children = [], galley = false, runningHead, extra = {}) {
|
|
22
|
+
const attribs = {
|
|
23
|
+
class: `segnavia-section segnavia-${kind}`,
|
|
24
|
+
id,
|
|
25
|
+
...extra
|
|
26
|
+
};
|
|
27
|
+
if (galley) attribs["data-segnavia-flow"] = id;
|
|
28
|
+
if (galley && runningHead !== void 0) attribs["data-running-head"] = runningHead;
|
|
29
|
+
return element("section", attribs, children);
|
|
30
|
+
}
|
|
31
|
+
function collectHeadings(children, sectionId, depth, firstLevel, idFor, generated) {
|
|
32
|
+
return elements(children).flatMap((heading) => {
|
|
33
|
+
const match = /^h([1-6])$/.exec(heading.name);
|
|
34
|
+
if (!match || heading.attribs["data-contents"] === "exclude") return [];
|
|
35
|
+
const level = Number(match[1]);
|
|
36
|
+
if (level < firstLevel || level > depth) return [];
|
|
37
|
+
generated.n += 1;
|
|
38
|
+
const id = heading.attribs.id ?? idFor(generated.n);
|
|
39
|
+
heading.attribs.id = id;
|
|
40
|
+
return [{
|
|
41
|
+
id,
|
|
42
|
+
level,
|
|
43
|
+
text: textContent(heading, true).replace(/\s+/g, " ").trim(),
|
|
44
|
+
sectionId
|
|
45
|
+
}];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function heldHeadings(children, sectionId, depth, generated) {
|
|
49
|
+
return collectHeadings(children, sectionId, depth, 1, (n) => `segnavia-h-${sectionId.replace(/^segnavia-/, "")}-${n}`, generated);
|
|
50
|
+
}
|
|
51
|
+
function designedSection(entry, id, kind, galley, depth) {
|
|
52
|
+
const extra = flowAttribs(entry.opens, entry.parents, {
|
|
53
|
+
"data-segnavia-kind": "fixed",
|
|
54
|
+
...kind === "front" ? { "data-sequence": "front" } : {}
|
|
55
|
+
});
|
|
56
|
+
const headings = [];
|
|
57
|
+
const generated = { n: 0 };
|
|
58
|
+
const holders = entry.pages.map((page, offset) => {
|
|
59
|
+
const document = parse(page.html);
|
|
60
|
+
headings.push(...heldHeadings(document.children, id, depth, generated));
|
|
61
|
+
return element("div", {
|
|
62
|
+
"data-segnavia-designed-page": String(offset + 1),
|
|
63
|
+
hidden: ""
|
|
64
|
+
}, document.children);
|
|
65
|
+
});
|
|
66
|
+
if (entry.contents && entry.title !== void 0) headings.unshift({
|
|
67
|
+
id,
|
|
68
|
+
level: 1,
|
|
69
|
+
text: entry.title,
|
|
70
|
+
sectionId: id
|
|
71
|
+
});
|
|
72
|
+
const runningHead = entry.title;
|
|
73
|
+
return {
|
|
74
|
+
built: {
|
|
75
|
+
info: {
|
|
76
|
+
id,
|
|
77
|
+
kind,
|
|
78
|
+
title: entry.title,
|
|
79
|
+
runningHead
|
|
80
|
+
},
|
|
81
|
+
node: sectionNode(kind, id, holders, galley, runningHead, extra)
|
|
82
|
+
},
|
|
83
|
+
headings
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function declaredRegions(html) {
|
|
87
|
+
return new Set(elements(parse(html).children).flatMap((node) => node.attribs["data-region"] ? [node.attribs["data-region"]] : []));
|
|
88
|
+
}
|
|
89
|
+
const regionNameClear = "use a letter followed by letters, digits, hyphens or underscores to clear this refusal";
|
|
90
|
+
function templateRegionNameIssues(templates) {
|
|
91
|
+
return templates.flatMap((template) => [...declaredRegions(template.html)].flatMap((key) => REGION_NAME.test(key) ? [] : [`template "${template.name}" marks region "${key}" which is not an HTML name token; ${regionNameClear}`]));
|
|
92
|
+
}
|
|
93
|
+
const markFurniture = BOOK_FURNITURE.find((item) => item.key === "segnavia:mark");
|
|
94
|
+
const markFields = new Set(markFurniture.fields.map((field) => motifFieldAttribute(field.key)));
|
|
95
|
+
const allowedMarkFields = markFurniture.fields.map((field) => field.key).join(" and ");
|
|
96
|
+
function markNodeIssues(parent, node) {
|
|
97
|
+
if (node.attribs[MOTIF_ATTRIBUTE] !== markFurniture.key) return [];
|
|
98
|
+
const issues = Object.keys(node.attribs).filter((attribute) => attribute.startsWith(`${MOTIF_ATTRIBUTE}-`) && !markFields.has(attribute)).map((attribute) => {
|
|
99
|
+
const field = attribute.slice(`${MOTIF_ATTRIBUTE}-`.length);
|
|
100
|
+
return `parent page "${parent.name}" mark has unknown field "${field}"; use only ${allowedMarkFields} to clear this refusal`;
|
|
101
|
+
});
|
|
102
|
+
for (const field of markFurniture.fields) {
|
|
103
|
+
const attribute = motifFieldAttribute(field.key);
|
|
104
|
+
const value = node.attribs[attribute] ?? ("default" in field ? field.default : void 0);
|
|
105
|
+
if ("required" in field && field.required && (value === void 0 || value.trim() === "")) issues.push(`parent page "${parent.name}" mark has an empty ${field.key}; set a non-empty ${attribute} on the segnavia:mark placement to clear this refusal`);
|
|
106
|
+
if ("choices" in field && value !== void 0) {
|
|
107
|
+
const choices = field.choices;
|
|
108
|
+
if (!choices.includes(value)) issues.push(`parent page "${parent.name}" mark has unknown ${field.key} value "${value}"; use ${choices.join(" or ")} to clear this refusal`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return issues;
|
|
112
|
+
}
|
|
113
|
+
function markPlacementIssues(parents) {
|
|
114
|
+
return parents.flatMap((parent) => ["head", "foot"].flatMap((region) => {
|
|
115
|
+
const html = parent[region];
|
|
116
|
+
if (html === void 0) return [];
|
|
117
|
+
return elements(parse(html).children).flatMap((node) => markNodeIssues(parent, node));
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
function undeclaredRegionIssues(entry, id, templates) {
|
|
121
|
+
const label = entry.title ?? id;
|
|
122
|
+
return entry.pages.flatMap((page, index) => {
|
|
123
|
+
const name = page.template ?? entry.template;
|
|
124
|
+
const template = templates.get(name);
|
|
125
|
+
if (!template) return [];
|
|
126
|
+
const declared = declaredRegions(template.html);
|
|
127
|
+
return Object.keys(page.regions).flatMap((region) => declared.has(region) ? [] : [`section ${label} page ${index + 1} fills region "${region}" which template "${name}" does not declare; add that region to the template or remove the fill to clear this refusal`]);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function templatedSection(entry, id, kind, galley, depth) {
|
|
131
|
+
const extra = flowAttribs(entry.opens, entry.parents, {
|
|
132
|
+
"data-segnavia-kind": "templated",
|
|
133
|
+
"data-template": entry.template,
|
|
134
|
+
...kind === "front" ? { "data-sequence": "front" } : {}
|
|
135
|
+
});
|
|
136
|
+
const headings = [];
|
|
137
|
+
const generated = { n: 0 };
|
|
138
|
+
const holders = entry.pages.map((page, offset) => {
|
|
139
|
+
const name = page.template ?? entry.template;
|
|
140
|
+
const regions = Object.entries(page.regions).map(([region, html]) => {
|
|
141
|
+
const document = parse(html);
|
|
142
|
+
headings.push(...heldHeadings(document.children, id, depth, generated));
|
|
143
|
+
return element("div", {
|
|
144
|
+
"data-region": region,
|
|
145
|
+
hidden: ""
|
|
146
|
+
}, document.children);
|
|
147
|
+
});
|
|
148
|
+
return element("div", {
|
|
149
|
+
"data-segnavia-templated-page": String(offset + 1),
|
|
150
|
+
"data-template": name,
|
|
151
|
+
hidden: ""
|
|
152
|
+
}, regions);
|
|
153
|
+
});
|
|
154
|
+
if (entry.contents && entry.title !== void 0) headings.unshift({
|
|
155
|
+
id,
|
|
156
|
+
level: 1,
|
|
157
|
+
text: entry.title,
|
|
158
|
+
sectionId: id
|
|
159
|
+
});
|
|
160
|
+
const runningHead = entry.title;
|
|
161
|
+
return {
|
|
162
|
+
built: {
|
|
163
|
+
info: {
|
|
164
|
+
id,
|
|
165
|
+
kind,
|
|
166
|
+
title: entry.title,
|
|
167
|
+
runningHead
|
|
168
|
+
},
|
|
169
|
+
node: sectionNode(kind, id, holders, galley, runningHead, extra)
|
|
170
|
+
},
|
|
171
|
+
headings
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function templateHolders(templates) {
|
|
175
|
+
return templates.map((template) => element("div", {
|
|
176
|
+
"data-segnavia-template": template.name,
|
|
177
|
+
hidden: "",
|
|
178
|
+
...flowAttribs(void 0, template.parents)
|
|
179
|
+
}, parse(template.html).children));
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { collectHeadings, designedSection, flowAttribs, markPlacementIssues, sectionNode, templateHolders, templateRegionNameIssues, templatedSection, undeclaredRegionIssues };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { bookDocument } from "./book-document.js";
|
|
2
|
+
import { resolveBookPagination } from "@segnavia/format";
|
|
3
|
+
//#region src/book/book-view.ts
|
|
4
|
+
function bookViewFromHtml(book, html) {
|
|
5
|
+
const view = {
|
|
6
|
+
html,
|
|
7
|
+
title: book.title,
|
|
8
|
+
scripts: book.scripts,
|
|
9
|
+
...book.dir === void 0 ? {} : { dir: book.dir },
|
|
10
|
+
...book.lang === void 0 ? {} : { lang: book.lang },
|
|
11
|
+
...book.fonts === void 0 ? {} : { fonts: book.fonts },
|
|
12
|
+
...book.css === void 0 ? {} : { css: book.css }
|
|
13
|
+
};
|
|
14
|
+
const pagination = resolveBookPagination(book);
|
|
15
|
+
return pagination ? {
|
|
16
|
+
...view,
|
|
17
|
+
pagination
|
|
18
|
+
} : view;
|
|
19
|
+
}
|
|
20
|
+
function bookView(book) {
|
|
21
|
+
const document = bookDocument(book);
|
|
22
|
+
if (document.issues.length > 0) throw new Error(`The book cannot be shown: ${document.issues.join("; ")}.`);
|
|
23
|
+
return bookViewFromHtml(book, document.html);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { bookView, bookViewFromHtml };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PaginatePageMarks } from "./paginate-types.js";
|
|
2
|
+
//#region src/book/furniture.d.ts
|
|
3
|
+
export interface BookFurnitureValues {
|
|
4
|
+
folio?: string;
|
|
5
|
+
runningHead?: string;
|
|
6
|
+
marks?: PaginatePageMarks;
|
|
7
|
+
}
|
|
8
|
+
export declare function fillBookFurniture(html: string, values: BookFurnitureValues, dir?: string, lang?: string): string;
|
|
9
|
+
export declare function embedFunction(name: string, fn: {
|
|
10
|
+
name: string;
|
|
11
|
+
toString(): string;
|
|
12
|
+
}): string;
|
|
13
|
+
export declare function furnitureScript(): string;
|
|
14
|
+
//#endregion
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveDirection, textDirection } from "@segnavia/format";
|
|
2
|
+
//#region src/book/furniture.ts
|
|
3
|
+
function fillBookFurniture(html, values, dir, lang) {
|
|
4
|
+
const motifField = {
|
|
5
|
+
"segnavia:folio": "folio",
|
|
6
|
+
"segnavia:running-head": "runningHead"
|
|
7
|
+
};
|
|
8
|
+
const declared = dir === "ltr" || dir === "rtl" || dir === "auto" ? dir : void 0;
|
|
9
|
+
function attribute(attrs, name) {
|
|
10
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11
|
+
const found = new RegExp(`\\s${escaped}\\s*=\\s*(?:(["'])([^"']*)\\1|([^\\s>]+))`, "i").exec(attrs);
|
|
12
|
+
return found?.[2] ?? found?.[3];
|
|
13
|
+
}
|
|
14
|
+
function escapeHtml(value) {
|
|
15
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
16
|
+
}
|
|
17
|
+
return html.replace(/<([a-zA-Z][\w:-]*)([^>]*?)\sdata-motif\s*=\s*(?:(["'])([^"']+)\3|([^\s>]+))([^>]*)>([\s\S]*?)<\/\1>/gi, (match, tag, before, _quote, quoted, unquoted, after) => {
|
|
18
|
+
const key = quoted ?? unquoted;
|
|
19
|
+
const field = motifField[key];
|
|
20
|
+
if (!field && key !== "segnavia:mark") return match;
|
|
21
|
+
const attrs = `${before}${after}`;
|
|
22
|
+
const name = key === "segnavia:mark" ? attribute(attrs, "data-motif-name") : void 0;
|
|
23
|
+
const which = key === "segnavia:mark" && attribute(attrs, "data-motif-which") === "last" ? "last" : "first";
|
|
24
|
+
const value = field ? values[field] : name === void 0 ? void 0 : values.marks?.[name]?.[which];
|
|
25
|
+
if (value === void 0) return "";
|
|
26
|
+
return `<${tag}${attrs.replace(/\sdata-motif(?:-[\w-]+)?\s*=\s*(?:(["'])([^"']*)\1|[^\s>]+)/gi, "")}><bdi dir="${resolveDirection(declared, lang, value)}">${escapeHtml(value)}</bdi></${tag}>`;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function embedFunction(name, fn) {
|
|
30
|
+
return `var ${fn.name}=${fn.toString()};var ${name}=${fn.name};`;
|
|
31
|
+
}
|
|
32
|
+
function furnitureScript() {
|
|
33
|
+
return `${embedFunction("textDirection", textDirection)}\n${embedFunction("resolveDirection", resolveDirection)}\n${embedFunction("fillBookFurniture", fillBookFurniture)}`;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { embedFunction, fillBookFurniture, furnitureScript };
|
package/dist/book/notes.js
CHANGED
|
@@ -37,9 +37,13 @@ function noteState() {
|
|
|
37
37
|
globalFootnote: 0,
|
|
38
38
|
chapterFootnotes: /* @__PURE__ */ new Map(),
|
|
39
39
|
endnoteCount: 0,
|
|
40
|
-
|
|
40
|
+
chapterEndnotes: /* @__PURE__ */ new Map(),
|
|
41
|
+
footnoteParagraphs: []
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
function endnotesInOrder(state) {
|
|
45
|
+
return [...state.chapterEndnotes.values()].flat();
|
|
46
|
+
}
|
|
43
47
|
function nearestBlock(note) {
|
|
44
48
|
for (let parent = note.parent; parent; parent = parent.parent) if (isTag(parent) && BLOCKS.has(parent.name)) return parent;
|
|
45
49
|
}
|
|
@@ -79,7 +83,7 @@ function addFootnoteBlock(block, paragraph, blocks) {
|
|
|
79
83
|
if (INSIDE_BLOCKS.has(block.name)) append(block, notes);
|
|
80
84
|
else insertAfter(block, notes);
|
|
81
85
|
}
|
|
82
|
-
function transformFootnote(note, book, chapter, state, blocks) {
|
|
86
|
+
function transformFootnote(note, book, chapter, state, blocks, galley) {
|
|
83
87
|
const block = nearestBlock(note);
|
|
84
88
|
if (!block) return;
|
|
85
89
|
const number = footnoteNumber(book, chapter, state);
|
|
@@ -88,10 +92,12 @@ function transformFootnote(note, book, chapter, state, blocks) {
|
|
|
88
92
|
const marker = noteMarker(number, book.footnotes.marker);
|
|
89
93
|
const content = [...note.children];
|
|
90
94
|
replace(note, element("sup", { class: "segnavia-note-ref" }, [markerLink(refId, noteId, marker)]));
|
|
91
|
-
|
|
95
|
+
const paragraph = noteParagraph("segnavia-footnote", noteId, refId, marker, content);
|
|
96
|
+
if (galley) state.footnoteParagraphs.push(paragraph);
|
|
97
|
+
else addFootnoteBlock(block, paragraph, blocks);
|
|
92
98
|
state.footnotes = true;
|
|
93
99
|
}
|
|
94
|
-
function transformEndnote(note, book, state) {
|
|
100
|
+
function transformEndnote(note, book, state, owner) {
|
|
95
101
|
state.endnoteCount += 1;
|
|
96
102
|
const number = state.endnoteCount;
|
|
97
103
|
const noteId = `segnavia-en-${number}`;
|
|
@@ -99,14 +105,17 @@ function transformEndnote(note, book, state) {
|
|
|
99
105
|
const marker = noteMarker(number, book.endnotes.marker);
|
|
100
106
|
const content = [...note.children];
|
|
101
107
|
replace(note, element("sup", { class: "segnavia-note-ref" }, [markerLink(refId, noteId, marker)]));
|
|
102
|
-
|
|
108
|
+
const paragraph = noteParagraph("segnavia-endnote", noteId, refId, marker, content);
|
|
109
|
+
const held = state.chapterEndnotes.get(owner) ?? [];
|
|
110
|
+
held.push(paragraph);
|
|
111
|
+
state.chapterEndnotes.set(owner, held);
|
|
103
112
|
state.endnotes = true;
|
|
104
113
|
}
|
|
105
|
-
function transformNotes(root, book, chapter, state) {
|
|
114
|
+
function transformNotes(root, book, chapter, state, galley = false, notesOwner = chapter) {
|
|
106
115
|
const blocks = /* @__PURE__ */ new Map();
|
|
107
116
|
const notes = elements(root.children).filter((node) => node.name === "span" && (node.attribs["data-note"] === "footnote" || node.attribs["data-note"] === "endnote"));
|
|
108
|
-
for (const note of notes) if (note.attribs["data-note"] === "footnote") transformFootnote(note, book, chapter, state, blocks);
|
|
109
|
-
else transformEndnote(note, book, state);
|
|
117
|
+
for (const note of notes) if (note.attribs["data-note"] === "footnote") transformFootnote(note, book, chapter, state, blocks, galley);
|
|
118
|
+
else transformEndnote(note, book, state, notesOwner);
|
|
110
119
|
}
|
|
111
120
|
//#endregion
|
|
112
|
-
export { noteState, transformNotes };
|
|
121
|
+
export { endnotesInOrder, noteState, transformNotes };
|