react-ebookjs 0.0.3 → 0.0.5
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/README.md +2 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +305 -259
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +305 -259
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1705,202 +1705,6 @@ getSectionFractions_fn = function() {
|
|
|
1705
1705
|
return results;
|
|
1706
1706
|
};
|
|
1707
1707
|
|
|
1708
|
-
// src/vendor/foliate-js/search.js
|
|
1709
|
-
var CONTEXT_LENGTH = 50;
|
|
1710
|
-
var normalizeWhitespace3 = (str) => str.replace(/\s+/g, " ");
|
|
1711
|
-
var makeExcerpt = (strs, { startIndex, startOffset, endIndex, endOffset }) => {
|
|
1712
|
-
const start = strs[startIndex];
|
|
1713
|
-
const end = strs[endIndex];
|
|
1714
|
-
const match = start === end ? start.slice(startOffset, endOffset) : start.slice(startOffset) + strs.slice(start + 1, end).join("") + end.slice(0, endOffset);
|
|
1715
|
-
const trimmedStart = normalizeWhitespace3(start.slice(0, startOffset)).trimStart();
|
|
1716
|
-
const trimmedEnd = normalizeWhitespace3(end.slice(endOffset)).trimEnd();
|
|
1717
|
-
const ellipsisPre = trimmedStart.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
1718
|
-
const ellipsisPost = trimmedEnd.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
1719
|
-
const pre = `${ellipsisPre}${trimmedStart.slice(-CONTEXT_LENGTH)}`;
|
|
1720
|
-
const post = `${trimmedEnd.slice(0, CONTEXT_LENGTH)}${ellipsisPost}`;
|
|
1721
|
-
return { pre, match, post };
|
|
1722
|
-
};
|
|
1723
|
-
var simpleSearch = function* (strs, query, options = {}) {
|
|
1724
|
-
const { locales = "en", sensitivity } = options;
|
|
1725
|
-
const matchCase = sensitivity === "variant";
|
|
1726
|
-
const haystack = strs.join("");
|
|
1727
|
-
const lowerHaystack = matchCase ? haystack : haystack.toLocaleLowerCase(locales);
|
|
1728
|
-
const needle = matchCase ? query : query.toLocaleLowerCase(locales);
|
|
1729
|
-
const needleLength = needle.length;
|
|
1730
|
-
let index = -1;
|
|
1731
|
-
let strIndex = -1;
|
|
1732
|
-
let sum = 0;
|
|
1733
|
-
do {
|
|
1734
|
-
index = lowerHaystack.indexOf(needle, index + 1);
|
|
1735
|
-
if (index > -1) {
|
|
1736
|
-
while (sum <= index) sum += strs[++strIndex].length;
|
|
1737
|
-
const startIndex = strIndex;
|
|
1738
|
-
const startOffset = index - (sum - strs[strIndex].length);
|
|
1739
|
-
const end = index + needleLength;
|
|
1740
|
-
while (sum <= end) sum += strs[++strIndex].length;
|
|
1741
|
-
const endIndex = strIndex;
|
|
1742
|
-
const endOffset = end - (sum - strs[strIndex].length);
|
|
1743
|
-
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
1744
|
-
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
1745
|
-
}
|
|
1746
|
-
} while (index > -1);
|
|
1747
|
-
};
|
|
1748
|
-
var segmenterSearch = function* (strs, query, options = {}) {
|
|
1749
|
-
const { locales = "en", granularity = "word", sensitivity = "base" } = options;
|
|
1750
|
-
let segmenter, collator;
|
|
1751
|
-
try {
|
|
1752
|
-
segmenter = new Intl.Segmenter(locales, { usage: "search", granularity });
|
|
1753
|
-
collator = new Intl.Collator(locales, { sensitivity });
|
|
1754
|
-
} catch (e) {
|
|
1755
|
-
console.warn(e);
|
|
1756
|
-
segmenter = new Intl.Segmenter("en", { usage: "search", granularity });
|
|
1757
|
-
collator = new Intl.Collator("en", { sensitivity });
|
|
1758
|
-
}
|
|
1759
|
-
const queryLength = Array.from(segmenter.segment(query)).length;
|
|
1760
|
-
const substrArr = [];
|
|
1761
|
-
let strIndex = 0;
|
|
1762
|
-
let segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
1763
|
-
main: while (strIndex < strs.length) {
|
|
1764
|
-
while (substrArr.length < queryLength) {
|
|
1765
|
-
const { done, value } = segments.next();
|
|
1766
|
-
if (done) {
|
|
1767
|
-
strIndex++;
|
|
1768
|
-
if (strIndex < strs.length) {
|
|
1769
|
-
segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
1770
|
-
continue;
|
|
1771
|
-
} else break main;
|
|
1772
|
-
}
|
|
1773
|
-
const { index, segment } = value;
|
|
1774
|
-
if (!/[^\p{Format}]/u.test(segment)) continue;
|
|
1775
|
-
if (/\s/u.test(segment)) {
|
|
1776
|
-
if (!/\s/u.test(substrArr[substrArr.length - 1]?.segment))
|
|
1777
|
-
substrArr.push({ strIndex, index, segment: " " });
|
|
1778
|
-
continue;
|
|
1779
|
-
}
|
|
1780
|
-
value.strIndex = strIndex;
|
|
1781
|
-
substrArr.push(value);
|
|
1782
|
-
}
|
|
1783
|
-
const substr = substrArr.map((x) => x.segment).join("");
|
|
1784
|
-
if (collator.compare(query, substr) === 0) {
|
|
1785
|
-
const endIndex = strIndex;
|
|
1786
|
-
const lastSeg = substrArr[substrArr.length - 1];
|
|
1787
|
-
const endOffset = lastSeg.index + lastSeg.segment.length;
|
|
1788
|
-
const startIndex = substrArr[0].strIndex;
|
|
1789
|
-
const startOffset = substrArr[0].index;
|
|
1790
|
-
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
1791
|
-
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
1792
|
-
}
|
|
1793
|
-
substrArr.shift();
|
|
1794
|
-
}
|
|
1795
|
-
};
|
|
1796
|
-
var search = (strs, query, options) => {
|
|
1797
|
-
const { granularity = "grapheme", sensitivity = "base" } = options;
|
|
1798
|
-
if (!Intl?.Segmenter || granularity === "grapheme" && (sensitivity === "variant" || sensitivity === "accent"))
|
|
1799
|
-
return simpleSearch(strs, query, options);
|
|
1800
|
-
return segmenterSearch(strs, query, options);
|
|
1801
|
-
};
|
|
1802
|
-
var searchMatcher = (textWalker2, opts) => {
|
|
1803
|
-
const { defaultLocale, matchCase, matchDiacritics, matchWholeWords } = opts;
|
|
1804
|
-
return function* (doc, query) {
|
|
1805
|
-
const iter = textWalker2(doc, function* (strs, makeRange2) {
|
|
1806
|
-
for (const result of search(strs, query, {
|
|
1807
|
-
locales: doc.body.lang || doc.documentElement.lang || defaultLocale || "en",
|
|
1808
|
-
granularity: matchWholeWords ? "word" : "grapheme",
|
|
1809
|
-
sensitivity: matchDiacritics && matchCase ? "variant" : matchDiacritics && !matchCase ? "accent" : !matchDiacritics && matchCase ? "case" : "base"
|
|
1810
|
-
})) {
|
|
1811
|
-
const { startIndex, startOffset, endIndex, endOffset } = result.range;
|
|
1812
|
-
result.range = makeRange2(startIndex, startOffset, endIndex, endOffset);
|
|
1813
|
-
yield result;
|
|
1814
|
-
}
|
|
1815
|
-
});
|
|
1816
|
-
for (const result of iter) yield result;
|
|
1817
|
-
};
|
|
1818
|
-
};
|
|
1819
|
-
|
|
1820
|
-
// src/vendor/foliate-js/text-walker.js
|
|
1821
|
-
var walkRange = (range, walker) => {
|
|
1822
|
-
const nodes = [];
|
|
1823
|
-
for (let node = walker.currentNode; node; node = walker.nextNode()) {
|
|
1824
|
-
const compare = range.comparePoint(node, 0);
|
|
1825
|
-
if (compare === 0) nodes.push(node);
|
|
1826
|
-
else if (compare > 0) break;
|
|
1827
|
-
}
|
|
1828
|
-
return nodes;
|
|
1829
|
-
};
|
|
1830
|
-
var walkDocument = (_, walker) => {
|
|
1831
|
-
const nodes = [];
|
|
1832
|
-
for (let node = walker.nextNode(); node; node = walker.nextNode())
|
|
1833
|
-
nodes.push(node);
|
|
1834
|
-
return nodes;
|
|
1835
|
-
};
|
|
1836
|
-
var filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION;
|
|
1837
|
-
var acceptNode = (node) => {
|
|
1838
|
-
if (node.nodeType === 1) {
|
|
1839
|
-
const name = node.tagName.toLowerCase();
|
|
1840
|
-
if (name === "script" || name === "style") return NodeFilter.FILTER_REJECT;
|
|
1841
|
-
return NodeFilter.FILTER_SKIP;
|
|
1842
|
-
}
|
|
1843
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
1844
|
-
};
|
|
1845
|
-
var textWalker = function* (x, func) {
|
|
1846
|
-
const root = x.commonAncestorContainer ?? x.body ?? x;
|
|
1847
|
-
const walker = document.createTreeWalker(root, filter, { acceptNode });
|
|
1848
|
-
const walk = x.commonAncestorContainer ? walkRange : walkDocument;
|
|
1849
|
-
const nodes = walk(x, walker);
|
|
1850
|
-
const strs = nodes.map((node) => node.nodeValue);
|
|
1851
|
-
const makeRange2 = (startIndex, startOffset, endIndex, endOffset) => {
|
|
1852
|
-
const range = document.createRange();
|
|
1853
|
-
range.setStart(nodes[startIndex], startOffset);
|
|
1854
|
-
range.setEnd(nodes[endIndex], endOffset);
|
|
1855
|
-
return range;
|
|
1856
|
-
};
|
|
1857
|
-
for (const match of func(strs, makeRange2)) yield match;
|
|
1858
|
-
};
|
|
1859
|
-
|
|
1860
|
-
// src/lib/search.ts
|
|
1861
|
-
var Search = class {
|
|
1862
|
-
constructor(book, language, tocProgress) {
|
|
1863
|
-
this.book = book;
|
|
1864
|
-
this.language = language;
|
|
1865
|
-
this.tocProgress = tocProgress;
|
|
1866
|
-
}
|
|
1867
|
-
getCFI(index, range) {
|
|
1868
|
-
const baseCFI = this.book.sections?.[index]?.cfi ?? fake.fromIndex(index);
|
|
1869
|
-
if (!range) return baseCFI;
|
|
1870
|
-
return joinIndir(baseCFI, fromRange(range));
|
|
1871
|
-
}
|
|
1872
|
-
async *searchSection(matcher, query, index) {
|
|
1873
|
-
const doc = await this.book.sections?.[index]?.createDocument();
|
|
1874
|
-
for (const { range, excerpt } of matcher(doc, query))
|
|
1875
|
-
yield { cfi: this.getCFI(index, range), excerpt };
|
|
1876
|
-
}
|
|
1877
|
-
async *searchBook(matcher, query) {
|
|
1878
|
-
const { sections } = this.book;
|
|
1879
|
-
if (!sections) return;
|
|
1880
|
-
for (const [index, section] of sections.entries()) {
|
|
1881
|
-
if (!section?.createDocument) continue;
|
|
1882
|
-
const doc = await section.createDocument();
|
|
1883
|
-
const subitems = Array.from(matcher(doc, query), ({ range, excerpt }) => ({ cfi: this.getCFI(index, range), excerpt }));
|
|
1884
|
-
if (subitems.length) yield { index, subitems };
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
async *search({ query, index }) {
|
|
1888
|
-
const matcher = searchMatcher(textWalker, { defaultLocale: this.language, query, index });
|
|
1889
|
-
const iter = index != null ? this.searchSection(matcher, query, index) : this.searchBook(matcher, query);
|
|
1890
|
-
for await (const result of iter) {
|
|
1891
|
-
if ("subitems" in result && result.subitems) {
|
|
1892
|
-
yield {
|
|
1893
|
-
label: this.tocProgress?.getProgress(result.index)?.label ?? "",
|
|
1894
|
-
subitems: result.subitems
|
|
1895
|
-
};
|
|
1896
|
-
} else {
|
|
1897
|
-
yield result;
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
yield "done";
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
|
|
1904
1708
|
// src/vendor/foliate-js/overlayer.js
|
|
1905
1709
|
var createSVGElement = (tag) => document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
1906
1710
|
var _svg, _map;
|
|
@@ -2148,7 +1952,7 @@ var {
|
|
|
2148
1952
|
FILTER_REJECT,
|
|
2149
1953
|
FILTER_SKIP
|
|
2150
1954
|
} = NodeFilter;
|
|
2151
|
-
var
|
|
1955
|
+
var filter = SHOW_ELEMENT | SHOW_TEXT | SHOW_CDATA_SECTION;
|
|
2152
1956
|
var getBoundingClientRect = (target) => {
|
|
2153
1957
|
let top = Infinity, right = -Infinity, left = Infinity, bottom = -Infinity;
|
|
2154
1958
|
for (const rect of target.getClientRects()) {
|
|
@@ -2176,7 +1980,7 @@ var getVisibleRange = (doc, start, end, mapRect) => {
|
|
|
2176
1980
|
}
|
|
2177
1981
|
return FILTER_SKIP;
|
|
2178
1982
|
};
|
|
2179
|
-
const walker = doc.createTreeWalker(doc.body,
|
|
1983
|
+
const walker = doc.createTreeWalker(doc.body, filter, { acceptNode: acceptNode2 });
|
|
2180
1984
|
const nodes = [];
|
|
2181
1985
|
for (let node = walker.nextNode(); node; node = walker.nextNode())
|
|
2182
1986
|
nodes.push(node);
|
|
@@ -3422,38 +3226,67 @@ function useReader() {
|
|
|
3422
3226
|
}
|
|
3423
3227
|
return context;
|
|
3424
3228
|
}
|
|
3229
|
+
var resolveNavigation = (book, target, sectionProgress) => {
|
|
3230
|
+
if (typeof target === "number") {
|
|
3231
|
+
return { index: target };
|
|
3232
|
+
} else if (typeof target === "object") {
|
|
3233
|
+
const [index, anchor] = sectionProgress.getSection(target.fraction);
|
|
3234
|
+
return { index, anchor };
|
|
3235
|
+
} else if (isCFI.test(target)) {
|
|
3236
|
+
if (book.resolveCFI) {
|
|
3237
|
+
return book.resolveCFI(target);
|
|
3238
|
+
} else {
|
|
3239
|
+
const parts = parse(target);
|
|
3240
|
+
const index = fake.toIndex((parts.parent ?? parts).shift());
|
|
3241
|
+
const anchor = (doc) => toRange(doc, parts);
|
|
3242
|
+
return { index, anchor };
|
|
3243
|
+
}
|
|
3244
|
+
} else {
|
|
3245
|
+
const href = book.resolveHref?.(target);
|
|
3246
|
+
if (href === null) return;
|
|
3247
|
+
return href;
|
|
3248
|
+
}
|
|
3249
|
+
};
|
|
3425
3250
|
function Reader({ book, progress, onProgressChange, children }) {
|
|
3426
3251
|
const rendererRef = React.useRef(null);
|
|
3427
3252
|
const sectionProgress = React.useMemo(() => new SectionProgress(book.sections, 1500, 1600), [book]);
|
|
3428
3253
|
const [tocProgress, setTocProgress] = React.useState();
|
|
3429
3254
|
const [currentProgress, setCurrentProgress] = React.useState(0);
|
|
3430
|
-
const
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
const
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
} else {
|
|
3440
|
-
const parts = parse(target);
|
|
3441
|
-
const index = fake.toIndex((parts.parent ?? parts).shift());
|
|
3442
|
-
const anchor = (doc) => toRange(doc, parts);
|
|
3443
|
-
return { index, anchor };
|
|
3255
|
+
const [annotations, setAnnotations] = React.useState([]);
|
|
3256
|
+
const addAnnotation = React.useCallback((cfi) => {
|
|
3257
|
+
const resolved = resolveNavigation(book, cfi, sectionProgress);
|
|
3258
|
+
if (resolved) {
|
|
3259
|
+
const content = rendererRef.current?.getContents().find((x) => x.index === resolved.index && x.overlayer);
|
|
3260
|
+
if (content && "overlayer" in content) {
|
|
3261
|
+
const { overlayer, doc } = content;
|
|
3262
|
+
const range = doc && typeof resolved.anchor === "function" ? resolved.anchor(doc) : resolved.anchor;
|
|
3263
|
+
overlayer.add(cfi, range, Overlayer.outline);
|
|
3444
3264
|
}
|
|
3445
|
-
|
|
3446
|
-
const href = book.resolveHref?.(target);
|
|
3447
|
-
if (href === null) return;
|
|
3448
|
-
return href;
|
|
3265
|
+
setAnnotations((prev) => [...prev, cfi]);
|
|
3449
3266
|
}
|
|
3450
|
-
};
|
|
3451
|
-
|
|
3452
|
-
const
|
|
3267
|
+
}, [book, sectionProgress, rendererRef]);
|
|
3268
|
+
const removeAnnotation = React.useCallback((cfi) => {
|
|
3269
|
+
const content = rendererRef.current?.getContents();
|
|
3270
|
+
if (!content) return;
|
|
3271
|
+
for (const item of content) {
|
|
3272
|
+
if ("overlayer" in item) {
|
|
3273
|
+
const { overlayer } = item;
|
|
3274
|
+
overlayer.remove(cfi);
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
setAnnotations((prev) => prev.filter((x) => x !== cfi));
|
|
3278
|
+
}, [rendererRef]);
|
|
3279
|
+
const clearAnnotations = React.useCallback(() => {
|
|
3280
|
+
for (const cfi of annotations) {
|
|
3281
|
+
removeAnnotation(cfi);
|
|
3282
|
+
}
|
|
3283
|
+
}, [annotations, removeAnnotation]);
|
|
3284
|
+
const goTo = React.useCallback(async (target) => {
|
|
3285
|
+
const resolved = resolveNavigation(book, target, sectionProgress);
|
|
3453
3286
|
if (resolved) {
|
|
3454
3287
|
await rendererRef.current?.goTo(resolved);
|
|
3455
3288
|
}
|
|
3456
|
-
}
|
|
3289
|
+
}, [book, sectionProgress]);
|
|
3457
3290
|
React.useEffect(() => {
|
|
3458
3291
|
if (book.splitTOCHref && book.getTOCFragment) {
|
|
3459
3292
|
const ids = book.sections?.map((s) => s?.id);
|
|
@@ -3487,7 +3320,19 @@ function Reader({ book, progress, onProgressChange, children }) {
|
|
|
3487
3320
|
async function previous(distance) {
|
|
3488
3321
|
await rendererRef.current?.prev(distance);
|
|
3489
3322
|
}
|
|
3490
|
-
return /* @__PURE__ */ jsx(ReaderContext.Provider, { value: {
|
|
3323
|
+
return /* @__PURE__ */ jsx(ReaderContext.Provider, { value: {
|
|
3324
|
+
book,
|
|
3325
|
+
sectionProgress,
|
|
3326
|
+
annotations,
|
|
3327
|
+
tocProgress,
|
|
3328
|
+
rendererRef,
|
|
3329
|
+
goTo,
|
|
3330
|
+
next,
|
|
3331
|
+
previous,
|
|
3332
|
+
addAnnotation,
|
|
3333
|
+
removeAnnotation,
|
|
3334
|
+
clearAnnotations
|
|
3335
|
+
}, children });
|
|
3491
3336
|
}
|
|
3492
3337
|
function ReaderContent({
|
|
3493
3338
|
fontSize = 16,
|
|
@@ -3496,7 +3341,7 @@ function ReaderContent({
|
|
|
3496
3341
|
hyphenate = true,
|
|
3497
3342
|
flow = "paginated"
|
|
3498
3343
|
}) {
|
|
3499
|
-
const { book, rendererRef } = useReader();
|
|
3344
|
+
const { book, rendererRef, annotations, sectionProgress, goTo } = useReader();
|
|
3500
3345
|
const info = languageInfo(book);
|
|
3501
3346
|
React.useEffect(() => {
|
|
3502
3347
|
const renderer = rendererRef.current;
|
|
@@ -3510,17 +3355,44 @@ function ReaderContent({
|
|
|
3510
3355
|
console.log(value, range, index);
|
|
3511
3356
|
}
|
|
3512
3357
|
});
|
|
3358
|
+
for (const cfi of annotations) {
|
|
3359
|
+
const resolved = resolveNavigation(book, cfi, sectionProgress);
|
|
3360
|
+
if (resolved) {
|
|
3361
|
+
if (index === resolved.index) {
|
|
3362
|
+
const range = doc && typeof resolved.anchor === "function" ? resolved.anchor(doc) : resolved.anchor;
|
|
3363
|
+
overlayer.add(cfi, range, Overlayer.outline);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3513
3367
|
attach(overlayer);
|
|
3514
3368
|
};
|
|
3369
|
+
const onLoad = (e) => {
|
|
3370
|
+
const { doc, index } = e.detail;
|
|
3371
|
+
const section = book.sections?.[index];
|
|
3372
|
+
doc.addEventListener("click", (e2) => {
|
|
3373
|
+
const a = e2.target.closest("a[href]");
|
|
3374
|
+
if (!a) return;
|
|
3375
|
+
e2.preventDefault();
|
|
3376
|
+
const hrefAttr = a.getAttribute("href");
|
|
3377
|
+
const href = section?.resolveHref?.(hrefAttr) ?? hrefAttr;
|
|
3378
|
+
if (book?.isExternal?.(href)) {
|
|
3379
|
+
globalThis.open(href, "_blank");
|
|
3380
|
+
} else {
|
|
3381
|
+
goTo(href);
|
|
3382
|
+
}
|
|
3383
|
+
});
|
|
3384
|
+
};
|
|
3515
3385
|
renderer.addEventListener("create-overlayer", createOverlayer);
|
|
3386
|
+
renderer.addEventListener("load", onLoad);
|
|
3516
3387
|
renderer.setAttribute("exportparts", "head,foot,filter");
|
|
3517
3388
|
renderer.open(book);
|
|
3518
3389
|
renderer.next();
|
|
3519
3390
|
return () => {
|
|
3520
3391
|
renderer.removeEventListener("create-overlayer", createOverlayer);
|
|
3392
|
+
renderer.removeEventListener("load", onLoad);
|
|
3521
3393
|
};
|
|
3522
3394
|
}
|
|
3523
|
-
}, [book, rendererRef]);
|
|
3395
|
+
}, [book, rendererRef, annotations, goTo]);
|
|
3524
3396
|
React.useEffect(() => {
|
|
3525
3397
|
if (!rendererRef.current) return;
|
|
3526
3398
|
const css = `
|
|
@@ -3551,7 +3423,7 @@ function ReaderContent({
|
|
|
3551
3423
|
}
|
|
3552
3424
|
}, [fontSize, lineSpacing, justify, hyphenate, flow]);
|
|
3553
3425
|
const isFixedLayout = book.rendition?.layout === "pre-paginated";
|
|
3554
|
-
const Renderer = isFixedLayout ? "foliate-
|
|
3426
|
+
const Renderer = isFixedLayout ? "foliate-fxl" : "foliate-paginator";
|
|
3555
3427
|
return /* @__PURE__ */ jsx(
|
|
3556
3428
|
Renderer,
|
|
3557
3429
|
{
|
|
@@ -3569,48 +3441,223 @@ function ReaderPrevious({ children, ...props }) {
|
|
|
3569
3441
|
const { previous } = useReader();
|
|
3570
3442
|
return /* @__PURE__ */ jsx("button", { onClick: () => previous(), ...props, children });
|
|
3571
3443
|
}
|
|
3444
|
+
|
|
3445
|
+
// src/hooks/useBookNavigation.ts
|
|
3572
3446
|
function useBookNavigator() {
|
|
3573
3447
|
const { goTo, next, previous } = useReader();
|
|
3574
3448
|
return { goTo, next, previous };
|
|
3575
3449
|
}
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3450
|
+
|
|
3451
|
+
// src/hooks/useSearch.ts
|
|
3452
|
+
import * as React2 from "react";
|
|
3453
|
+
|
|
3454
|
+
// src/vendor/foliate-js/search.js
|
|
3455
|
+
var CONTEXT_LENGTH = 50;
|
|
3456
|
+
var normalizeWhitespace3 = (str) => str.replace(/\s+/g, " ");
|
|
3457
|
+
var makeExcerpt = (strs, { startIndex, startOffset, endIndex, endOffset }) => {
|
|
3458
|
+
const start = strs[startIndex];
|
|
3459
|
+
const end = strs[endIndex];
|
|
3460
|
+
const match = start === end ? start.slice(startOffset, endOffset) : start.slice(startOffset) + strs.slice(start + 1, end).join("") + end.slice(0, endOffset);
|
|
3461
|
+
const trimmedStart = normalizeWhitespace3(start.slice(0, startOffset)).trimStart();
|
|
3462
|
+
const trimmedEnd = normalizeWhitespace3(end.slice(endOffset)).trimEnd();
|
|
3463
|
+
const ellipsisPre = trimmedStart.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
3464
|
+
const ellipsisPost = trimmedEnd.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
3465
|
+
const pre = `${ellipsisPre}${trimmedStart.slice(-CONTEXT_LENGTH)}`;
|
|
3466
|
+
const post = `${trimmedEnd.slice(0, CONTEXT_LENGTH)}${ellipsisPost}`;
|
|
3467
|
+
return { pre, match, post };
|
|
3468
|
+
};
|
|
3469
|
+
var simpleSearch = function* (strs, query, options = {}) {
|
|
3470
|
+
const { locales = "en", sensitivity } = options;
|
|
3471
|
+
const matchCase = sensitivity === "variant";
|
|
3472
|
+
const haystack = strs.join("");
|
|
3473
|
+
const lowerHaystack = matchCase ? haystack : haystack.toLocaleLowerCase(locales);
|
|
3474
|
+
const needle = matchCase ? query : query.toLocaleLowerCase(locales);
|
|
3475
|
+
const needleLength = needle.length;
|
|
3476
|
+
let index = -1;
|
|
3477
|
+
let strIndex = -1;
|
|
3478
|
+
let sum = 0;
|
|
3479
|
+
do {
|
|
3480
|
+
index = lowerHaystack.indexOf(needle, index + 1);
|
|
3481
|
+
if (index > -1) {
|
|
3482
|
+
while (sum <= index) sum += strs[++strIndex].length;
|
|
3483
|
+
const startIndex = strIndex;
|
|
3484
|
+
const startOffset = index - (sum - strs[strIndex].length);
|
|
3485
|
+
const end = index + needleLength;
|
|
3486
|
+
while (sum <= end) sum += strs[++strIndex].length;
|
|
3487
|
+
const endIndex = strIndex;
|
|
3488
|
+
const endOffset = end - (sum - strs[strIndex].length);
|
|
3489
|
+
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
3490
|
+
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
3491
|
+
}
|
|
3492
|
+
} while (index > -1);
|
|
3493
|
+
};
|
|
3494
|
+
var segmenterSearch = function* (strs, query, options = {}) {
|
|
3495
|
+
const { locales = "en", granularity = "word", sensitivity = "base" } = options;
|
|
3496
|
+
let segmenter, collator;
|
|
3497
|
+
try {
|
|
3498
|
+
segmenter = new Intl.Segmenter(locales, { usage: "search", granularity });
|
|
3499
|
+
collator = new Intl.Collator(locales, { sensitivity });
|
|
3500
|
+
} catch (e) {
|
|
3501
|
+
console.warn(e);
|
|
3502
|
+
segmenter = new Intl.Segmenter("en", { usage: "search", granularity });
|
|
3503
|
+
collator = new Intl.Collator("en", { sensitivity });
|
|
3504
|
+
}
|
|
3505
|
+
const queryLength = Array.from(segmenter.segment(query)).length;
|
|
3506
|
+
const substrArr = [];
|
|
3507
|
+
let strIndex = 0;
|
|
3508
|
+
let segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
3509
|
+
main: while (strIndex < strs.length) {
|
|
3510
|
+
while (substrArr.length < queryLength) {
|
|
3511
|
+
const { done, value } = segments.next();
|
|
3512
|
+
if (done) {
|
|
3513
|
+
strIndex++;
|
|
3514
|
+
if (strIndex < strs.length) {
|
|
3515
|
+
segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
3516
|
+
continue;
|
|
3517
|
+
} else break main;
|
|
3518
|
+
}
|
|
3519
|
+
const { index, segment } = value;
|
|
3520
|
+
if (!/[^\p{Format}]/u.test(segment)) continue;
|
|
3521
|
+
if (/\s/u.test(segment)) {
|
|
3522
|
+
if (!/\s/u.test(substrArr[substrArr.length - 1]?.segment))
|
|
3523
|
+
substrArr.push({ strIndex, index, segment: " " });
|
|
3524
|
+
continue;
|
|
3590
3525
|
}
|
|
3526
|
+
value.strIndex = strIndex;
|
|
3527
|
+
substrArr.push(value);
|
|
3591
3528
|
}
|
|
3529
|
+
const substr = substrArr.map((x) => x.segment).join("");
|
|
3530
|
+
if (collator.compare(query, substr) === 0) {
|
|
3531
|
+
const endIndex = strIndex;
|
|
3532
|
+
const lastSeg = substrArr[substrArr.length - 1];
|
|
3533
|
+
const endOffset = lastSeg.index + lastSeg.segment.length;
|
|
3534
|
+
const startIndex = substrArr[0].strIndex;
|
|
3535
|
+
const startOffset = substrArr[0].index;
|
|
3536
|
+
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
3537
|
+
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
3538
|
+
}
|
|
3539
|
+
substrArr.shift();
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
var search = (strs, query, options) => {
|
|
3543
|
+
const { granularity = "grapheme", sensitivity = "base" } = options;
|
|
3544
|
+
if (!Intl?.Segmenter || granularity === "grapheme" && (sensitivity === "variant" || sensitivity === "accent"))
|
|
3545
|
+
return simpleSearch(strs, query, options);
|
|
3546
|
+
return segmenterSearch(strs, query, options);
|
|
3547
|
+
};
|
|
3548
|
+
var searchMatcher = (textWalker2, opts) => {
|
|
3549
|
+
const { defaultLocale, matchCase, matchDiacritics, matchWholeWords } = opts;
|
|
3550
|
+
return function* (doc, query) {
|
|
3551
|
+
const iter = textWalker2(doc, function* (strs, makeRange2) {
|
|
3552
|
+
for (const result of search(strs, query, {
|
|
3553
|
+
locales: doc.body.lang || doc.documentElement.lang || defaultLocale || "en",
|
|
3554
|
+
granularity: matchWholeWords ? "word" : "grapheme",
|
|
3555
|
+
sensitivity: matchDiacritics && matchCase ? "variant" : matchDiacritics && !matchCase ? "accent" : !matchDiacritics && matchCase ? "case" : "base"
|
|
3556
|
+
})) {
|
|
3557
|
+
const { startIndex, startOffset, endIndex, endOffset } = result.range;
|
|
3558
|
+
result.range = makeRange2(startIndex, startOffset, endIndex, endOffset);
|
|
3559
|
+
yield result;
|
|
3560
|
+
}
|
|
3561
|
+
});
|
|
3562
|
+
for (const result of iter) yield result;
|
|
3563
|
+
};
|
|
3564
|
+
};
|
|
3565
|
+
|
|
3566
|
+
// src/vendor/foliate-js/text-walker.js
|
|
3567
|
+
var walkRange = (range, walker) => {
|
|
3568
|
+
const nodes = [];
|
|
3569
|
+
for (let node = walker.currentNode; node; node = walker.nextNode()) {
|
|
3570
|
+
const compare = range.comparePoint(node, 0);
|
|
3571
|
+
if (compare === 0) nodes.push(node);
|
|
3572
|
+
else if (compare > 0) break;
|
|
3573
|
+
}
|
|
3574
|
+
return nodes;
|
|
3575
|
+
};
|
|
3576
|
+
var walkDocument = (_, walker) => {
|
|
3577
|
+
const nodes = [];
|
|
3578
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode())
|
|
3579
|
+
nodes.push(node);
|
|
3580
|
+
return nodes;
|
|
3581
|
+
};
|
|
3582
|
+
var filter2 = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION;
|
|
3583
|
+
var acceptNode = (node) => {
|
|
3584
|
+
if (node.nodeType === 1) {
|
|
3585
|
+
const name = node.tagName.toLowerCase();
|
|
3586
|
+
if (name === "script" || name === "style") return NodeFilter.FILTER_REJECT;
|
|
3587
|
+
return NodeFilter.FILTER_SKIP;
|
|
3588
|
+
}
|
|
3589
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
3590
|
+
};
|
|
3591
|
+
var textWalker = function* (x, func) {
|
|
3592
|
+
const root = x.commonAncestorContainer ?? x.body ?? x;
|
|
3593
|
+
const walker = document.createTreeWalker(root, filter2, { acceptNode });
|
|
3594
|
+
const walk = x.commonAncestorContainer ? walkRange : walkDocument;
|
|
3595
|
+
const nodes = walk(x, walker);
|
|
3596
|
+
const strs = nodes.map((node) => node.nodeValue);
|
|
3597
|
+
const makeRange2 = (startIndex, startOffset, endIndex, endOffset) => {
|
|
3598
|
+
const range = document.createRange();
|
|
3599
|
+
range.setStart(nodes[startIndex], startOffset);
|
|
3600
|
+
range.setEnd(nodes[endIndex], endOffset);
|
|
3601
|
+
return range;
|
|
3602
|
+
};
|
|
3603
|
+
for (const match of func(strs, makeRange2)) yield match;
|
|
3604
|
+
};
|
|
3605
|
+
|
|
3606
|
+
// src/lib/search.ts
|
|
3607
|
+
function getCFI(book, index, range) {
|
|
3608
|
+
const baseCFI = book.sections?.[index]?.cfi ?? fake.fromIndex(index);
|
|
3609
|
+
if (!range) return baseCFI;
|
|
3610
|
+
return joinIndir(baseCFI, fromRange(range));
|
|
3611
|
+
}
|
|
3612
|
+
function createSearcher(book, language, tocProgress) {
|
|
3613
|
+
async function* searchSection(matcher, query, index) {
|
|
3614
|
+
const doc = await book.sections?.[index]?.createDocument();
|
|
3615
|
+
for (const { range, excerpt } of matcher(doc, query))
|
|
3616
|
+
yield { cfi: getCFI(book, index, range), excerpt };
|
|
3617
|
+
}
|
|
3618
|
+
async function* searchBook(matcher, query) {
|
|
3619
|
+
const { sections } = book;
|
|
3620
|
+
if (!sections) return;
|
|
3621
|
+
for (const [index, section] of sections.entries()) {
|
|
3622
|
+
if (!section?.createDocument) continue;
|
|
3623
|
+
const doc = await section.createDocument();
|
|
3624
|
+
const subitems = Array.from(matcher(doc, query), ({ range, excerpt }) => ({ cfi: getCFI(book, index, range), excerpt }));
|
|
3625
|
+
if (subitems.length) yield { index, subitems };
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
async function* search2({ query, index }) {
|
|
3629
|
+
const matcher = searchMatcher(textWalker, { defaultLocale: language, query, index });
|
|
3630
|
+
const iter = index !== void 0 ? searchSection(matcher, query, index) : searchBook(matcher, query);
|
|
3631
|
+
for await (const result of iter) {
|
|
3632
|
+
if ("subitems" in result && result.subitems) {
|
|
3633
|
+
yield {
|
|
3634
|
+
label: tocProgress?.getProgress(result.index)?.label ?? "",
|
|
3635
|
+
subitems: result.subitems
|
|
3636
|
+
};
|
|
3637
|
+
} else {
|
|
3638
|
+
yield result;
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
yield "done";
|
|
3642
|
+
}
|
|
3643
|
+
return search2;
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
// src/hooks/useSearch.ts
|
|
3647
|
+
function useSearch(query) {
|
|
3648
|
+
const { book, tocProgress, rendererRef, addAnnotation, clearAnnotations } = useReader();
|
|
3649
|
+
const [loading, setLoading] = React2.useState(false);
|
|
3650
|
+
const [results, setResults] = React2.useState();
|
|
3651
|
+
React2.useEffect(() => {
|
|
3652
|
+
if (!rendererRef.current) return;
|
|
3592
3653
|
setResults(void 0);
|
|
3593
|
-
|
|
3654
|
+
clearAnnotations();
|
|
3594
3655
|
if (query) {
|
|
3595
|
-
let addAnnotation2 = function(cfi) {
|
|
3596
|
-
const resolved = resolveNavigation(cfi);
|
|
3597
|
-
if (resolved) {
|
|
3598
|
-
const content = rendererRef.current?.getContents().find((x) => x.index === resolved.index && x.overlayer);
|
|
3599
|
-
if (content && "overlayer" in content) {
|
|
3600
|
-
const { overlayer, doc } = content;
|
|
3601
|
-
const range = doc && typeof resolved.anchor === "function" ? resolved.anchor(doc) : resolved.anchor;
|
|
3602
|
-
overlayer.add(cfi, range, Overlayer.outline);
|
|
3603
|
-
setAnnotations((prev) => [...prev, cfi]);
|
|
3604
|
-
}
|
|
3605
|
-
}
|
|
3606
|
-
};
|
|
3607
|
-
var addAnnotation = addAnnotation2;
|
|
3608
3656
|
setLoading(true);
|
|
3609
3657
|
const abortController = new AbortController();
|
|
3610
|
-
const searchResults = [];
|
|
3611
3658
|
const language = languageInfo(book)?.canonical ?? "en";
|
|
3612
|
-
const search2 =
|
|
3613
|
-
const searchIterator = search2
|
|
3659
|
+
const search2 = createSearcher(book, language, tocProgress);
|
|
3660
|
+
const searchIterator = search2({ query });
|
|
3614
3661
|
const processSearchResults = async () => {
|
|
3615
3662
|
for await (const result of searchIterator) {
|
|
3616
3663
|
if (abortController.signal.aborted) return;
|
|
@@ -3619,13 +3666,12 @@ function useSearch(query) {
|
|
|
3619
3666
|
break;
|
|
3620
3667
|
}
|
|
3621
3668
|
if ("label" in result && typeof result.label === "string") {
|
|
3622
|
-
|
|
3623
|
-
setResults([...searchResults]);
|
|
3669
|
+
setResults((prev) => [...prev ?? [], { label: result.label, href: result.subitems[0].cfi }]);
|
|
3624
3670
|
for (const item of result.subitems) {
|
|
3625
|
-
|
|
3671
|
+
addAnnotation(item.cfi);
|
|
3626
3672
|
}
|
|
3627
3673
|
} else if ("cfi" in result && typeof result.cfi === "string") {
|
|
3628
|
-
|
|
3674
|
+
addAnnotation(result.cfi);
|
|
3629
3675
|
}
|
|
3630
3676
|
}
|
|
3631
3677
|
};
|