@readium/navigator 2.5.8 → 2.6.1
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/index.js +2573 -1952
- package/dist/index.umd.cjs +41 -40
- package/package.json +1 -2
- package/src/decorations/index.ts +148 -0
- package/src/epub/EpubNavigator.ts +184 -8
- package/src/epub/frame/FrameManager.ts +1 -1
- package/src/epub/fxl/FXLCoordinator.ts +1 -1
- package/src/epub/preferences/EpubDefaults.ts +1 -1
- package/src/epub/preferences/EpubSettings.ts +1 -1
- package/src/helpers/index.ts +0 -1
- package/src/index.ts +1 -0
- package/src/protection/DevToolsDetector.ts +1 -1
- package/src/webpub/WebPubFrameManager.ts +1 -1
- package/src/webpub/WebPubNavigator.ts +149 -5
- package/src/webpub/preferences/WebPubDefaults.ts +1 -1
- package/src/webpub/preferences/WebPubSettings.ts +1 -1
- package/types/src/decorations/index.d.ts +75 -0
- package/types/src/epub/EpubNavigator.d.ts +18 -2
- package/types/src/helpers/index.d.ts +0 -1
- package/types/src/index.d.ts +1 -0
- package/types/src/webpub/WebPubNavigator.d.ts +16 -1
- package/src/helpers/sML.ts +0 -143
- package/types/src/helpers/sML.d.ts +0 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readium/navigator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Next generation SDK for publications in Web Apps",
|
|
6
6
|
"author": "readium",
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
"tslib": "^2.8.1",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
62
|
"typescript-plugin-css-modules": "^5.2.0",
|
|
63
|
-
"user-agent-data-types": "^0.4.2",
|
|
64
63
|
"vite": "^7.3.1"
|
|
65
64
|
}
|
|
66
65
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Decoration as InjectableDecoration,
|
|
3
|
+
BuiltinDecorationStyle,
|
|
4
|
+
HTMLDecorationTemplate as WireHTMLDecorationTemplate,
|
|
5
|
+
} from "@readium/navigator-html-injectables";
|
|
6
|
+
import { DecorationStyleType } from "@readium/navigator-html-injectables";
|
|
7
|
+
|
|
8
|
+
export type { BuiltinDecorationStyle };
|
|
9
|
+
export { DecorationLayout, DecorationStyleType, DecorationWidth } from "@readium/navigator-html-injectables";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Navigator-level decoration template. `element` is a function called once per decoration to
|
|
13
|
+
* generate the HTML string that the injectable will render. The result is resolved on the
|
|
14
|
+
* navigator side (before postMessage) and sanitized by the injectable.
|
|
15
|
+
*/
|
|
16
|
+
export interface HTMLDecorationTemplate extends Omit<WireHTMLDecorationTemplate, 'element'> {
|
|
17
|
+
element: (decoration: Decoration) => string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DecorationStyle = BuiltinDecorationStyle | HTMLDecorationTemplate;
|
|
21
|
+
|
|
22
|
+
export interface Decoration extends Omit<InjectableDecoration, 'style'> {
|
|
23
|
+
style: DecorationStyle;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolves a navigator-level Decoration to a wire-safe plain object for postMessage.
|
|
28
|
+
* For Template styles, calls `element(decoration)` and embeds the resulting HTML string.
|
|
29
|
+
* For registered custom style IDs (found in `decorationTemplates`), resolves the template
|
|
30
|
+
* and converts the style to a Template wire object.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveDecorationForWire(
|
|
33
|
+
decoration: Decoration,
|
|
34
|
+
decorationTemplates?: Record<string, HTMLDecorationTemplate>
|
|
35
|
+
): unknown {
|
|
36
|
+
const { style } = decoration;
|
|
37
|
+
if (style.type === DecorationStyleType.Template) {
|
|
38
|
+
const tpl = style as HTMLDecorationTemplate;
|
|
39
|
+
return { ...decoration, style: { ...tpl, element: tpl.element(decoration) } };
|
|
40
|
+
}
|
|
41
|
+
if (style.type && decorationTemplates?.[style.type]) {
|
|
42
|
+
const tpl = decorationTemplates[style.type];
|
|
43
|
+
return {
|
|
44
|
+
...decoration,
|
|
45
|
+
style: {
|
|
46
|
+
type: DecorationStyleType.Template,
|
|
47
|
+
layout: tpl.layout,
|
|
48
|
+
width: tpl.width,
|
|
49
|
+
stylesheet: tpl.stylesheet,
|
|
50
|
+
isActive: style.isActive,
|
|
51
|
+
element: tpl.element(decoration),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return decoration;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface DecorationActivationEvent {
|
|
59
|
+
decoration: Decoration;
|
|
60
|
+
group: string;
|
|
61
|
+
/** Bounding rect of the activated decoration in navigator container coordinates (CSS pixels). */
|
|
62
|
+
rect?: { top: number; left: number; width: number; height: number };
|
|
63
|
+
/** Tap/click point in navigator container coordinates (CSS pixels). */
|
|
64
|
+
point?: { x: number; y: number };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface DecorationObserver {
|
|
68
|
+
/**
|
|
69
|
+
* Called when a user activates a decoration (click or tap).
|
|
70
|
+
* Return true to indicate the event was handled — this suppresses normal tap/click navigation.
|
|
71
|
+
*/
|
|
72
|
+
onDecorationActivated(event: DecorationActivationEvent): boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stylesEqual(a: DecorationStyle, b: DecorationStyle): boolean {
|
|
76
|
+
if (a.type !== b.type) return false;
|
|
77
|
+
if ((a.isActive ?? false) !== (b.isActive ?? false)) return false;
|
|
78
|
+
if (a.type === DecorationStyleType.Template) {
|
|
79
|
+
const ta = a as HTMLDecorationTemplate;
|
|
80
|
+
const tb = b as HTMLDecorationTemplate;
|
|
81
|
+
// element is a function — not comparable by value; excluded from equality
|
|
82
|
+
return ta.layout === tb.layout &&
|
|
83
|
+
ta.width === tb.width &&
|
|
84
|
+
ta.stylesheet === tb.stylesheet;
|
|
85
|
+
}
|
|
86
|
+
const ba = a as BuiltinDecorationStyle;
|
|
87
|
+
const bb = b as BuiltinDecorationStyle;
|
|
88
|
+
return ba.tint === bb.tint &&
|
|
89
|
+
ba.layout === bb.layout &&
|
|
90
|
+
ba.width === bb.width &&
|
|
91
|
+
(ba.enforceContrast ?? true) === (bb.enforceContrast ?? true);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function serializeLocations(loc: any): any {
|
|
95
|
+
return typeof loc?.serialize === 'function' ? loc.serialize() : loc;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function decorationsEqual(a: Decoration, b: Decoration): boolean {
|
|
99
|
+
return (
|
|
100
|
+
a.locator.href === b.locator.href &&
|
|
101
|
+
JSON.stringify(serializeLocations(a.locator.locations)) === JSON.stringify(serializeLocations(b.locator.locations)) &&
|
|
102
|
+
stylesEqual(a.style, b.style) &&
|
|
103
|
+
JSON.stringify(a.extras ?? null) === JSON.stringify(b.extras ?? null)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Configuration for decoration rendering. */
|
|
108
|
+
export interface DecoratorConfig {
|
|
109
|
+
/**
|
|
110
|
+
* Named custom styles. Each key is a style type ID; the value is the template that
|
|
111
|
+
* generates the HTML for decorations of that type. When a decoration's `style.type`
|
|
112
|
+
* matches a key here, the navigator resolves the template and sends it to the
|
|
113
|
+
* injectable as a Template-type decoration.
|
|
114
|
+
*/
|
|
115
|
+
decorationTemplates?: Record<string, HTMLDecorationTemplate>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const BUILTIN_DECORATION_TYPES = new Set<string>([
|
|
119
|
+
DecorationStyleType.Highlight,
|
|
120
|
+
DecorationStyleType.Underline,
|
|
121
|
+
DecorationStyleType.Outline,
|
|
122
|
+
DecorationStyleType.TextColor,
|
|
123
|
+
DecorationStyleType.Mask,
|
|
124
|
+
DecorationStyleType.Template,
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
export interface DecorableNavigator {
|
|
128
|
+
/**
|
|
129
|
+
* Replaces all decorations for the given group with the provided list.
|
|
130
|
+
* The navigator diffs the new list against the current state and issues
|
|
131
|
+
* add / update / remove / clear commands as needed.
|
|
132
|
+
*/
|
|
133
|
+
applyDecorations(decorations: Decoration[], group: string): void;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Returns whether the given style type ID can be rendered by this navigator.
|
|
137
|
+
* Returns true for all built-in types and any IDs registered in DecoratorConfig.
|
|
138
|
+
*/
|
|
139
|
+
supportsDecorationStyle(styleTypeId: string): boolean;
|
|
140
|
+
|
|
141
|
+
/** Registers an observer for activation events on the given group. */
|
|
142
|
+
registerDecorationObserver(group: string, observer: DecorationObserver): void;
|
|
143
|
+
|
|
144
|
+
/** Unregisters a previously registered observer from all groups. */
|
|
145
|
+
unregisterDecorationObserver(observer: DecorationObserver): void;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { BUILTIN_DECORATION_TYPES };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Layout, Link, Locator, Profile, Publication, ReadingProgression } from "@readium/shared";
|
|
1
|
+
import { Layout, Link, Locator, LocatorText, Profile, Publication, ReadingProgression } from "@readium/shared";
|
|
2
2
|
import { Configurable, ConfigurableSettings, LineLengths, ProgressionRange, VisualNavigator, VisualNavigatorViewport } from "../index.ts";
|
|
3
3
|
import { FramePoolManager } from "./frame/FramePoolManager.ts";
|
|
4
4
|
import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
|
|
5
|
-
import { CommsEventKey, ContextMenuEvent, FXLModules, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, KeyboardPeripheralEvent } from "@readium/navigator-html-injectables";
|
|
5
|
+
import { CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, FXLModules, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, KeyboardPeripheralEvent } from "@readium/navigator-html-injectables";
|
|
6
|
+
import { Decoration, DecorationActivationEvent, DecorationObserver, DecorableNavigator, DecoratorConfig, decorationsEqual, resolveDecorationForWire, BUILTIN_DECORATION_TYPES } from "../decorations/index.ts";
|
|
6
7
|
import * as path from "path-browserify";
|
|
7
8
|
import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
|
|
8
9
|
import { FrameManager } from "./frame/FrameManager.ts";
|
|
@@ -29,6 +30,7 @@ export interface EpubNavigatorConfiguration {
|
|
|
29
30
|
injectables?: IInjectablesConfig;
|
|
30
31
|
contentProtection?: IContentProtectionConfig;
|
|
31
32
|
keyboardPeripherals?: IKeyboardPeripheralsConfig;
|
|
33
|
+
decoratorConfig?: DecoratorConfig;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export interface EpubNavigatorListeners {
|
|
@@ -64,7 +66,7 @@ const defaultListeners = (listeners: EpubNavigatorListeners): EpubNavigatorListe
|
|
|
64
66
|
peripheral: listeners.peripheral || (() => {}),
|
|
65
67
|
})
|
|
66
68
|
|
|
67
|
-
export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences
|
|
69
|
+
export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences>, DecorableNavigator {
|
|
68
70
|
private readonly pub: Publication;
|
|
69
71
|
private readonly container: HTMLElement;
|
|
70
72
|
private readonly listeners: EpubNavigatorListeners;
|
|
@@ -93,6 +95,13 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
93
95
|
|
|
94
96
|
private resizeObserver: ResizeObserver;
|
|
95
97
|
|
|
98
|
+
private readonly _decoratorConfig: DecoratorConfig;
|
|
99
|
+
|
|
100
|
+
private _decorations: Map<string, Decoration[]> = new Map();
|
|
101
|
+
private _decorationObservers: Map<string, Set<DecorationObserver>> = new Map();
|
|
102
|
+
private _decorationActivationState: Map<string, boolean> = new Map();
|
|
103
|
+
private _decorationActivationConsumed = false;
|
|
104
|
+
|
|
96
105
|
private reflowViewport: VisualNavigatorViewport = {
|
|
97
106
|
readingOrder: [],
|
|
98
107
|
progressions: new Map(),
|
|
@@ -152,6 +161,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
152
161
|
this._readiumRulesPromise = createReadiumEpubRules(pub.metadata, pub.readingOrder.items);
|
|
153
162
|
|
|
154
163
|
this._contentProtection = configuration.contentProtection || {};
|
|
164
|
+
this._decoratorConfig = configuration.decoratorConfig || {};
|
|
155
165
|
|
|
156
166
|
// Merge keyboard peripherals
|
|
157
167
|
this._keyboardPeripherals = this.mergeKeyboardPeripherals(
|
|
@@ -421,11 +431,19 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
421
431
|
* to trigger the navigator when user's mouse/keyboard focus is
|
|
422
432
|
* outside the readium-controller navigator. Be careful!
|
|
423
433
|
*/
|
|
424
|
-
public eventListener(key: CommsEventKey | ManagerEventKey, data: unknown) {
|
|
434
|
+
public eventListener(key: CommsEventKey | ManagerEventKey, data: unknown, sourceFrame?: FrameManager | FXLFrameManager) {
|
|
425
435
|
switch (key) {
|
|
426
436
|
case "_pong":
|
|
427
437
|
this.listeners.frameLoaded(this._cframes[0]!.iframe.contentWindow!);
|
|
428
438
|
this.listeners.positionChanged(this.currentLocation);
|
|
439
|
+
if (sourceFrame) {
|
|
440
|
+
const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
|
|
441
|
+
const i = frames.indexOf(sourceFrame);
|
|
442
|
+
const href = i >= 0 ? this.viewport.readingOrder[i] : undefined;
|
|
443
|
+
if (href) this._reapplyDecorationsToFrame(sourceFrame, href);
|
|
444
|
+
} else {
|
|
445
|
+
this._reapplyDecorationsToCurrentFrames();
|
|
446
|
+
}
|
|
429
447
|
break;
|
|
430
448
|
case "first_visible_locator":
|
|
431
449
|
const loc = Locator.deserialize(data as string);
|
|
@@ -439,11 +457,31 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
439
457
|
});
|
|
440
458
|
this.listeners.positionChanged(this.currentLocation);
|
|
441
459
|
break;
|
|
442
|
-
case "text_selected":
|
|
443
|
-
|
|
460
|
+
case "text_selected": {
|
|
461
|
+
const selection = data as BasicTextSelection;
|
|
462
|
+
if (sourceFrame) {
|
|
463
|
+
const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
|
|
464
|
+
const i = frames.indexOf(sourceFrame);
|
|
465
|
+
const href = i >= 0 ? this.viewport.readingOrder[i] : undefined;
|
|
466
|
+
if (href) {
|
|
467
|
+
const link = this.pub.readingOrder.findWithHref(href);
|
|
468
|
+
selection.locator = new Locator({ href, type: link!.type || "application/xhtml+xml", text: new LocatorText({ highlight: selection.text }) });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
this.listeners.textSelected(selection);
|
|
444
472
|
break;
|
|
473
|
+
}
|
|
474
|
+
case "decoration_activated": {
|
|
475
|
+
const handled = this._handleDecorationActivated(data as DecorationActivatedEvent);
|
|
476
|
+
if (handled) this._decorationActivationConsumed = true;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
445
479
|
case "click":
|
|
446
480
|
case "tap":
|
|
481
|
+
if (this._decorationActivationConsumed) {
|
|
482
|
+
this._decorationActivationConsumed = false;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
447
485
|
const edata = data as FrameClickEvent;
|
|
448
486
|
if (edata.interactiveElement) {
|
|
449
487
|
const element = new DOMParser().parseFromString(
|
|
@@ -589,10 +627,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
589
627
|
if(vframes.length === 0) throw Error("no cframe to attach listener to");
|
|
590
628
|
vframes.forEach(f => {
|
|
591
629
|
if(f.msg) f.msg.listener = (key: CommsEventKey | ManagerEventKey, value: unknown) => {
|
|
592
|
-
this.eventListener(key, value);
|
|
630
|
+
this.eventListener(key, value, f);
|
|
593
631
|
}
|
|
594
632
|
})
|
|
595
|
-
|
|
633
|
+
this._reapplyDecorationsToCurrentFrames();
|
|
596
634
|
}
|
|
597
635
|
|
|
598
636
|
private async apply() {
|
|
@@ -605,6 +643,141 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
605
643
|
throw Error("Link for " + this.currentLocation.href + " not found!");
|
|
606
644
|
}
|
|
607
645
|
|
|
646
|
+
// DecorableNavigator
|
|
647
|
+
|
|
648
|
+
public supportsDecorationStyle(styleTypeId: string): boolean {
|
|
649
|
+
return BUILTIN_DECORATION_TYPES.has(styleTypeId) ||
|
|
650
|
+
!!this._decoratorConfig.decorationTemplates?.[styleTypeId];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
public registerDecorationObserver(group: string, observer: DecorationObserver): void {
|
|
654
|
+
if (!this._decorationObservers.has(group))
|
|
655
|
+
this._decorationObservers.set(group, new Set());
|
|
656
|
+
this._decorationObservers.get(group)!.add(observer);
|
|
657
|
+
|
|
658
|
+
// Store activation state and send to current frames
|
|
659
|
+
this._decorationActivationState.set(group, true);
|
|
660
|
+
this._sendDecorationActivationToFrames(group, true);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
public unregisterDecorationObserver(observer: DecorationObserver): void {
|
|
664
|
+
this._decorationObservers.forEach((set, group) => {
|
|
665
|
+
if (set.has(observer)) {
|
|
666
|
+
set.delete(observer);
|
|
667
|
+
if (set.size === 0) {
|
|
668
|
+
this._decorationActivationState.delete(group);
|
|
669
|
+
this._sendDecorationActivationToFrames(group, false);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
private _sendDecorationActivationToFrames(group: string, activatable: boolean): void {
|
|
676
|
+
const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
|
|
677
|
+
frames.forEach(f => {
|
|
678
|
+
if (f.msg) f.msg.send("decoration_activatable", { group, activatable });
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
public applyDecorations(decorations: Decoration[], group: string): void {
|
|
683
|
+
const previous = this._decorations.get(group) ?? [];
|
|
684
|
+
const prevById = new Map(previous.map(d => [d.id, d]));
|
|
685
|
+
const nextById = new Map(decorations.map(d => [d.id, d]));
|
|
686
|
+
|
|
687
|
+
const toRemove: string[] = [];
|
|
688
|
+
const toAdd: Decoration[] = [];
|
|
689
|
+
const toUpdate: Decoration[] = [];
|
|
690
|
+
|
|
691
|
+
for (const [id, prev] of prevById) {
|
|
692
|
+
if (!nextById.has(id)) toRemove.push(id);
|
|
693
|
+
else if (!decorationsEqual(prev, nextById.get(id)!)) toUpdate.push(nextById.get(id)!);
|
|
694
|
+
}
|
|
695
|
+
for (const [id, next] of nextById) {
|
|
696
|
+
if (!prevById.has(id)) toAdd.push(next);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
this._decorations.set(group, decorations);
|
|
700
|
+
this._sendDecorationOps(group, toRemove, toAdd, toUpdate, previous);
|
|
701
|
+
// Resend activation state after ops: FIFO postMessage guarantees the group
|
|
702
|
+
// exists in the injectable by the time this arrives, so it won't be dropped.
|
|
703
|
+
// Fixes the case where registerDecorationObserver was called before the group
|
|
704
|
+
// was created (activation sent before first decorate:add → silently dropped).
|
|
705
|
+
const activatable = this._decorationActivationState.get(group);
|
|
706
|
+
if (activatable !== undefined) {
|
|
707
|
+
this._sendDecorationActivationToFrames(group, activatable);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private _sendDecorationOps(
|
|
712
|
+
group: string,
|
|
713
|
+
toRemove: string[],
|
|
714
|
+
toAdd: Decoration[],
|
|
715
|
+
toUpdate: Decoration[],
|
|
716
|
+
previous: Decoration[]
|
|
717
|
+
): void {
|
|
718
|
+
const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
|
|
719
|
+
const prevById = new Map(previous.map(d => [d.id, d]));
|
|
720
|
+
const visibleHrefs = this.viewport.readingOrder;
|
|
721
|
+
|
|
722
|
+
frames.forEach((frame, i) => {
|
|
723
|
+
if (!frame.msg) return;
|
|
724
|
+
const href = visibleHrefs[i];
|
|
725
|
+
if (!href) return;
|
|
726
|
+
for (const id of toRemove) {
|
|
727
|
+
const d = prevById.get(id);
|
|
728
|
+
if (!d || d.locator.href !== href) continue;
|
|
729
|
+
frame.msg.send("decorate", { group, action: "remove", decoration: { id } });
|
|
730
|
+
}
|
|
731
|
+
for (const d of toAdd) {
|
|
732
|
+
if (d.locator.href !== href) continue;
|
|
733
|
+
frame.msg.send("decorate", { group, action: "add", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
|
|
734
|
+
}
|
|
735
|
+
for (const d of toUpdate) {
|
|
736
|
+
if (d.locator.href !== href) continue;
|
|
737
|
+
frame.msg.send("decorate", { group, action: "update", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private _reapplyDecorationsToFrame(frame: FrameManager | FXLFrameManager, href: string): void {
|
|
743
|
+
if (!frame.msg) return;
|
|
744
|
+
for (const [group, decorations] of this._decorations) {
|
|
745
|
+
const matching = decorations.filter(d => d.locator.href === href);
|
|
746
|
+
if (matching.length === 0) continue;
|
|
747
|
+
frame.msg.send("decorate", { group, action: "clear" });
|
|
748
|
+
for (const d of matching)
|
|
749
|
+
frame.msg.send("decorate", { group, action: "add", decoration: resolveDecorationForWire(d, this._decoratorConfig.decorationTemplates) });
|
|
750
|
+
}
|
|
751
|
+
for (const [group, activatable] of this._decorationActivationState) {
|
|
752
|
+
frame.msg.send("decoration_activatable", { group, activatable });
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private _reapplyDecorationsToCurrentFrames(): void {
|
|
757
|
+
const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
|
|
758
|
+
const visibleHrefs = this.viewport.readingOrder;
|
|
759
|
+
frames.forEach((frame, i) => {
|
|
760
|
+
const href = visibleHrefs[i];
|
|
761
|
+
if (href) this._reapplyDecorationsToFrame(frame, href);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private _handleDecorationActivated(data: DecorationActivatedEvent): boolean {
|
|
766
|
+
const observers = this._decorationObservers.get(data.group);
|
|
767
|
+
if (!observers || observers.size === 0) return false;
|
|
768
|
+
|
|
769
|
+
const decoration = (this._decorations.get(data.group) ?? []).find(d => d.id === data.decorationId);
|
|
770
|
+
if (!decoration) return false;
|
|
771
|
+
|
|
772
|
+
const event: DecorationActivationEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
|
|
773
|
+
let anyHandled = false;
|
|
774
|
+
for (const obs of observers)
|
|
775
|
+
if (obs.onDecorationActivated(event)) anyHandled = true;
|
|
776
|
+
return anyHandled;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// End of Decoration
|
|
780
|
+
|
|
608
781
|
public async destroy() {
|
|
609
782
|
if (this._suspiciousActivityListener) {
|
|
610
783
|
window.removeEventListener(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, this._suspiciousActivityListener);
|
|
@@ -615,6 +788,9 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
615
788
|
this._navigatorProtector?.destroy();
|
|
616
789
|
this._keyboardPeripheralsManager?.destroy();
|
|
617
790
|
await this.framePool?.destroy();
|
|
791
|
+
this._decorations.clear();
|
|
792
|
+
this._decorationObservers.clear();
|
|
793
|
+
this._decorationActivationState.clear();
|
|
618
794
|
}
|
|
619
795
|
|
|
620
796
|
private async changeResource(relative: number): Promise<boolean> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Loader, ModuleName } from "@readium/navigator-html-injectables";
|
|
2
2
|
import { FrameComms } from "./FrameComms.ts";
|
|
3
3
|
import type { ReadiumWindow } from "../../../../navigator-html-injectables/types/src/helpers/dom";
|
|
4
|
-
import { sML } from "
|
|
4
|
+
import { sML } from "@readium/navigator-html-injectables";
|
|
5
5
|
import type { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
|
|
6
6
|
import { KeyboardConditionBridge } from "../../peripherals/KeyboardConditionBridge.ts";
|
|
7
7
|
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
withFallback
|
|
20
20
|
} from "../../preferences/guards.ts";
|
|
21
21
|
|
|
22
|
-
import { sMLWithRequest } from "
|
|
22
|
+
import { sMLWithRequest } from "@readium/navigator-html-injectables";
|
|
23
23
|
|
|
24
24
|
export interface IEpubDefaults {
|
|
25
25
|
backgroundColor?: string | null,
|
|
@@ -3,7 +3,7 @@ import { ExperimentKey, TextAlignment } from "../../preferences/Types.ts";
|
|
|
3
3
|
import { EpubDefaults } from "./EpubDefaults.ts";
|
|
4
4
|
import { EpubPreferences } from "./EpubPreferences.ts";
|
|
5
5
|
|
|
6
|
-
import { sMLWithRequest } from "
|
|
6
|
+
import { sMLWithRequest } from "@readium/navigator-html-injectables";
|
|
7
7
|
|
|
8
8
|
export interface IEpubSettings {
|
|
9
9
|
backgroundColor?: string | null,
|
package/src/helpers/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Loader, ModuleName } from "@readium/navigator-html-injectables";
|
|
2
2
|
import { FrameComms } from "../epub/frame/FrameComms.ts";
|
|
3
3
|
import type { ReadiumWindow } from "../../../navigator-html-injectables/types/src/helpers/dom";
|
|
4
|
-
import { sML } from "
|
|
4
|
+
import { sML } from "@readium/navigator-html-injectables";
|
|
5
5
|
import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
|
|
6
6
|
import { KeyboardConditionBridge } from "../peripherals/KeyboardConditionBridge.ts";
|
|
7
7
|
|