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.js
CHANGED
|
@@ -1747,202 +1747,6 @@ getSectionFractions_fn = function() {
|
|
|
1747
1747
|
return results;
|
|
1748
1748
|
};
|
|
1749
1749
|
|
|
1750
|
-
// src/vendor/foliate-js/search.js
|
|
1751
|
-
var CONTEXT_LENGTH = 50;
|
|
1752
|
-
var normalizeWhitespace3 = (str) => str.replace(/\s+/g, " ");
|
|
1753
|
-
var makeExcerpt = (strs, { startIndex, startOffset, endIndex, endOffset }) => {
|
|
1754
|
-
const start = strs[startIndex];
|
|
1755
|
-
const end = strs[endIndex];
|
|
1756
|
-
const match = start === end ? start.slice(startOffset, endOffset) : start.slice(startOffset) + strs.slice(start + 1, end).join("") + end.slice(0, endOffset);
|
|
1757
|
-
const trimmedStart = normalizeWhitespace3(start.slice(0, startOffset)).trimStart();
|
|
1758
|
-
const trimmedEnd = normalizeWhitespace3(end.slice(endOffset)).trimEnd();
|
|
1759
|
-
const ellipsisPre = trimmedStart.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
1760
|
-
const ellipsisPost = trimmedEnd.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
1761
|
-
const pre = `${ellipsisPre}${trimmedStart.slice(-CONTEXT_LENGTH)}`;
|
|
1762
|
-
const post = `${trimmedEnd.slice(0, CONTEXT_LENGTH)}${ellipsisPost}`;
|
|
1763
|
-
return { pre, match, post };
|
|
1764
|
-
};
|
|
1765
|
-
var simpleSearch = function* (strs, query, options = {}) {
|
|
1766
|
-
const { locales = "en", sensitivity } = options;
|
|
1767
|
-
const matchCase = sensitivity === "variant";
|
|
1768
|
-
const haystack = strs.join("");
|
|
1769
|
-
const lowerHaystack = matchCase ? haystack : haystack.toLocaleLowerCase(locales);
|
|
1770
|
-
const needle = matchCase ? query : query.toLocaleLowerCase(locales);
|
|
1771
|
-
const needleLength = needle.length;
|
|
1772
|
-
let index = -1;
|
|
1773
|
-
let strIndex = -1;
|
|
1774
|
-
let sum = 0;
|
|
1775
|
-
do {
|
|
1776
|
-
index = lowerHaystack.indexOf(needle, index + 1);
|
|
1777
|
-
if (index > -1) {
|
|
1778
|
-
while (sum <= index) sum += strs[++strIndex].length;
|
|
1779
|
-
const startIndex = strIndex;
|
|
1780
|
-
const startOffset = index - (sum - strs[strIndex].length);
|
|
1781
|
-
const end = index + needleLength;
|
|
1782
|
-
while (sum <= end) sum += strs[++strIndex].length;
|
|
1783
|
-
const endIndex = strIndex;
|
|
1784
|
-
const endOffset = end - (sum - strs[strIndex].length);
|
|
1785
|
-
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
1786
|
-
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
1787
|
-
}
|
|
1788
|
-
} while (index > -1);
|
|
1789
|
-
};
|
|
1790
|
-
var segmenterSearch = function* (strs, query, options = {}) {
|
|
1791
|
-
const { locales = "en", granularity = "word", sensitivity = "base" } = options;
|
|
1792
|
-
let segmenter, collator;
|
|
1793
|
-
try {
|
|
1794
|
-
segmenter = new Intl.Segmenter(locales, { usage: "search", granularity });
|
|
1795
|
-
collator = new Intl.Collator(locales, { sensitivity });
|
|
1796
|
-
} catch (e) {
|
|
1797
|
-
console.warn(e);
|
|
1798
|
-
segmenter = new Intl.Segmenter("en", { usage: "search", granularity });
|
|
1799
|
-
collator = new Intl.Collator("en", { sensitivity });
|
|
1800
|
-
}
|
|
1801
|
-
const queryLength = Array.from(segmenter.segment(query)).length;
|
|
1802
|
-
const substrArr = [];
|
|
1803
|
-
let strIndex = 0;
|
|
1804
|
-
let segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
1805
|
-
main: while (strIndex < strs.length) {
|
|
1806
|
-
while (substrArr.length < queryLength) {
|
|
1807
|
-
const { done, value } = segments.next();
|
|
1808
|
-
if (done) {
|
|
1809
|
-
strIndex++;
|
|
1810
|
-
if (strIndex < strs.length) {
|
|
1811
|
-
segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
1812
|
-
continue;
|
|
1813
|
-
} else break main;
|
|
1814
|
-
}
|
|
1815
|
-
const { index, segment } = value;
|
|
1816
|
-
if (!/[^\p{Format}]/u.test(segment)) continue;
|
|
1817
|
-
if (/\s/u.test(segment)) {
|
|
1818
|
-
if (!/\s/u.test(substrArr[substrArr.length - 1]?.segment))
|
|
1819
|
-
substrArr.push({ strIndex, index, segment: " " });
|
|
1820
|
-
continue;
|
|
1821
|
-
}
|
|
1822
|
-
value.strIndex = strIndex;
|
|
1823
|
-
substrArr.push(value);
|
|
1824
|
-
}
|
|
1825
|
-
const substr = substrArr.map((x) => x.segment).join("");
|
|
1826
|
-
if (collator.compare(query, substr) === 0) {
|
|
1827
|
-
const endIndex = strIndex;
|
|
1828
|
-
const lastSeg = substrArr[substrArr.length - 1];
|
|
1829
|
-
const endOffset = lastSeg.index + lastSeg.segment.length;
|
|
1830
|
-
const startIndex = substrArr[0].strIndex;
|
|
1831
|
-
const startOffset = substrArr[0].index;
|
|
1832
|
-
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
1833
|
-
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
1834
|
-
}
|
|
1835
|
-
substrArr.shift();
|
|
1836
|
-
}
|
|
1837
|
-
};
|
|
1838
|
-
var search = (strs, query, options) => {
|
|
1839
|
-
const { granularity = "grapheme", sensitivity = "base" } = options;
|
|
1840
|
-
if (!Intl?.Segmenter || granularity === "grapheme" && (sensitivity === "variant" || sensitivity === "accent"))
|
|
1841
|
-
return simpleSearch(strs, query, options);
|
|
1842
|
-
return segmenterSearch(strs, query, options);
|
|
1843
|
-
};
|
|
1844
|
-
var searchMatcher = (textWalker2, opts) => {
|
|
1845
|
-
const { defaultLocale, matchCase, matchDiacritics, matchWholeWords } = opts;
|
|
1846
|
-
return function* (doc, query) {
|
|
1847
|
-
const iter = textWalker2(doc, function* (strs, makeRange2) {
|
|
1848
|
-
for (const result of search(strs, query, {
|
|
1849
|
-
locales: doc.body.lang || doc.documentElement.lang || defaultLocale || "en",
|
|
1850
|
-
granularity: matchWholeWords ? "word" : "grapheme",
|
|
1851
|
-
sensitivity: matchDiacritics && matchCase ? "variant" : matchDiacritics && !matchCase ? "accent" : !matchDiacritics && matchCase ? "case" : "base"
|
|
1852
|
-
})) {
|
|
1853
|
-
const { startIndex, startOffset, endIndex, endOffset } = result.range;
|
|
1854
|
-
result.range = makeRange2(startIndex, startOffset, endIndex, endOffset);
|
|
1855
|
-
yield result;
|
|
1856
|
-
}
|
|
1857
|
-
});
|
|
1858
|
-
for (const result of iter) yield result;
|
|
1859
|
-
};
|
|
1860
|
-
};
|
|
1861
|
-
|
|
1862
|
-
// src/vendor/foliate-js/text-walker.js
|
|
1863
|
-
var walkRange = (range, walker) => {
|
|
1864
|
-
const nodes = [];
|
|
1865
|
-
for (let node = walker.currentNode; node; node = walker.nextNode()) {
|
|
1866
|
-
const compare = range.comparePoint(node, 0);
|
|
1867
|
-
if (compare === 0) nodes.push(node);
|
|
1868
|
-
else if (compare > 0) break;
|
|
1869
|
-
}
|
|
1870
|
-
return nodes;
|
|
1871
|
-
};
|
|
1872
|
-
var walkDocument = (_, walker) => {
|
|
1873
|
-
const nodes = [];
|
|
1874
|
-
for (let node = walker.nextNode(); node; node = walker.nextNode())
|
|
1875
|
-
nodes.push(node);
|
|
1876
|
-
return nodes;
|
|
1877
|
-
};
|
|
1878
|
-
var filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION;
|
|
1879
|
-
var acceptNode = (node) => {
|
|
1880
|
-
if (node.nodeType === 1) {
|
|
1881
|
-
const name = node.tagName.toLowerCase();
|
|
1882
|
-
if (name === "script" || name === "style") return NodeFilter.FILTER_REJECT;
|
|
1883
|
-
return NodeFilter.FILTER_SKIP;
|
|
1884
|
-
}
|
|
1885
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
1886
|
-
};
|
|
1887
|
-
var textWalker = function* (x, func) {
|
|
1888
|
-
const root = x.commonAncestorContainer ?? x.body ?? x;
|
|
1889
|
-
const walker = document.createTreeWalker(root, filter, { acceptNode });
|
|
1890
|
-
const walk = x.commonAncestorContainer ? walkRange : walkDocument;
|
|
1891
|
-
const nodes = walk(x, walker);
|
|
1892
|
-
const strs = nodes.map((node) => node.nodeValue);
|
|
1893
|
-
const makeRange2 = (startIndex, startOffset, endIndex, endOffset) => {
|
|
1894
|
-
const range = document.createRange();
|
|
1895
|
-
range.setStart(nodes[startIndex], startOffset);
|
|
1896
|
-
range.setEnd(nodes[endIndex], endOffset);
|
|
1897
|
-
return range;
|
|
1898
|
-
};
|
|
1899
|
-
for (const match of func(strs, makeRange2)) yield match;
|
|
1900
|
-
};
|
|
1901
|
-
|
|
1902
|
-
// src/lib/search.ts
|
|
1903
|
-
var Search = class {
|
|
1904
|
-
constructor(book, language, tocProgress) {
|
|
1905
|
-
this.book = book;
|
|
1906
|
-
this.language = language;
|
|
1907
|
-
this.tocProgress = tocProgress;
|
|
1908
|
-
}
|
|
1909
|
-
getCFI(index, range) {
|
|
1910
|
-
const baseCFI = this.book.sections?.[index]?.cfi ?? fake.fromIndex(index);
|
|
1911
|
-
if (!range) return baseCFI;
|
|
1912
|
-
return joinIndir(baseCFI, fromRange(range));
|
|
1913
|
-
}
|
|
1914
|
-
async *searchSection(matcher, query, index) {
|
|
1915
|
-
const doc = await this.book.sections?.[index]?.createDocument();
|
|
1916
|
-
for (const { range, excerpt } of matcher(doc, query))
|
|
1917
|
-
yield { cfi: this.getCFI(index, range), excerpt };
|
|
1918
|
-
}
|
|
1919
|
-
async *searchBook(matcher, query) {
|
|
1920
|
-
const { sections } = this.book;
|
|
1921
|
-
if (!sections) return;
|
|
1922
|
-
for (const [index, section] of sections.entries()) {
|
|
1923
|
-
if (!section?.createDocument) continue;
|
|
1924
|
-
const doc = await section.createDocument();
|
|
1925
|
-
const subitems = Array.from(matcher(doc, query), ({ range, excerpt }) => ({ cfi: this.getCFI(index, range), excerpt }));
|
|
1926
|
-
if (subitems.length) yield { index, subitems };
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
async *search({ query, index }) {
|
|
1930
|
-
const matcher = searchMatcher(textWalker, { defaultLocale: this.language, query, index });
|
|
1931
|
-
const iter = index != null ? this.searchSection(matcher, query, index) : this.searchBook(matcher, query);
|
|
1932
|
-
for await (const result of iter) {
|
|
1933
|
-
if ("subitems" in result && result.subitems) {
|
|
1934
|
-
yield {
|
|
1935
|
-
label: this.tocProgress?.getProgress(result.index)?.label ?? "",
|
|
1936
|
-
subitems: result.subitems
|
|
1937
|
-
};
|
|
1938
|
-
} else {
|
|
1939
|
-
yield result;
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
yield "done";
|
|
1943
|
-
}
|
|
1944
|
-
};
|
|
1945
|
-
|
|
1946
1750
|
// src/vendor/foliate-js/overlayer.js
|
|
1947
1751
|
var createSVGElement = (tag) => document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
1948
1752
|
var _svg, _map;
|
|
@@ -2190,7 +1994,7 @@ var {
|
|
|
2190
1994
|
FILTER_REJECT,
|
|
2191
1995
|
FILTER_SKIP
|
|
2192
1996
|
} = NodeFilter;
|
|
2193
|
-
var
|
|
1997
|
+
var filter = SHOW_ELEMENT | SHOW_TEXT | SHOW_CDATA_SECTION;
|
|
2194
1998
|
var getBoundingClientRect = (target) => {
|
|
2195
1999
|
let top = Infinity, right = -Infinity, left = Infinity, bottom = -Infinity;
|
|
2196
2000
|
for (const rect of target.getClientRects()) {
|
|
@@ -2218,7 +2022,7 @@ var getVisibleRange = (doc, start, end, mapRect) => {
|
|
|
2218
2022
|
}
|
|
2219
2023
|
return FILTER_SKIP;
|
|
2220
2024
|
};
|
|
2221
|
-
const walker = doc.createTreeWalker(doc.body,
|
|
2025
|
+
const walker = doc.createTreeWalker(doc.body, filter, { acceptNode: acceptNode2 });
|
|
2222
2026
|
const nodes = [];
|
|
2223
2027
|
for (let node = walker.nextNode(); node; node = walker.nextNode())
|
|
2224
2028
|
nodes.push(node);
|
|
@@ -3464,38 +3268,67 @@ function useReader() {
|
|
|
3464
3268
|
}
|
|
3465
3269
|
return context;
|
|
3466
3270
|
}
|
|
3271
|
+
var resolveNavigation = (book, target, sectionProgress) => {
|
|
3272
|
+
if (typeof target === "number") {
|
|
3273
|
+
return { index: target };
|
|
3274
|
+
} else if (typeof target === "object") {
|
|
3275
|
+
const [index, anchor] = sectionProgress.getSection(target.fraction);
|
|
3276
|
+
return { index, anchor };
|
|
3277
|
+
} else if (isCFI.test(target)) {
|
|
3278
|
+
if (book.resolveCFI) {
|
|
3279
|
+
return book.resolveCFI(target);
|
|
3280
|
+
} else {
|
|
3281
|
+
const parts = parse(target);
|
|
3282
|
+
const index = fake.toIndex((parts.parent ?? parts).shift());
|
|
3283
|
+
const anchor = (doc) => toRange(doc, parts);
|
|
3284
|
+
return { index, anchor };
|
|
3285
|
+
}
|
|
3286
|
+
} else {
|
|
3287
|
+
const href = book.resolveHref?.(target);
|
|
3288
|
+
if (href === null) return;
|
|
3289
|
+
return href;
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3467
3292
|
function Reader({ book, progress, onProgressChange, children }) {
|
|
3468
3293
|
const rendererRef = React.useRef(null);
|
|
3469
3294
|
const sectionProgress = React.useMemo(() => new SectionProgress(book.sections, 1500, 1600), [book]);
|
|
3470
3295
|
const [tocProgress, setTocProgress] = React.useState();
|
|
3471
3296
|
const [currentProgress, setCurrentProgress] = React.useState(0);
|
|
3472
|
-
const
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
const
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
} else {
|
|
3482
|
-
const parts = parse(target);
|
|
3483
|
-
const index = fake.toIndex((parts.parent ?? parts).shift());
|
|
3484
|
-
const anchor = (doc) => toRange(doc, parts);
|
|
3485
|
-
return { index, anchor };
|
|
3297
|
+
const [annotations, setAnnotations] = React.useState([]);
|
|
3298
|
+
const addAnnotation = React.useCallback((cfi) => {
|
|
3299
|
+
const resolved = resolveNavigation(book, cfi, sectionProgress);
|
|
3300
|
+
if (resolved) {
|
|
3301
|
+
const content = rendererRef.current?.getContents().find((x) => x.index === resolved.index && x.overlayer);
|
|
3302
|
+
if (content && "overlayer" in content) {
|
|
3303
|
+
const { overlayer, doc } = content;
|
|
3304
|
+
const range = doc && typeof resolved.anchor === "function" ? resolved.anchor(doc) : resolved.anchor;
|
|
3305
|
+
overlayer.add(cfi, range, Overlayer.outline);
|
|
3486
3306
|
}
|
|
3487
|
-
|
|
3488
|
-
const href = book.resolveHref?.(target);
|
|
3489
|
-
if (href === null) return;
|
|
3490
|
-
return href;
|
|
3307
|
+
setAnnotations((prev) => [...prev, cfi]);
|
|
3491
3308
|
}
|
|
3492
|
-
};
|
|
3493
|
-
|
|
3494
|
-
const
|
|
3309
|
+
}, [book, sectionProgress, rendererRef]);
|
|
3310
|
+
const removeAnnotation = React.useCallback((cfi) => {
|
|
3311
|
+
const content = rendererRef.current?.getContents();
|
|
3312
|
+
if (!content) return;
|
|
3313
|
+
for (const item of content) {
|
|
3314
|
+
if ("overlayer" in item) {
|
|
3315
|
+
const { overlayer } = item;
|
|
3316
|
+
overlayer.remove(cfi);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
setAnnotations((prev) => prev.filter((x) => x !== cfi));
|
|
3320
|
+
}, [rendererRef]);
|
|
3321
|
+
const clearAnnotations = React.useCallback(() => {
|
|
3322
|
+
for (const cfi of annotations) {
|
|
3323
|
+
removeAnnotation(cfi);
|
|
3324
|
+
}
|
|
3325
|
+
}, [annotations, removeAnnotation]);
|
|
3326
|
+
const goTo = React.useCallback(async (target) => {
|
|
3327
|
+
const resolved = resolveNavigation(book, target, sectionProgress);
|
|
3495
3328
|
if (resolved) {
|
|
3496
3329
|
await rendererRef.current?.goTo(resolved);
|
|
3497
3330
|
}
|
|
3498
|
-
}
|
|
3331
|
+
}, [book, sectionProgress]);
|
|
3499
3332
|
React.useEffect(() => {
|
|
3500
3333
|
if (book.splitTOCHref && book.getTOCFragment) {
|
|
3501
3334
|
const ids = book.sections?.map((s) => s?.id);
|
|
@@ -3529,7 +3362,19 @@ function Reader({ book, progress, onProgressChange, children }) {
|
|
|
3529
3362
|
async function previous(distance) {
|
|
3530
3363
|
await rendererRef.current?.prev(distance);
|
|
3531
3364
|
}
|
|
3532
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReaderContext.Provider, { value: {
|
|
3365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReaderContext.Provider, { value: {
|
|
3366
|
+
book,
|
|
3367
|
+
sectionProgress,
|
|
3368
|
+
annotations,
|
|
3369
|
+
tocProgress,
|
|
3370
|
+
rendererRef,
|
|
3371
|
+
goTo,
|
|
3372
|
+
next,
|
|
3373
|
+
previous,
|
|
3374
|
+
addAnnotation,
|
|
3375
|
+
removeAnnotation,
|
|
3376
|
+
clearAnnotations
|
|
3377
|
+
}, children });
|
|
3533
3378
|
}
|
|
3534
3379
|
function ReaderContent({
|
|
3535
3380
|
fontSize = 16,
|
|
@@ -3538,7 +3383,7 @@ function ReaderContent({
|
|
|
3538
3383
|
hyphenate = true,
|
|
3539
3384
|
flow = "paginated"
|
|
3540
3385
|
}) {
|
|
3541
|
-
const { book, rendererRef } = useReader();
|
|
3386
|
+
const { book, rendererRef, annotations, sectionProgress, goTo } = useReader();
|
|
3542
3387
|
const info = languageInfo(book);
|
|
3543
3388
|
React.useEffect(() => {
|
|
3544
3389
|
const renderer = rendererRef.current;
|
|
@@ -3552,17 +3397,44 @@ function ReaderContent({
|
|
|
3552
3397
|
console.log(value, range, index);
|
|
3553
3398
|
}
|
|
3554
3399
|
});
|
|
3400
|
+
for (const cfi of annotations) {
|
|
3401
|
+
const resolved = resolveNavigation(book, cfi, sectionProgress);
|
|
3402
|
+
if (resolved) {
|
|
3403
|
+
if (index === resolved.index) {
|
|
3404
|
+
const range = doc && typeof resolved.anchor === "function" ? resolved.anchor(doc) : resolved.anchor;
|
|
3405
|
+
overlayer.add(cfi, range, Overlayer.outline);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3555
3409
|
attach(overlayer);
|
|
3556
3410
|
};
|
|
3411
|
+
const onLoad = (e) => {
|
|
3412
|
+
const { doc, index } = e.detail;
|
|
3413
|
+
const section = book.sections?.[index];
|
|
3414
|
+
doc.addEventListener("click", (e2) => {
|
|
3415
|
+
const a = e2.target.closest("a[href]");
|
|
3416
|
+
if (!a) return;
|
|
3417
|
+
e2.preventDefault();
|
|
3418
|
+
const hrefAttr = a.getAttribute("href");
|
|
3419
|
+
const href = section?.resolveHref?.(hrefAttr) ?? hrefAttr;
|
|
3420
|
+
if (book?.isExternal?.(href)) {
|
|
3421
|
+
globalThis.open(href, "_blank");
|
|
3422
|
+
} else {
|
|
3423
|
+
goTo(href);
|
|
3424
|
+
}
|
|
3425
|
+
});
|
|
3426
|
+
};
|
|
3557
3427
|
renderer.addEventListener("create-overlayer", createOverlayer);
|
|
3428
|
+
renderer.addEventListener("load", onLoad);
|
|
3558
3429
|
renderer.setAttribute("exportparts", "head,foot,filter");
|
|
3559
3430
|
renderer.open(book);
|
|
3560
3431
|
renderer.next();
|
|
3561
3432
|
return () => {
|
|
3562
3433
|
renderer.removeEventListener("create-overlayer", createOverlayer);
|
|
3434
|
+
renderer.removeEventListener("load", onLoad);
|
|
3563
3435
|
};
|
|
3564
3436
|
}
|
|
3565
|
-
}, [book, rendererRef]);
|
|
3437
|
+
}, [book, rendererRef, annotations, goTo]);
|
|
3566
3438
|
React.useEffect(() => {
|
|
3567
3439
|
if (!rendererRef.current) return;
|
|
3568
3440
|
const css = `
|
|
@@ -3593,7 +3465,7 @@ function ReaderContent({
|
|
|
3593
3465
|
}
|
|
3594
3466
|
}, [fontSize, lineSpacing, justify, hyphenate, flow]);
|
|
3595
3467
|
const isFixedLayout = book.rendition?.layout === "pre-paginated";
|
|
3596
|
-
const Renderer = isFixedLayout ? "foliate-
|
|
3468
|
+
const Renderer = isFixedLayout ? "foliate-fxl" : "foliate-paginator";
|
|
3597
3469
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3598
3470
|
Renderer,
|
|
3599
3471
|
{
|
|
@@ -3611,48 +3483,223 @@ function ReaderPrevious({ children, ...props }) {
|
|
|
3611
3483
|
const { previous } = useReader();
|
|
3612
3484
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => previous(), ...props, children });
|
|
3613
3485
|
}
|
|
3486
|
+
|
|
3487
|
+
// src/hooks/useBookNavigation.ts
|
|
3614
3488
|
function useBookNavigator() {
|
|
3615
3489
|
const { goTo, next, previous } = useReader();
|
|
3616
3490
|
return { goTo, next, previous };
|
|
3617
3491
|
}
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3492
|
+
|
|
3493
|
+
// src/hooks/useSearch.ts
|
|
3494
|
+
var React2 = __toESM(require("react"));
|
|
3495
|
+
|
|
3496
|
+
// src/vendor/foliate-js/search.js
|
|
3497
|
+
var CONTEXT_LENGTH = 50;
|
|
3498
|
+
var normalizeWhitespace3 = (str) => str.replace(/\s+/g, " ");
|
|
3499
|
+
var makeExcerpt = (strs, { startIndex, startOffset, endIndex, endOffset }) => {
|
|
3500
|
+
const start = strs[startIndex];
|
|
3501
|
+
const end = strs[endIndex];
|
|
3502
|
+
const match = start === end ? start.slice(startOffset, endOffset) : start.slice(startOffset) + strs.slice(start + 1, end).join("") + end.slice(0, endOffset);
|
|
3503
|
+
const trimmedStart = normalizeWhitespace3(start.slice(0, startOffset)).trimStart();
|
|
3504
|
+
const trimmedEnd = normalizeWhitespace3(end.slice(endOffset)).trimEnd();
|
|
3505
|
+
const ellipsisPre = trimmedStart.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
3506
|
+
const ellipsisPost = trimmedEnd.length < CONTEXT_LENGTH ? "" : "\u2026";
|
|
3507
|
+
const pre = `${ellipsisPre}${trimmedStart.slice(-CONTEXT_LENGTH)}`;
|
|
3508
|
+
const post = `${trimmedEnd.slice(0, CONTEXT_LENGTH)}${ellipsisPost}`;
|
|
3509
|
+
return { pre, match, post };
|
|
3510
|
+
};
|
|
3511
|
+
var simpleSearch = function* (strs, query, options = {}) {
|
|
3512
|
+
const { locales = "en", sensitivity } = options;
|
|
3513
|
+
const matchCase = sensitivity === "variant";
|
|
3514
|
+
const haystack = strs.join("");
|
|
3515
|
+
const lowerHaystack = matchCase ? haystack : haystack.toLocaleLowerCase(locales);
|
|
3516
|
+
const needle = matchCase ? query : query.toLocaleLowerCase(locales);
|
|
3517
|
+
const needleLength = needle.length;
|
|
3518
|
+
let index = -1;
|
|
3519
|
+
let strIndex = -1;
|
|
3520
|
+
let sum = 0;
|
|
3521
|
+
do {
|
|
3522
|
+
index = lowerHaystack.indexOf(needle, index + 1);
|
|
3523
|
+
if (index > -1) {
|
|
3524
|
+
while (sum <= index) sum += strs[++strIndex].length;
|
|
3525
|
+
const startIndex = strIndex;
|
|
3526
|
+
const startOffset = index - (sum - strs[strIndex].length);
|
|
3527
|
+
const end = index + needleLength;
|
|
3528
|
+
while (sum <= end) sum += strs[++strIndex].length;
|
|
3529
|
+
const endIndex = strIndex;
|
|
3530
|
+
const endOffset = end - (sum - strs[strIndex].length);
|
|
3531
|
+
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
3532
|
+
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
3533
|
+
}
|
|
3534
|
+
} while (index > -1);
|
|
3535
|
+
};
|
|
3536
|
+
var segmenterSearch = function* (strs, query, options = {}) {
|
|
3537
|
+
const { locales = "en", granularity = "word", sensitivity = "base" } = options;
|
|
3538
|
+
let segmenter, collator;
|
|
3539
|
+
try {
|
|
3540
|
+
segmenter = new Intl.Segmenter(locales, { usage: "search", granularity });
|
|
3541
|
+
collator = new Intl.Collator(locales, { sensitivity });
|
|
3542
|
+
} catch (e) {
|
|
3543
|
+
console.warn(e);
|
|
3544
|
+
segmenter = new Intl.Segmenter("en", { usage: "search", granularity });
|
|
3545
|
+
collator = new Intl.Collator("en", { sensitivity });
|
|
3546
|
+
}
|
|
3547
|
+
const queryLength = Array.from(segmenter.segment(query)).length;
|
|
3548
|
+
const substrArr = [];
|
|
3549
|
+
let strIndex = 0;
|
|
3550
|
+
let segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
3551
|
+
main: while (strIndex < strs.length) {
|
|
3552
|
+
while (substrArr.length < queryLength) {
|
|
3553
|
+
const { done, value } = segments.next();
|
|
3554
|
+
if (done) {
|
|
3555
|
+
strIndex++;
|
|
3556
|
+
if (strIndex < strs.length) {
|
|
3557
|
+
segments = segmenter.segment(strs[strIndex])[Symbol.iterator]();
|
|
3558
|
+
continue;
|
|
3559
|
+
} else break main;
|
|
3560
|
+
}
|
|
3561
|
+
const { index, segment } = value;
|
|
3562
|
+
if (!/[^\p{Format}]/u.test(segment)) continue;
|
|
3563
|
+
if (/\s/u.test(segment)) {
|
|
3564
|
+
if (!/\s/u.test(substrArr[substrArr.length - 1]?.segment))
|
|
3565
|
+
substrArr.push({ strIndex, index, segment: " " });
|
|
3566
|
+
continue;
|
|
3632
3567
|
}
|
|
3568
|
+
value.strIndex = strIndex;
|
|
3569
|
+
substrArr.push(value);
|
|
3633
3570
|
}
|
|
3571
|
+
const substr = substrArr.map((x) => x.segment).join("");
|
|
3572
|
+
if (collator.compare(query, substr) === 0) {
|
|
3573
|
+
const endIndex = strIndex;
|
|
3574
|
+
const lastSeg = substrArr[substrArr.length - 1];
|
|
3575
|
+
const endOffset = lastSeg.index + lastSeg.segment.length;
|
|
3576
|
+
const startIndex = substrArr[0].strIndex;
|
|
3577
|
+
const startOffset = substrArr[0].index;
|
|
3578
|
+
const range = { startIndex, startOffset, endIndex, endOffset };
|
|
3579
|
+
yield { range, excerpt: makeExcerpt(strs, range) };
|
|
3580
|
+
}
|
|
3581
|
+
substrArr.shift();
|
|
3582
|
+
}
|
|
3583
|
+
};
|
|
3584
|
+
var search = (strs, query, options) => {
|
|
3585
|
+
const { granularity = "grapheme", sensitivity = "base" } = options;
|
|
3586
|
+
if (!Intl?.Segmenter || granularity === "grapheme" && (sensitivity === "variant" || sensitivity === "accent"))
|
|
3587
|
+
return simpleSearch(strs, query, options);
|
|
3588
|
+
return segmenterSearch(strs, query, options);
|
|
3589
|
+
};
|
|
3590
|
+
var searchMatcher = (textWalker2, opts) => {
|
|
3591
|
+
const { defaultLocale, matchCase, matchDiacritics, matchWholeWords } = opts;
|
|
3592
|
+
return function* (doc, query) {
|
|
3593
|
+
const iter = textWalker2(doc, function* (strs, makeRange2) {
|
|
3594
|
+
for (const result of search(strs, query, {
|
|
3595
|
+
locales: doc.body.lang || doc.documentElement.lang || defaultLocale || "en",
|
|
3596
|
+
granularity: matchWholeWords ? "word" : "grapheme",
|
|
3597
|
+
sensitivity: matchDiacritics && matchCase ? "variant" : matchDiacritics && !matchCase ? "accent" : !matchDiacritics && matchCase ? "case" : "base"
|
|
3598
|
+
})) {
|
|
3599
|
+
const { startIndex, startOffset, endIndex, endOffset } = result.range;
|
|
3600
|
+
result.range = makeRange2(startIndex, startOffset, endIndex, endOffset);
|
|
3601
|
+
yield result;
|
|
3602
|
+
}
|
|
3603
|
+
});
|
|
3604
|
+
for (const result of iter) yield result;
|
|
3605
|
+
};
|
|
3606
|
+
};
|
|
3607
|
+
|
|
3608
|
+
// src/vendor/foliate-js/text-walker.js
|
|
3609
|
+
var walkRange = (range, walker) => {
|
|
3610
|
+
const nodes = [];
|
|
3611
|
+
for (let node = walker.currentNode; node; node = walker.nextNode()) {
|
|
3612
|
+
const compare = range.comparePoint(node, 0);
|
|
3613
|
+
if (compare === 0) nodes.push(node);
|
|
3614
|
+
else if (compare > 0) break;
|
|
3615
|
+
}
|
|
3616
|
+
return nodes;
|
|
3617
|
+
};
|
|
3618
|
+
var walkDocument = (_, walker) => {
|
|
3619
|
+
const nodes = [];
|
|
3620
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode())
|
|
3621
|
+
nodes.push(node);
|
|
3622
|
+
return nodes;
|
|
3623
|
+
};
|
|
3624
|
+
var filter2 = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION;
|
|
3625
|
+
var acceptNode = (node) => {
|
|
3626
|
+
if (node.nodeType === 1) {
|
|
3627
|
+
const name = node.tagName.toLowerCase();
|
|
3628
|
+
if (name === "script" || name === "style") return NodeFilter.FILTER_REJECT;
|
|
3629
|
+
return NodeFilter.FILTER_SKIP;
|
|
3630
|
+
}
|
|
3631
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
3632
|
+
};
|
|
3633
|
+
var textWalker = function* (x, func) {
|
|
3634
|
+
const root = x.commonAncestorContainer ?? x.body ?? x;
|
|
3635
|
+
const walker = document.createTreeWalker(root, filter2, { acceptNode });
|
|
3636
|
+
const walk = x.commonAncestorContainer ? walkRange : walkDocument;
|
|
3637
|
+
const nodes = walk(x, walker);
|
|
3638
|
+
const strs = nodes.map((node) => node.nodeValue);
|
|
3639
|
+
const makeRange2 = (startIndex, startOffset, endIndex, endOffset) => {
|
|
3640
|
+
const range = document.createRange();
|
|
3641
|
+
range.setStart(nodes[startIndex], startOffset);
|
|
3642
|
+
range.setEnd(nodes[endIndex], endOffset);
|
|
3643
|
+
return range;
|
|
3644
|
+
};
|
|
3645
|
+
for (const match of func(strs, makeRange2)) yield match;
|
|
3646
|
+
};
|
|
3647
|
+
|
|
3648
|
+
// src/lib/search.ts
|
|
3649
|
+
function getCFI(book, index, range) {
|
|
3650
|
+
const baseCFI = book.sections?.[index]?.cfi ?? fake.fromIndex(index);
|
|
3651
|
+
if (!range) return baseCFI;
|
|
3652
|
+
return joinIndir(baseCFI, fromRange(range));
|
|
3653
|
+
}
|
|
3654
|
+
function createSearcher(book, language, tocProgress) {
|
|
3655
|
+
async function* searchSection(matcher, query, index) {
|
|
3656
|
+
const doc = await book.sections?.[index]?.createDocument();
|
|
3657
|
+
for (const { range, excerpt } of matcher(doc, query))
|
|
3658
|
+
yield { cfi: getCFI(book, index, range), excerpt };
|
|
3659
|
+
}
|
|
3660
|
+
async function* searchBook(matcher, query) {
|
|
3661
|
+
const { sections } = book;
|
|
3662
|
+
if (!sections) return;
|
|
3663
|
+
for (const [index, section] of sections.entries()) {
|
|
3664
|
+
if (!section?.createDocument) continue;
|
|
3665
|
+
const doc = await section.createDocument();
|
|
3666
|
+
const subitems = Array.from(matcher(doc, query), ({ range, excerpt }) => ({ cfi: getCFI(book, index, range), excerpt }));
|
|
3667
|
+
if (subitems.length) yield { index, subitems };
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
async function* search2({ query, index }) {
|
|
3671
|
+
const matcher = searchMatcher(textWalker, { defaultLocale: language, query, index });
|
|
3672
|
+
const iter = index !== void 0 ? searchSection(matcher, query, index) : searchBook(matcher, query);
|
|
3673
|
+
for await (const result of iter) {
|
|
3674
|
+
if ("subitems" in result && result.subitems) {
|
|
3675
|
+
yield {
|
|
3676
|
+
label: tocProgress?.getProgress(result.index)?.label ?? "",
|
|
3677
|
+
subitems: result.subitems
|
|
3678
|
+
};
|
|
3679
|
+
} else {
|
|
3680
|
+
yield result;
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
yield "done";
|
|
3684
|
+
}
|
|
3685
|
+
return search2;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// src/hooks/useSearch.ts
|
|
3689
|
+
function useSearch(query) {
|
|
3690
|
+
const { book, tocProgress, rendererRef, addAnnotation, clearAnnotations } = useReader();
|
|
3691
|
+
const [loading, setLoading] = React2.useState(false);
|
|
3692
|
+
const [results, setResults] = React2.useState();
|
|
3693
|
+
React2.useEffect(() => {
|
|
3694
|
+
if (!rendererRef.current) return;
|
|
3634
3695
|
setResults(void 0);
|
|
3635
|
-
|
|
3696
|
+
clearAnnotations();
|
|
3636
3697
|
if (query) {
|
|
3637
|
-
let addAnnotation2 = function(cfi) {
|
|
3638
|
-
const resolved = resolveNavigation(cfi);
|
|
3639
|
-
if (resolved) {
|
|
3640
|
-
const content = rendererRef.current?.getContents().find((x) => x.index === resolved.index && x.overlayer);
|
|
3641
|
-
if (content && "overlayer" in content) {
|
|
3642
|
-
const { overlayer, doc } = content;
|
|
3643
|
-
const range = doc && typeof resolved.anchor === "function" ? resolved.anchor(doc) : resolved.anchor;
|
|
3644
|
-
overlayer.add(cfi, range, Overlayer.outline);
|
|
3645
|
-
setAnnotations((prev) => [...prev, cfi]);
|
|
3646
|
-
}
|
|
3647
|
-
}
|
|
3648
|
-
};
|
|
3649
|
-
var addAnnotation = addAnnotation2;
|
|
3650
3698
|
setLoading(true);
|
|
3651
3699
|
const abortController = new AbortController();
|
|
3652
|
-
const searchResults = [];
|
|
3653
3700
|
const language = languageInfo(book)?.canonical ?? "en";
|
|
3654
|
-
const search2 =
|
|
3655
|
-
const searchIterator = search2
|
|
3701
|
+
const search2 = createSearcher(book, language, tocProgress);
|
|
3702
|
+
const searchIterator = search2({ query });
|
|
3656
3703
|
const processSearchResults = async () => {
|
|
3657
3704
|
for await (const result of searchIterator) {
|
|
3658
3705
|
if (abortController.signal.aborted) return;
|
|
@@ -3661,13 +3708,12 @@ function useSearch(query) {
|
|
|
3661
3708
|
break;
|
|
3662
3709
|
}
|
|
3663
3710
|
if ("label" in result && typeof result.label === "string") {
|
|
3664
|
-
|
|
3665
|
-
setResults([...searchResults]);
|
|
3711
|
+
setResults((prev) => [...prev ?? [], { label: result.label, href: result.subitems[0].cfi }]);
|
|
3666
3712
|
for (const item of result.subitems) {
|
|
3667
|
-
|
|
3713
|
+
addAnnotation(item.cfi);
|
|
3668
3714
|
}
|
|
3669
3715
|
} else if ("cfi" in result && typeof result.cfi === "string") {
|
|
3670
|
-
|
|
3716
|
+
addAnnotation(result.cfi);
|
|
3671
3717
|
}
|
|
3672
3718
|
}
|
|
3673
3719
|
};
|