@readium/navigator 2.1.1 → 2.2.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 +3912 -2721
- package/dist/index.umd.cjs +286 -71
- package/package.json +1 -1
- package/src/css/Properties.ts +47 -0
- package/src/css/index.ts +1 -0
- package/src/epub/css/Properties.ts +10 -48
- package/src/epub/preferences/EpubDefaults.ts +1 -1
- package/src/epub/preferences/EpubPreferences.ts +1 -1
- package/src/epub/preferences/EpubPreferencesEditor.ts +30 -23
- package/src/index.ts +3 -1
- package/src/preferences/Types.ts +40 -0
- package/src/{epub/preferences → preferences}/guards.ts +5 -6
- package/src/preferences/index.ts +2 -1
- package/src/webpub/WebPubBlobBuilder.ts +167 -0
- package/src/webpub/WebPubFrameManager.ts +156 -0
- package/src/webpub/WebPubFramePoolManager.ts +221 -0
- package/src/webpub/WebPubNavigator.ts +494 -0
- package/src/webpub/css/Properties.ts +71 -0
- package/src/webpub/css/WebPubCSS.ts +42 -0
- package/src/webpub/css/WebPubStylesheet.ts +204 -0
- package/src/webpub/css/index.ts +3 -0
- package/src/webpub/index.ts +6 -0
- package/src/webpub/preferences/WebPubDefaults.ts +61 -0
- package/src/webpub/preferences/WebPubPreferences.ts +88 -0
- package/src/webpub/preferences/WebPubPreferencesEditor.ts +193 -0
- package/src/webpub/preferences/WebPubSettings.ts +88 -0
- package/src/webpub/preferences/index.ts +4 -0
- package/types/src/css/Properties.d.ts +20 -0
- package/types/src/css/index.d.ts +1 -0
- package/types/src/epub/css/Properties.d.ts +1 -21
- package/types/src/index.d.ts +2 -0
- package/types/src/preferences/Types.d.ts +8 -0
- package/types/src/preferences/guards.d.ts +9 -0
- package/types/src/preferences/index.d.ts +1 -0
- package/types/src/web/WebPubBlobBuilder.d.ts +10 -0
- package/types/src/web/WebPubFrameManager.d.ts +20 -0
- package/types/src/web/WebPubNavigator.d.ts +48 -0
- package/types/src/web/index.d.ts +3 -0
- package/types/src/webpub/WebPubBlobBuilder.d.ts +16 -0
- package/types/src/webpub/WebPubFrameManager.d.ts +24 -0
- package/types/src/webpub/WebPubFramePoolManager.d.ts +23 -0
- package/types/src/webpub/WebPubNavigator.d.ts +70 -0
- package/types/src/webpub/css/Properties.d.ts +36 -0
- package/types/src/webpub/css/WebPubCSS.d.ts +10 -0
- package/types/src/webpub/css/WebPubStylesheet.d.ts +1 -0
- package/types/src/webpub/css/index.d.ts +3 -0
- package/types/src/webpub/index.d.ts +6 -0
- package/types/src/webpub/preferences/WebPubDefaults.d.ts +32 -0
- package/types/src/webpub/preferences/WebPubPreferences.d.ts +36 -0
- package/types/src/webpub/preferences/WebPubPreferencesEditor.d.ts +27 -0
- package/types/src/webpub/preferences/WebPubSettings.d.ts +35 -0
- package/types/src/webpub/preferences/index.d.ts +4 -0
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type BodyHyphens = "auto" | "none";
|
|
2
|
+
export type BoxSizing = "content-box" | "border-box";
|
|
3
|
+
export type FontOpticalSizing = "auto" | "none";
|
|
4
|
+
export type FontWidth = "ultra-condensed" | "extra-condensed" | "condensed" | "semi-condensed" | "normal" | "semi-expanded" | "expanded" | "extra-expanded" | "ultra-expanded" | number;
|
|
5
|
+
export type Ligatures = "common-ligatures" | "none";
|
|
6
|
+
export type TypeScale = 1 | 1.067 | 1.125 | 1.2 | 1.25 | 1.333 | 1.414 | 1.5 | 1.618;
|
|
7
|
+
export type View = "paged" | "scroll";
|
|
8
|
+
|
|
9
|
+
export abstract class Properties {
|
|
10
|
+
constructor() {}
|
|
11
|
+
|
|
12
|
+
protected toFlag(name: string) {
|
|
13
|
+
return `readium-${ name }-on`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected toUnitless(value: number) {
|
|
17
|
+
return value.toString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected toPercentage(value: number, ratio: boolean = false) {
|
|
21
|
+
if (ratio || value > 0 && value <= 1) {
|
|
22
|
+
return `${ Math.round(value * 100) }%`;
|
|
23
|
+
} else {
|
|
24
|
+
return `${ value }%`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected toVw(value: number) {
|
|
29
|
+
const percentage = Math.round(value * 100);
|
|
30
|
+
return `${ Math.min(percentage, 100) }vw`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected toVh(value: number) {
|
|
34
|
+
const percentage = Math.round(value * 100);
|
|
35
|
+
return `${ Math.min(percentage, 100) }vh`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected toPx(value: number) {
|
|
39
|
+
return `${ value }px`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected toRem(value: number) {
|
|
43
|
+
return `${ value }rem`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
abstract toCSSProperties(): { [key: string]: string };
|
|
47
|
+
}
|
package/src/css/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Properties";
|
|
@@ -1,52 +1,14 @@
|
|
|
1
1
|
import { TextAlignment } from "../../preferences/Types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
constructor() {}
|
|
13
|
-
|
|
14
|
-
protected toFlag(name: string) {
|
|
15
|
-
return `readium-${ name }-on`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
protected toUnitless(value: number) {
|
|
19
|
-
return value.toString();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
protected toPercentage(value: number, ratio: boolean = false) {
|
|
23
|
-
if (ratio || value > 0 && value <= 1) {
|
|
24
|
-
return `${ Math.round(value * 100) }%`;
|
|
25
|
-
} else {
|
|
26
|
-
return `${ value }%`;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
protected toVw(value: number) {
|
|
31
|
-
const percentage = Math.round(value * 100);
|
|
32
|
-
return `${ Math.min(percentage, 100) }vw`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
protected toVh(value: number) {
|
|
36
|
-
const percentage = Math.round(value * 100);
|
|
37
|
-
return `${ Math.min(percentage, 100) }vh`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
protected toPx(value: number) {
|
|
41
|
-
return `${ value }px`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
protected toRem(value: number) {
|
|
45
|
-
return `${ value }rem`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
abstract toCSSProperties(): { [key: string]: string };
|
|
49
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
BodyHyphens,
|
|
4
|
+
BoxSizing,
|
|
5
|
+
FontOpticalSizing,
|
|
6
|
+
FontWidth,
|
|
7
|
+
Ligatures,
|
|
8
|
+
Properties,
|
|
9
|
+
TypeScale,
|
|
10
|
+
View
|
|
11
|
+
} from "../../css/Properties";
|
|
50
12
|
|
|
51
13
|
export interface IUserProperties {
|
|
52
14
|
advancedSettings?: boolean | null;
|
|
@@ -5,9 +5,16 @@ import { EpubSettings } from "./EpubSettings";
|
|
|
5
5
|
import { BooleanPreference, EnumPreference, Preference, RangePreference } from "../../preferences/Preference";
|
|
6
6
|
import {
|
|
7
7
|
TextAlignment,
|
|
8
|
+
filterRangeConfig,
|
|
8
9
|
fontSizeRangeConfig,
|
|
9
10
|
fontWeightRangeConfig,
|
|
10
|
-
fontWidthRangeConfig
|
|
11
|
+
fontWidthRangeConfig,
|
|
12
|
+
letterSpacingRangeConfig,
|
|
13
|
+
lineHeightRangeConfig,
|
|
14
|
+
lineLengthRangeConfig,
|
|
15
|
+
paragraphIndentRangeConfig,
|
|
16
|
+
paragraphSpacingRangeConfig,
|
|
17
|
+
wordSpacingRangeConfig
|
|
11
18
|
} from "../../preferences/Types";
|
|
12
19
|
|
|
13
20
|
import defaultColors from "@readium/css/css/vars/colors.json";
|
|
@@ -85,8 +92,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
85
92
|
onChange: (newValue: number | boolean | null | undefined) => {
|
|
86
93
|
this.updatePreference("darkenFilter", newValue || null);
|
|
87
94
|
},
|
|
88
|
-
supportedRange:
|
|
89
|
-
step:
|
|
95
|
+
supportedRange: filterRangeConfig.range,
|
|
96
|
+
step: filterRangeConfig.step
|
|
90
97
|
});
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -192,8 +199,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
192
199
|
onChange: (newValue: number | boolean | null | undefined) => {
|
|
193
200
|
this.updatePreference("invertFilter", newValue || null);
|
|
194
201
|
},
|
|
195
|
-
supportedRange:
|
|
196
|
-
step:
|
|
202
|
+
supportedRange: filterRangeConfig.range,
|
|
203
|
+
step: filterRangeConfig.step
|
|
197
204
|
});
|
|
198
205
|
}
|
|
199
206
|
|
|
@@ -205,8 +212,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
205
212
|
onChange: (newValue: number | boolean | null | undefined) => {
|
|
206
213
|
this.updatePreference("invertGaijiFilter", newValue || null);
|
|
207
214
|
},
|
|
208
|
-
supportedRange:
|
|
209
|
-
step:
|
|
215
|
+
supportedRange: filterRangeConfig.range,
|
|
216
|
+
step: filterRangeConfig.step
|
|
210
217
|
});
|
|
211
218
|
}
|
|
212
219
|
|
|
@@ -240,8 +247,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
240
247
|
onChange: (newValue: number | null | undefined) => {
|
|
241
248
|
this.updatePreference("letterSpacing", newValue || null);
|
|
242
249
|
},
|
|
243
|
-
supportedRange:
|
|
244
|
-
step: .
|
|
250
|
+
supportedRange: letterSpacingRangeConfig.range,
|
|
251
|
+
step: letterSpacingRangeConfig.step
|
|
245
252
|
});
|
|
246
253
|
}
|
|
247
254
|
|
|
@@ -266,8 +273,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
266
273
|
onChange: (newValue: number | null | undefined) => {
|
|
267
274
|
this.updatePreference("lineHeight", newValue || null);
|
|
268
275
|
},
|
|
269
|
-
supportedRange:
|
|
270
|
-
step: .
|
|
276
|
+
supportedRange: lineHeightRangeConfig.range,
|
|
277
|
+
step: lineHeightRangeConfig.step
|
|
271
278
|
});
|
|
272
279
|
}
|
|
273
280
|
|
|
@@ -290,8 +297,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
290
297
|
onChange: (newValue: number | null | undefined) => {
|
|
291
298
|
this.updatePreference("maximalLineLength", newValue);
|
|
292
299
|
},
|
|
293
|
-
supportedRange:
|
|
294
|
-
step:
|
|
300
|
+
supportedRange: lineLengthRangeConfig.range,
|
|
301
|
+
step: lineLengthRangeConfig.step
|
|
295
302
|
});
|
|
296
303
|
}
|
|
297
304
|
|
|
@@ -303,8 +310,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
303
310
|
onChange: (newValue: number | null | undefined) => {
|
|
304
311
|
this.updatePreference("minimalLineLength", newValue);
|
|
305
312
|
},
|
|
306
|
-
supportedRange:
|
|
307
|
-
step:
|
|
313
|
+
supportedRange: lineLengthRangeConfig.range,
|
|
314
|
+
step: lineLengthRangeConfig.step
|
|
308
315
|
});
|
|
309
316
|
}
|
|
310
317
|
|
|
@@ -327,8 +334,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
327
334
|
onChange: (newValue: number | null | undefined) => {
|
|
328
335
|
this.updatePreference("optimalLineLength", newValue as number);
|
|
329
336
|
},
|
|
330
|
-
supportedRange:
|
|
331
|
-
step:
|
|
337
|
+
supportedRange: lineLengthRangeConfig.range,
|
|
338
|
+
step: lineLengthRangeConfig.step
|
|
332
339
|
});
|
|
333
340
|
}
|
|
334
341
|
|
|
@@ -351,8 +358,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
351
358
|
onChange: (newValue: number | null | undefined) => {
|
|
352
359
|
this.updatePreference("paragraphIndent", newValue || null);
|
|
353
360
|
},
|
|
354
|
-
supportedRange:
|
|
355
|
-
step: .
|
|
361
|
+
supportedRange: paragraphIndentRangeConfig.range,
|
|
362
|
+
step: paragraphIndentRangeConfig.step
|
|
356
363
|
});
|
|
357
364
|
}
|
|
358
365
|
|
|
@@ -364,8 +371,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
364
371
|
onChange: (newValue: number | null | undefined) => {
|
|
365
372
|
this.updatePreference("paragraphSpacing", newValue || null);
|
|
366
373
|
},
|
|
367
|
-
supportedRange:
|
|
368
|
-
step: .
|
|
374
|
+
supportedRange: paragraphSpacingRangeConfig.range,
|
|
375
|
+
step: paragraphSpacingRangeConfig.step
|
|
369
376
|
});
|
|
370
377
|
}
|
|
371
378
|
|
|
@@ -501,8 +508,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
|
|
|
501
508
|
onChange: (newValue: number | null | undefined) => {
|
|
502
509
|
this.updatePreference("wordSpacing", newValue || null);
|
|
503
510
|
},
|
|
504
|
-
supportedRange:
|
|
505
|
-
step:
|
|
511
|
+
supportedRange: wordSpacingRangeConfig.range,
|
|
512
|
+
step: wordSpacingRangeConfig.step
|
|
506
513
|
});
|
|
507
514
|
}
|
|
508
515
|
}
|
package/src/index.ts
CHANGED
package/src/preferences/Types.ts
CHANGED
|
@@ -10,6 +10,11 @@ export type RangeConfig = {
|
|
|
10
10
|
step: number
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export const filterRangeConfig: RangeConfig = {
|
|
14
|
+
range: [0, 100],
|
|
15
|
+
step: 1
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
export const fontSizeRangeConfig: RangeConfig = {
|
|
14
19
|
range: [0.7, 4],
|
|
15
20
|
step: 0.05
|
|
@@ -23,4 +28,39 @@ export const fontWeightRangeConfig: RangeConfig = {
|
|
|
23
28
|
export const fontWidthRangeConfig: RangeConfig = {
|
|
24
29
|
range: [50, 250],
|
|
25
30
|
step: 10
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const letterSpacingRangeConfig: RangeConfig = {
|
|
34
|
+
range: [0, 1],
|
|
35
|
+
step: .125
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const lineHeightRangeConfig: RangeConfig = {
|
|
39
|
+
range: [1, 2],
|
|
40
|
+
step: .1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const lineLengthRangeConfig: RangeConfig = {
|
|
44
|
+
range: [20, 100],
|
|
45
|
+
step: 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const paragraphIndentRangeConfig: RangeConfig = {
|
|
49
|
+
range: [0, 3],
|
|
50
|
+
step: .25
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const paragraphSpacingRangeConfig: RangeConfig = {
|
|
54
|
+
range: [0, 3],
|
|
55
|
+
step: .25
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const wordSpacingRangeConfig: RangeConfig = {
|
|
59
|
+
range: [0, 2],
|
|
60
|
+
step: .125
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const zoomRangeConfig: RangeConfig = {
|
|
64
|
+
range: [0.7, 4],
|
|
65
|
+
step: 0.05
|
|
26
66
|
}
|
|
@@ -29,14 +29,13 @@ export function ensureString(value: string | null | undefined): string | null |
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function ensureBoolean(value: boolean | null | undefined): boolean | null | undefined {
|
|
32
|
-
return typeof value === "boolean"
|
|
33
|
-
? value
|
|
34
|
-
: value === undefined || value === null
|
|
35
|
-
? value
|
|
32
|
+
return typeof value === "boolean"
|
|
33
|
+
? value
|
|
34
|
+
: value === undefined || value === null
|
|
35
|
+
? value
|
|
36
36
|
: undefined;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
39
|
export function ensureEnumValue<T extends string>(value: T | null | undefined, enumType: Record<T, string>): T | null | undefined {
|
|
41
40
|
if (value === undefined) {
|
|
42
41
|
return undefined;
|
|
@@ -83,4 +82,4 @@ export function ensureValueInRange(value: number | null | undefined, range: [num
|
|
|
83
82
|
|
|
84
83
|
export function withFallback<T>(value: T | null | undefined, defaultValue: T | null): T | null {
|
|
85
84
|
return value === undefined ? defaultValue : value;
|
|
86
|
-
}
|
|
85
|
+
}
|
package/src/preferences/index.ts
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Link, Publication } from "@readium/shared";
|
|
2
|
+
import { webPubStylesheet } from "./css/WebPubStylesheet";
|
|
3
|
+
|
|
4
|
+
// Utilities (matching FrameBlobBuilder pattern)
|
|
5
|
+
const blobify = (source: string, type: string) => URL.createObjectURL(new Blob([source], { type }));
|
|
6
|
+
const stripJS = (source: string) => source.replace(/\/\/.*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\n/g, "").replace(/\s+/g, " ");
|
|
7
|
+
const scriptify = (doc: Document, source: string) => {
|
|
8
|
+
const s = doc.createElement("script");
|
|
9
|
+
s.dataset.readium = "true";
|
|
10
|
+
s.src = source.startsWith("blob:") ? source : blobify(source, "text/javascript");
|
|
11
|
+
return s;
|
|
12
|
+
}
|
|
13
|
+
const styleify = (doc: Document, source: string) => {
|
|
14
|
+
const s = doc.createElement("style");
|
|
15
|
+
s.dataset.readium = "true";
|
|
16
|
+
s.textContent = source;
|
|
17
|
+
return s;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type CacheFunction = () => string;
|
|
21
|
+
const resourceBlobCache = new Map<string, string>();
|
|
22
|
+
const cached = (key: string, cacher: CacheFunction) => {
|
|
23
|
+
if (resourceBlobCache.has(key)) return resourceBlobCache.get(key)!;
|
|
24
|
+
const value = cacher();
|
|
25
|
+
resourceBlobCache.set(key, value);
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const cssSelectorGenerator = (doc: Document) => scriptify(doc, cached("css-selector-generator", () => blobify(
|
|
30
|
+
"!function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports._readium_cssSelectorGenerator=e():t._readium_cssSelectorGenerator=e()}(self,(()=>(()=>{\"use strict\";var t,e,n={d:(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(t,\"__esModule\",{value:!0})}},o={};function r(t){return t&&t instanceof Element}function i(t=\"unknown problem\",...e){console.warn(`CssSelectorGenerator: ${t}`,...e)}n.r(o),n.d(o,{default:()=>z,getCssSelector:()=>U}),function(t){t.NONE=\"none\",t.DESCENDANT=\"descendant\",t.CHILD=\"child\"}(t||(t={})),function(t){t.id=\"id\",t.class=\"class\",t.tag=\"tag\",t.attribute=\"attribute\",t.nthchild=\"nthchild\",t.nthoftype=\"nthoftype\"}(e||(e={}));const c={selectors:[e.id,e.class,e.tag,e.attribute],includeTag:!1,whitelist:[],blacklist:[],combineWithinSelector:!0,combineBetweenSelectors:!0,root:null,maxCombinations:Number.POSITIVE_INFINITY,maxCandidates:Number.POSITIVE_INFINITY};function u(t){return t instanceof RegExp}function s(t){return[\"string\",\"function\"].includes(typeof t)||u(t)}function l(t){return Array.isArray(t)?t.filter(s):[]}function a(t){const e=[Node.DOCUMENT_NODE,Node.DOCUMENT_FRAGMENT_NODE,Node.ELEMENT_NODE];return function(t){return t instanceof Node}(t)&&e.includes(t.nodeType)}function f(t,e){if(a(t))return t.contains(e)||i(\"element root mismatch\",\"Provided root does not contain the element. This will most likely result in producing a fallback selector using element\'s real root node. If you plan to use the selector using provided root (e.g. `root.querySelector`), it will nto work as intended.\"),t;const n=e.getRootNode({composed:!1});return a(n)?(n!==document&&i(\"shadow root inferred\",\"You did not provide a root and the element is a child of Shadow DOM. This will produce a selector using ShadowRoot as a root. If you plan to use the selector using document as a root (e.g. `document.querySelector`), it will not work as intended.\"),n):e.ownerDocument.querySelector(\":root\")}function d(t){return\"number\"==typeof t?t:Number.POSITIVE_INFINITY}function m(t=[]){const[e=[],...n]=t;return 0===n.length?e:n.reduce(((t,e)=>t.filter((t=>e.includes(t)))),e)}function p(t){return[].concat(...t)}function h(t){const e=t.map((t=>{if(u(t))return e=>t.test(e);if(\"function\"==typeof t)return e=>{const n=t(e);return\"boolean\"!=typeof n?(i(\"pattern matcher function invalid\",\"Provided pattern matching function does not return boolean. It\'s result will be ignored.\",t),!1):n};if(\"string\"==typeof t){const e=new RegExp(\"^\"+t.replace(\/[|\\\\{}()[\\]^$+?.]\/g,\"\\\\$&\").replace(\/\\*\/g,\".+\")+\"$\");return t=>e.test(t)}return i(\"pattern matcher invalid\",\"Pattern matching only accepts strings, regular expressions and\/or functions. This item is invalid and will be ignored.\",t),()=>!1}));return t=>e.some((e=>e(t)))}function g(t,e,n){const o=Array.from(f(n,t[0]).querySelectorAll(e));return o.length===t.length&&t.every((t=>o.includes(t)))}function y(t,e){e=null!=e?e:function(t){return t.ownerDocument.querySelector(\":root\")}(t);const n=[];let o=t;for(;r(o)&&o!==e;)n.push(o),o=o.parentElement;return n}function b(t,e){return m(t.map((t=>y(t,e))))}const N={[t.NONE]:{type:t.NONE,value:\"\"},[t.DESCENDANT]:{type:t.DESCENDANT,value:\" > \"},[t.CHILD]:{type:t.CHILD,value:\" \"}},S=new RegExp([\"^$\",\"\\\\s\"].join(\"|\")),E=new RegExp([\"^$\"].join(\"|\")),w=[e.nthoftype,e.tag,e.id,e.class,e.attribute,e.nthchild],v=h([\"class\",\"id\",\"ng-*\"]);function C({nodeName:t}){return`[${t}]`}function O({nodeName:t,nodeValue:e}){return`[${t}=\'${L(e)}\']`}function T(t){const e=Array.from(t.attributes).filter((e=>function({nodeName:t},e){const n=e.tagName.toLowerCase();return!([\"input\",\"option\"].includes(n)&&\"value\"===t||v(t))}(e,t)));return[...e.map(C),...e.map(O)]}function I(t){return(t.getAttribute(\"class\")||\"\").trim().split(\/\\s+\/).filter((t=>!E.test(t))).map((t=>`.${L(t)}`))}function x(t){const e=t.getAttribute(\"id\")||\"\",n=`#${L(e)}`,o=t.getRootNode({composed:!1});return!S.test(e)&&g([t],n,o)?[n]:[]}function j(t){const e=t.parentNode;if(e){const n=Array.from(e.childNodes).filter(r).indexOf(t);if(n>-1)return[`:nth-child(${n+1})`]}return[]}function A(t){return[L(t.tagName.toLowerCase())]}function D(t){const e=[...new Set(p(t.map(A)))];return 0===e.length||e.length>1?[]:[e[0]]}function $(t){const e=D([t])[0],n=t.parentElement;if(n){const o=Array.from(n.children).filter((t=>t.tagName.toLowerCase()===e)),r=o.indexOf(t);if(r>-1)return[`${e}:nth-of-type(${r+1})`]}return[]}function R(t=[],{maxResults:e=Number.POSITIVE_INFINITY}={}){const n=[];let o=0,r=k(1);for(;r.length<=t.length&&o<e;)o+=1,n.push(r.map((e=>t[e]))),r=P(r,t.length-1);return n}function P(t=[],e=0){const n=t.length;if(0===n)return[];const o=[...t];o[n-1]+=1;for(let t=n-1;t>=0;t--)if(o[t]>e){if(0===t)return k(n+1);o[t-1]++,o[t]=o[t-1]+1}return o[n-1]>e?k(n+1):o}function k(t=1){return Array.from(Array(t).keys())}const _=\":\".charCodeAt(0).toString(16).toUpperCase(),M=\/[ !\"#$%&\'()\\[\\]{|}<>*+,.\/;=?@^`~\\\\]\/;function L(t=\"\"){var e,n;return null!==(n=null===(e=null===CSS||void 0===CSS?void 0:CSS.escape)||void 0===e?void 0:e.call(CSS,t))&&void 0!==n?n:function(t=\"\"){return t.split(\"\").map((t=>\":\"===t?`\\\\${_} `:M.test(t)?`\\\\${t}`:escape(t).replace(\/%\/g,\"\\\\\"))).join(\"\")}(t)}const q={tag:D,id:function(t){return 0===t.length||t.length>1?[]:x(t[0])},class:function(t){return m(t.map(I))},attribute:function(t){return m(t.map(T))},nthchild:function(t){return m(t.map(j))},nthoftype:function(t){return m(t.map($))}},F={tag:A,id:x,class:I,attribute:T,nthchild:j,nthoftype:$};function V(t){return t.includes(e.tag)||t.includes(e.nthoftype)?[...t]:[...t,e.tag]}function Y(t={}){const n=[...w];return t[e.tag]&&t[e.nthoftype]&&n.splice(n.indexOf(e.tag),1),n.map((e=>{return(o=t)[n=e]?o[n].join(\"\"):\"\";var n,o})).join(\"\")}function B(t,e,n=\"\",o){const r=function(t,e){return\"\"===e?t:function(t,e){return[...t.map((t=>e+\" \"+t)),...t.map((t=>e+\" > \"+t))]}(t,e)}(function(t,e,n){const o=function(t,e){const{blacklist:n,whitelist:o,combineWithinSelector:r,maxCombinations:i}=e,c=h(n),u=h(o);return function(t){const{selectors:e,includeTag:n}=t,o=[].concat(e);return n&&!o.includes(\"tag\")&&o.push(\"tag\"),o}(e).reduce(((e,n)=>{const o=function(t,e){var n;return(null!==(n=q[e])&&void 0!==n?n:()=>[])(t)}(t,n),s=function(t=[],e,n){return t.filter((t=>n(t)||!e(t)))}(o,c,u),l=function(t=[],e){return t.sort(((t,n)=>{const o=e(t),r=e(n);return o&&!r?-1:!o&&r?1:0}))}(s,u);return e[n]=r?R(l,{maxResults:i}):l.map((t=>[t])),e}),{})}(t,n),r=function(t,e){return function(t){const{selectors:e,combineBetweenSelectors:n,includeTag:o,maxCandidates:r}=t,i=n?R(e,{maxResults:r}):e.map((t=>[t]));return o?i.map(V):i}(e).map((e=>function(t,e){const n={};return t.forEach((t=>{const o=e[t];o.length>0&&(n[t]=o)})),function(t={}){let e=[];return Object.entries(t).forEach((([t,n])=>{e=n.flatMap((n=>0===e.length?[{[t]:n}]:e.map((e=>Object.assign(Object.assign({},e),{[t]:n})))))})),e}(n).map(Y)}(e,t))).filter((t=>t.length>0))}(o,n),i=p(r);return[...new Set(i)]}(t,o.root,o),n);for(const e of r)if(g(t,e,o.root))return e;return null}function G(t){return{value:t,include:!1}}function W({selectors:t,operator:n}){let o=[...w];t[e.tag]&&t[e.nthoftype]&&(o=o.filter((t=>t!==e.tag)));let r=\"\";return o.forEach((e=>{(t[e]||[]).forEach((({value:t,include:e})=>{e&&(r+=t)}))})),n.value+r}function H(n){return[\":root\",...y(n).reverse().map((n=>{const o=function(e,n,o=t.NONE){const r={};return n.forEach((t=>{Reflect.set(r,t,function(t,e){return F[e](t)}(e,t).map(G))})),{element:e,operator:N[o],selectors:r}}(n,[e.nthchild],t.DESCENDANT);return o.selectors.nthchild.forEach((t=>{t.include=!0})),o})).map(W)].join(\"\")}function U(t,n={}){const o=function(t){const e=(Array.isArray(t)?t:[t]).filter(r);return[...new Set(e)]}(t),i=function(t,n={}){const o=Object.assign(Object.assign({},c),n);return{selectors:(r=o.selectors,Array.isArray(r)?r.filter((t=>{return n=e,o=t,Object.values(n).includes(o);var n,o})):[]),whitelist:l(o.whitelist),blacklist:l(o.blacklist),root:f(o.root,t),combineWithinSelector:!!o.combineWithinSelector,combineBetweenSelectors:!!o.combineBetweenSelectors,includeTag:!!o.includeTag,maxCombinations:d(o.maxCombinations),maxCandidates:d(o.maxCandidates)};var r}(o[0],n);let u=\"\",s=i.root;function a(){return function(t,e,n=\"\",o){if(0===t.length)return null;const r=[t.length>1?t:[],...b(t,e).map((t=>[t]))];for(const t of r){const e=B(t,0,n,o);if(e)return{foundElements:t,selector:e}}return null}(o,s,u,i)}let m=a();for(;m;){const{foundElements:t,selector:e}=m;if(g(o,e,i.root))return e;s=t[0],u=e,m=a()}return o.length>1?o.map((t=>U(t,i))).join(\", \"):function(t){return t.map(H).join(\", \")}(o)}const z=U;return o})()));",
|
|
31
|
+
"text/javascript"
|
|
32
|
+
)));
|
|
33
|
+
|
|
34
|
+
const readiumPropertiesScript = `
|
|
35
|
+
window._readium_blockedEvents = [];
|
|
36
|
+
window._readium_blockEvents = false; // WebPub doesn't need event blocking
|
|
37
|
+
window._readium_eventBlocker = null;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const rBefore = (doc: Document) => scriptify(doc, cached("webpub-js-before", () => blobify(stripJS(readiumPropertiesScript), "text/javascript")));
|
|
41
|
+
const rAfter = (doc: Document) => scriptify(doc, cached("webpub-js-after", () => blobify(stripJS(`
|
|
42
|
+
if(window.onload) window.onload = new Proxy(window.onload, {
|
|
43
|
+
apply: function(target, receiver, args) {
|
|
44
|
+
if(!window._readium_blockEvents) {
|
|
45
|
+
Reflect.apply(target, receiver, args);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
_readium_blockedEvents.push([0, target, receiver, args]);
|
|
49
|
+
}
|
|
50
|
+
});`), "text/javascript")));
|
|
51
|
+
|
|
52
|
+
export class WebPubBlobBuilder {
|
|
53
|
+
private readonly item: Link;
|
|
54
|
+
private readonly burl: string;
|
|
55
|
+
private readonly pub: Publication;
|
|
56
|
+
private readonly cssProperties?: { [key: string]: string };
|
|
57
|
+
|
|
58
|
+
constructor(pub: Publication, baseURL: string, item: Link, cssProperties?: { [key: string]: string }) {
|
|
59
|
+
this.pub = pub;
|
|
60
|
+
this.item = item;
|
|
61
|
+
this.burl = item.toURL(baseURL) || "";
|
|
62
|
+
this.cssProperties = cssProperties;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async build(): Promise<string> {
|
|
66
|
+
if (!this.item.mediaType.isHTML) {
|
|
67
|
+
throw new Error(`Unsupported media type for WebPub: ${this.item.mediaType.string}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return await this.buildHtmlFrame();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async buildHtmlFrame(): Promise<string> {
|
|
74
|
+
// Load the HTML resource
|
|
75
|
+
const txt = await this.pub.get(this.item).readAsString();
|
|
76
|
+
if(!txt) throw new Error(`Failed reading item ${this.item.href}`);
|
|
77
|
+
const doc = new DOMParser().parseFromString(
|
|
78
|
+
txt,
|
|
79
|
+
this.item.mediaType.string as DOMParserSupportedType
|
|
80
|
+
);
|
|
81
|
+
const perror = doc.querySelector("parsererror");
|
|
82
|
+
if(perror) {
|
|
83
|
+
const details = perror.querySelector("div");
|
|
84
|
+
throw new Error(`Failed parsing item ${this.item.href}: ${details?.textContent || perror.textContent}`);
|
|
85
|
+
}
|
|
86
|
+
return this.finalizeDOM(doc, this.burl, this.item.mediaType, txt, this.cssProperties);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private hasExecutable(doc: Document): boolean {
|
|
90
|
+
return (
|
|
91
|
+
!!doc.querySelector("script") ||
|
|
92
|
+
!!doc.querySelector("body[onload]:not(body[onload=''])")
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private setProperties(cssProperties: { [key: string]: string }, doc: Document) {
|
|
97
|
+
for (const key in cssProperties) {
|
|
98
|
+
const value = cssProperties[key];
|
|
99
|
+
if (value) doc.documentElement.style.setProperty(key, value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private finalizeDOM(doc: Document, base: string | undefined, mediaType: any, txt?: string, cssProperties?: { [key: string]: string }): string {
|
|
104
|
+
if(!doc) return "";
|
|
105
|
+
|
|
106
|
+
// Add WebPubCSS stylesheet at end of head (like EPUB ReadiumCSS-after)
|
|
107
|
+
const webPubStyle = styleify(doc, webPubStylesheet);
|
|
108
|
+
doc.head.appendChild(webPubStyle);
|
|
109
|
+
if (cssProperties) {
|
|
110
|
+
this.setProperties(cssProperties, doc);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
doc.body.querySelectorAll("img").forEach((img) => {
|
|
114
|
+
img.setAttribute("fetchpriority", "high");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if(base !== undefined) {
|
|
118
|
+
const b = doc.createElement("base");
|
|
119
|
+
b.href = base;
|
|
120
|
+
b.dataset.readium = "true";
|
|
121
|
+
doc.head.firstChild!.before(b);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const hasExecutable = this.hasExecutable(doc);
|
|
125
|
+
if(hasExecutable) doc.head.firstChild!.before(rBefore(doc));
|
|
126
|
+
doc.head.firstChild!.before(cssSelectorGenerator(doc));
|
|
127
|
+
if(hasExecutable) doc.head.appendChild(rAfter(doc));
|
|
128
|
+
|
|
129
|
+
// Serialize properly based on content type
|
|
130
|
+
let serializedContent: string;
|
|
131
|
+
|
|
132
|
+
if (mediaType.string === "application/xhtml+xml") {
|
|
133
|
+
// XHTML: Use XMLSerializer for proper XML formatting
|
|
134
|
+
serializedContent = new XMLSerializer().serializeToString(doc);
|
|
135
|
+
} else {
|
|
136
|
+
// HTML: Use custom HTML serialization to preserve HTML formatting
|
|
137
|
+
serializedContent = this.serializeAsHTML(doc, txt || "");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Make blob from doc
|
|
141
|
+
return URL.createObjectURL(
|
|
142
|
+
new Blob([serializedContent], {
|
|
143
|
+
type: mediaType.isHTML
|
|
144
|
+
? mediaType.string
|
|
145
|
+
: "application/xhtml+xml",
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private serializeAsHTML(doc: Document, txt: string): string {
|
|
151
|
+
// For HTML content, try to preserve the original HTML structure
|
|
152
|
+
// while injecting our scripts
|
|
153
|
+
|
|
154
|
+
// Extract the original DOCTYPE if present
|
|
155
|
+
const doctypeMatch = txt.match(/<!DOCTYPE[^>]*>/i);
|
|
156
|
+
const doctype = doctypeMatch ? doctypeMatch[0] + "\n" : "";
|
|
157
|
+
|
|
158
|
+
// Get the HTML element and serialize it as HTML
|
|
159
|
+
const htmlElement = doc.documentElement;
|
|
160
|
+
let htmlContent = htmlElement.outerHTML;
|
|
161
|
+
|
|
162
|
+
// Try to preserve the original HTML structure
|
|
163
|
+
// This is a best-effort approach since there's no perfect HTML serializer
|
|
164
|
+
|
|
165
|
+
return doctype + htmlContent;
|
|
166
|
+
}
|
|
167
|
+
}
|