bookish-press 0.9.2 → 0.9.3
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/epub/build.test.js +42 -0
- package/dist/epub/package.js +17 -1
- package/dist/epub/xhtml.d.ts +6 -0
- package/dist/epub/xhtml.js +12 -0
- package/package.json +1 -1
package/dist/epub/build.test.js
CHANGED
|
@@ -409,3 +409,45 @@ test('a reference whose fields are not strings still exports', async () => {
|
|
|
409
409
|
expect(references).toContain('-370');
|
|
410
410
|
expect(references).toContain('The Republic');
|
|
411
411
|
});
|
|
412
|
+
test("a chapter's header image is rendered, not just packaged", async () => {
|
|
413
|
+
// Edition.getEmbeds() collects chapter header images, so they were fetched
|
|
414
|
+
// and packaged, but chapterDocument never rendered one -- the reader saw
|
|
415
|
+
// the book cover and nothing else, and paid for the bytes anyway.
|
|
416
|
+
const edition = makeEdition({
|
|
417
|
+
chapters: [
|
|
418
|
+
chapter('one', 'One', 'Text with no images of its own.', {
|
|
419
|
+
image: '|header.jpg|A header image|A caption|A credit|',
|
|
420
|
+
}),
|
|
421
|
+
],
|
|
422
|
+
});
|
|
423
|
+
const { entries } = await build(edition);
|
|
424
|
+
const one = textOf(entries, 'OEBPS/one.xhtml');
|
|
425
|
+
expect(one).toContain('<img');
|
|
426
|
+
expect(one).toContain('A header image');
|
|
427
|
+
// Above the title, as the web reader places it.
|
|
428
|
+
expect(one.indexOf('<img')).toBeLessThan(one.indexOf('<h1>'));
|
|
429
|
+
});
|
|
430
|
+
test('every packaged image is referenced by a document', async () => {
|
|
431
|
+
// The general form of the bug above: an image that is fetched, shrunk and
|
|
432
|
+
// written into the package but never pointed at is pure weight.
|
|
433
|
+
const edition = makeEdition({
|
|
434
|
+
images: { cover: '|cover.jpg|The cover|||' },
|
|
435
|
+
chapters: [
|
|
436
|
+
chapter('one', 'One', 'A body image: |body.jpg|Alt|||', {
|
|
437
|
+
image: '|chapterhead.jpg|A chapter header|||',
|
|
438
|
+
}),
|
|
439
|
+
chapter('two', 'Two', 'No images here.', {
|
|
440
|
+
image: '|another.jpg|Another header|||',
|
|
441
|
+
}),
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
const { entries } = await build(edition);
|
|
445
|
+
const documents = [...entries.keys()].filter((path) => path.endsWith('.xhtml'));
|
|
446
|
+
const markup = documents.map((path) => textOf(entries, path)).join('\n');
|
|
447
|
+
const images = [...entries.keys()]
|
|
448
|
+
.filter((path) => path.startsWith('OEBPS/images/'))
|
|
449
|
+
.map((path) => path.replace('OEBPS/', ''));
|
|
450
|
+
expect(images.length).toBeGreaterThan(2);
|
|
451
|
+
const orphans = images.filter((image) => !markup.includes(`"${image}"`));
|
|
452
|
+
expect(orphans, `packaged but never referenced: ${orphans.join(', ')}`).toEqual([]);
|
|
453
|
+
});
|
package/dist/epub/package.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import Reference from '../models/book/Reference';
|
|
8
8
|
import FormatNode from '../models/chapter/FormatNode';
|
|
9
9
|
import Parser from '../models/chapter/Parser';
|
|
10
|
-
import
|
|
10
|
+
import EmbedNode from '../models/chapter/EmbedNode';
|
|
11
|
+
import { chapterPath, document_, endnoteSection, escapeAttribute, escapeText, serializeChapter, serializeEmbed, serializeFormat, } from './xhtml';
|
|
11
12
|
export const MIMETYPE = 'application/epub+zip';
|
|
12
13
|
/** Where the package's documents live inside the archive. */
|
|
13
14
|
export const CONTENT_DIRECTORY = 'OEBPS';
|
|
@@ -149,6 +150,18 @@ export function safeFilename(title) {
|
|
|
149
150
|
.toLowerCase();
|
|
150
151
|
return cleaned.length > 0 ? cleaned.slice(0, 80) : 'book';
|
|
151
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* A chapter or back-matter page's header image, rendered the way the web
|
|
155
|
+
* reader does: above the title, with its caption and credit. The image is
|
|
156
|
+
* stored as Bookdown and already packaged by Edition.getEmbeds(), so this only
|
|
157
|
+
* has to render it.
|
|
158
|
+
*/
|
|
159
|
+
function headerImage(edition, image, context) {
|
|
160
|
+
if (image === null || image.trim().length === 0)
|
|
161
|
+
return '';
|
|
162
|
+
const node = Parser.parseEmbed(edition, image);
|
|
163
|
+
return node instanceof EmbedNode ? serializeEmbed(node, context) : '';
|
|
164
|
+
}
|
|
152
165
|
/** The plain-text form of a Bookdown string, for metadata. */
|
|
153
166
|
function text(edition, bookdown) {
|
|
154
167
|
return Parser.parseFormat(edition, bookdown).toText().trim();
|
|
@@ -203,6 +216,7 @@ export function chapterDocument(edition, chapter, context) {
|
|
|
203
216
|
const section = chapter.getSection();
|
|
204
217
|
const authors = chapter.getAuthors();
|
|
205
218
|
const header = `<header>
|
|
219
|
+
${headerImage(edition, chapter.getImage(), context)}
|
|
206
220
|
${number !== undefined
|
|
207
221
|
? `<span class="chapter-number">Chapter ${number}</span>`
|
|
208
222
|
: ''}
|
|
@@ -242,6 +256,7 @@ export function referencesDocument(edition, context) {
|
|
|
242
256
|
return undefined;
|
|
243
257
|
const header = edition.getHeader('references');
|
|
244
258
|
const body = `<section epub:type="bibliography">
|
|
259
|
+
${headerImage(edition, edition.getImage('references'), context)}
|
|
245
260
|
<h1>${escapeText(header)}</h1>
|
|
246
261
|
${ids
|
|
247
262
|
.map((id) => `<p class="reference" id="ref-${escapeAttribute(id)}">${referenceContent(references[id], context)}</p>`)
|
|
@@ -267,6 +282,7 @@ export function glossaryDocument(edition, context) {
|
|
|
267
282
|
// content, so there is no way to put a block inside it that would survive.
|
|
268
283
|
// The semantics are carried by the classes instead.
|
|
269
284
|
const body = `<section epub:type="glossary">
|
|
285
|
+
${headerImage(edition, edition.getImage('glossary'), context)}
|
|
270
286
|
<h1>${escapeText(header)}</h1>
|
|
271
287
|
${ids
|
|
272
288
|
.map((id) => {
|
package/dist/epub/xhtml.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type Edition from '../models/book/Edition';
|
|
15
15
|
import type ChapterNode from '../models/chapter/ChapterNode';
|
|
16
|
+
import EmbedNode from '../models/chapter/EmbedNode';
|
|
16
17
|
import FormatNode from '../models/chapter/FormatNode';
|
|
17
18
|
/**
|
|
18
19
|
* Escape text content. Order matters: ampersands first, or we escape our own
|
|
@@ -56,6 +57,11 @@ export declare function serializeChapter(chapter: ChapterNode, context: Serializ
|
|
|
56
57
|
};
|
|
57
58
|
/** The document filename for a chapter. */
|
|
58
59
|
export declare function chapterPath(chapterID: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Serialize a standalone embed, for the header images that a chapter and the
|
|
62
|
+
* back matter pages carry. Those are stored as Bookdown, parsed by the caller.
|
|
63
|
+
*/
|
|
64
|
+
export declare function serializeEmbed(node: EmbedNode, context: SerializationContext): string;
|
|
59
65
|
/** Serialize a standalone format node, for titles, captions and definitions. */
|
|
60
66
|
export declare function serializeFormat(node: FormatNode, context: SerializationContext): string;
|
|
61
67
|
/** Render gathered footnotes as an endnote section with links back to the text. */
|
package/dist/epub/xhtml.js
CHANGED
|
@@ -326,6 +326,18 @@ function definition(node, local) {
|
|
|
326
326
|
? phrase
|
|
327
327
|
: `<a class="definition" href="glossary.xhtml#gloss-${escapeAttribute(id)}">${phrase}</a>`;
|
|
328
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Serialize a standalone embed, for the header images that a chapter and the
|
|
331
|
+
* back matter pages carry. Those are stored as Bookdown, parsed by the caller.
|
|
332
|
+
*/
|
|
333
|
+
export function serializeEmbed(node, context) {
|
|
334
|
+
return embed(node, {
|
|
335
|
+
...context,
|
|
336
|
+
footnotes: [],
|
|
337
|
+
headers: [],
|
|
338
|
+
endnotes: [],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
329
341
|
/** Serialize a standalone format node, for titles, captions and definitions. */
|
|
330
342
|
export function serializeFormat(node, context) {
|
|
331
343
|
return inline(node, {
|