@readium/navigator 2.11.0 → 2.12.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/divina/DivinaNavigator.js +1 -1
- package/dist/epub/EpubNavigator.js +1 -1
- package/dist/epub/frame/FrameComms.js +1 -1
- package/dist/epub/frame/FrameManager.js +1 -1
- package/dist/epub/frame/FramePoolManager.js +1 -1
- package/dist/epub/fxl/FXLFrameManager.js +1 -1
- package/dist/helpers/serialize.js +1 -0
- package/dist/index.js +1 -1
- package/dist/preferences/guards.js +1 -1
- package/dist/readaloud/GuidedNavigationProvider.js +1 -0
- package/dist/readaloud/ReadAloudNavigator.js +1 -0
- package/dist/readaloud/ReadingUnit.js +1 -0
- package/dist/readaloud/SpeechProgress.js +1 -0
- package/dist/readaloud/preferences/ReadAloudDefaults.js +1 -0
- package/dist/readaloud/preferences/ReadAloudPreferences.js +1 -0
- package/dist/readaloud/preferences/ReadAloudPreferencesEditor.js +1 -0
- package/dist/readaloud/preferences/ReadAloudSettings.js +1 -0
- package/dist/webpub/WebPubFrameManager.js +1 -1
- package/dist/webpub/WebPubFramePoolManager.js +1 -1
- package/dist/webpub/WebPubNavigator.js +1 -1
- package/package.json +8 -6
- package/src/divina/DivinaNavigator.ts +1 -0
- package/src/epub/EpubNavigator.ts +127 -21
- package/src/epub/frame/FrameComms.ts +42 -7
- package/src/epub/frame/FrameManager.ts +20 -7
- package/src/epub/frame/FramePoolManager.ts +5 -0
- package/src/epub/fxl/FXLFrameManager.ts +30 -8
- package/src/helpers/serialize.ts +9 -0
- package/src/index.ts +1 -0
- package/src/preferences/guards.ts +11 -0
- package/src/readaloud/GuidedNavigationProvider.ts +87 -0
- package/src/readaloud/ReadAloudNavigator.ts +949 -0
- package/src/readaloud/ReadingUnit.ts +154 -0
- package/src/readaloud/SpeechProgress.ts +60 -0
- package/src/readaloud/index.ts +17 -0
- package/src/readaloud/preferences/ReadAloudDefaults.ts +24 -0
- package/src/readaloud/preferences/ReadAloudPreferences.ts +104 -0
- package/src/readaloud/preferences/ReadAloudPreferencesEditor.ts +115 -0
- package/src/readaloud/preferences/ReadAloudSettings.ts +44 -0
- package/src/readaloud/preferences/index.ts +4 -0
- package/src/webpub/WebPubFrameManager.ts +24 -7
- package/src/webpub/WebPubFramePoolManager.ts +5 -0
- package/src/webpub/WebPubNavigator.ts +114 -10
- package/types/src/epub/EpubNavigator.d.ts +26 -1
- package/types/src/epub/frame/FrameComms.d.ts +8 -1
- package/types/src/epub/frame/FrameManager.d.ts +3 -0
- package/types/src/epub/frame/FramePoolManager.d.ts +2 -0
- package/types/src/epub/fxl/FXLFrameManager.d.ts +5 -0
- package/types/src/helpers/serialize.d.ts +1 -0
- package/types/src/index.d.ts +1 -0
- package/types/src/preferences/guards.d.ts +1 -0
- package/types/src/readaloud/GuidedNavigationProvider.d.ts +28 -0
- package/types/src/readaloud/ReadAloudNavigator.d.ts +158 -0
- package/types/src/readaloud/ReadingUnit.d.ts +58 -0
- package/types/src/readaloud/SpeechProgress.d.ts +19 -0
- package/types/src/readaloud/index.d.ts +6 -0
- package/types/src/readaloud/preferences/ReadAloudDefaults.d.ts +11 -0
- package/types/src/readaloud/preferences/ReadAloudPreferences.d.ts +58 -0
- package/types/src/readaloud/preferences/ReadAloudPreferencesEditor.d.ts +37 -0
- package/types/src/readaloud/preferences/ReadAloudSettings.d.ts +26 -0
- package/types/src/readaloud/preferences/index.d.ts +4 -0
- package/types/src/webpub/WebPubFrameManager.d.ts +4 -0
- package/types/src/webpub/WebPubFramePoolManager.d.ts +2 -0
- package/types/src/webpub/WebPubNavigator.d.ts +25 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Returns a function that runs operations one after another, even when one rejects
|
|
2
|
+
export function createSerializer() {
|
|
3
|
+
let queue: Promise<unknown> = Promise.resolve();
|
|
4
|
+
return <T>(operation: () => Promise<T>): Promise<T> => {
|
|
5
|
+
const result = queue.then(operation);
|
|
6
|
+
queue = result.catch(() => {});
|
|
7
|
+
return result;
|
|
8
|
+
};
|
|
9
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -82,6 +82,17 @@ export function ensureValueInRange(value: number | null | undefined, range: [num
|
|
|
82
82
|
return value >= min && value <= max ? value : undefined;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export function ensureDecorationStyle<T extends object>(value: T | false | null | undefined): T | false | null | undefined {
|
|
86
|
+
if (value === undefined || value === null || value === false) {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value !== "object") {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const type = (value as { type?: unknown }).type;
|
|
93
|
+
return type === undefined || typeof type === "string" ? value : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
export function withFallback<T>(value: T | null | undefined, defaultValue: T | null): T | null {
|
|
86
97
|
return value === undefined ? defaultValue : value;
|
|
87
98
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { GuidedNavigationObject, Link, Publication } from "@readium/shared";
|
|
2
|
+
import { decodeTextref, makeGnd } from "@readium/guided-navigation";
|
|
3
|
+
|
|
4
|
+
export interface GuidedNavigationProvider {
|
|
5
|
+
/**
|
|
6
|
+
* Guided Navigation objects of a resource, with every textref qualified by the resource's href.
|
|
7
|
+
* Resolves to undefined when the resource has nothing to read.
|
|
8
|
+
*/
|
|
9
|
+
guideFor(link: Link): Promise<GuidedNavigationObject[] | undefined>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PublicationGuidedNavigationProviderOptions {
|
|
13
|
+
/** Generates from the resource's markup without using the publication's Guided Navigation document. */
|
|
14
|
+
generateFromMarkup?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Uses the publication's Guided Navigation document when its text can be located in the resource,
|
|
19
|
+
* and generates one from the resource's markup otherwise, or always with `generateFromMarkup`.
|
|
20
|
+
* Only the objects of the resource are kept from a document covering the whole publication.
|
|
21
|
+
*/
|
|
22
|
+
export class PublicationGuidedNavigationProvider implements GuidedNavigationProvider {
|
|
23
|
+
constructor(private readonly publication: Publication, private readonly options: PublicationGuidedNavigationProviderOptions = {}) {}
|
|
24
|
+
|
|
25
|
+
async guideFor(link: Link): Promise<GuidedNavigationObject[] | undefined> {
|
|
26
|
+
const href = link.href.split("#")[0];
|
|
27
|
+
if (!this.options.generateFromMarkup) {
|
|
28
|
+
const document = await this.publication.guideForLink(link, { skipAudio: true }).catch(() => undefined);
|
|
29
|
+
if (document) {
|
|
30
|
+
const guided = this.objectsIn(document.guided, href);
|
|
31
|
+
if (guided.length > 0 && this.isLocatable(guided)) return guided;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return this.generate(link, href);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async generate(link: Link, href: string): Promise<GuidedNavigationObject[] | undefined> {
|
|
38
|
+
const resource = this.publication.get(link);
|
|
39
|
+
try {
|
|
40
|
+
const resourceLink = await resource.link();
|
|
41
|
+
if (!resourceLink.mediaType.isHTML) return undefined;
|
|
42
|
+
const doc = await resource.readAsXML();
|
|
43
|
+
if (!doc || doc.getElementsByTagNameNS("*", "parsererror").length > 0) return undefined;
|
|
44
|
+
const body = doc.querySelector("body");
|
|
45
|
+
if (!body) return undefined;
|
|
46
|
+
const mediaType = resourceLink.mediaType.essence === "text/html" ? "text/html" : "application/xhtml+xml";
|
|
47
|
+
return makeGnd(body, mediaType, { textrefs: { roles: true }, href })?.guided;
|
|
48
|
+
} finally {
|
|
49
|
+
resource.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Objects without a resource of their own belong to their parent's, and are kept when that is `href` or unknown.
|
|
54
|
+
private objectsIn(objects: GuidedNavigationObject[], href: string, parentHref?: string): GuidedNavigationObject[] {
|
|
55
|
+
return objects.flatMap(object => {
|
|
56
|
+
const objectHref = this.resourceOf(object) ?? parentHref;
|
|
57
|
+
const children = object.children ? this.objectsIn(object.children, href, objectHref) : [];
|
|
58
|
+
if (objectHref !== undefined && objectHref !== href) return children;
|
|
59
|
+
if (children.length === 0 && !object.text && !object.textref && !object.imgref) return [];
|
|
60
|
+
// A fragment-only textref, as a resource's own document may use, is in that resource.
|
|
61
|
+
const textref = object.textref?.startsWith("#") ? href + object.textref : object.textref;
|
|
62
|
+
return [new GuidedNavigationObject({ ...object, textref, children: children.length > 0 ? children : undefined })];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private resourceOf(object: GuidedNavigationObject): string | undefined {
|
|
67
|
+
const textHref = object.textref?.split("#")[0];
|
|
68
|
+
if (textHref) return textHref;
|
|
69
|
+
return this.imageOf(object);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// An imgref only names a resource when it's an image of the reading order, as in Divina.
|
|
73
|
+
private imageOf(object: GuidedNavigationObject): string | undefined {
|
|
74
|
+
const href = object.imgref?.split("#")[0];
|
|
75
|
+
return href && this.publication.readingOrder.findWithHref(href) ? href : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Every object with text must be locatable by its own ref or an ancestor's, or following would miss it.
|
|
79
|
+
private isLocatable(objects: GuidedNavigationObject[], ancestorLocatable = false): boolean {
|
|
80
|
+
return objects.every(object => {
|
|
81
|
+
const own = object.textref ? decodeTextref(object) !== undefined : this.imageOf(object) !== undefined;
|
|
82
|
+
const locatable = ancestorLocatable || own;
|
|
83
|
+
if (object.text && !locatable) return false;
|
|
84
|
+
return !object.children || this.isLocatable(object.children, locatable);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|