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/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 filter2 = SHOW_ELEMENT | SHOW_TEXT | SHOW_CDATA_SECTION;
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, filter2, { acceptNode: acceptNode2 });
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 resolveNavigation = (target) => {
3431
- if (typeof target === "number") {
3432
- return { index: target };
3433
- } else if (typeof target === "object") {
3434
- const [index, anchor] = sectionProgress.getSection(target.fraction);
3435
- return { index, anchor };
3436
- } else if (isCFI.test(target)) {
3437
- if (book.resolveCFI) {
3438
- return book.resolveCFI(target);
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
- } else {
3446
- const href = book.resolveHref?.(target);
3447
- if (href === null) return;
3448
- return href;
3265
+ setAnnotations((prev) => [...prev, cfi]);
3449
3266
  }
3450
- };
3451
- async function goTo(target) {
3452
- const resolved = resolveNavigation(target);
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: { book, sectionProgress, tocProgress, rendererRef, goTo, resolveNavigation, next, previous }, children });
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-fixed-layout" : "foliate-paginator";
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
- function useSearch(query) {
3577
- const { book, tocProgress, rendererRef, resolveNavigation } = useReader();
3578
- const [loading, setLoading] = React.useState(false);
3579
- const [results, setResults] = React.useState();
3580
- const [annotations, setAnnotations] = React.useState([]);
3581
- React.useEffect(() => {
3582
- if (!rendererRef.current) return;
3583
- for (const cfi of annotations) {
3584
- const content = rendererRef.current?.getContents();
3585
- for (const item of content) {
3586
- if ("overlayer" in item) {
3587
- const { overlayer } = item;
3588
- overlayer.remove(cfi);
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
- setAnnotations([]);
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 = new Search(book, language, tocProgress);
3613
- const searchIterator = search2.search({ query });
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
- searchResults.push({ label: result.label, href: result.subitems[0].cfi });
3623
- setResults([...searchResults]);
3669
+ setResults((prev) => [...prev ?? [], { label: result.label, href: result.subitems[0].cfi }]);
3624
3670
  for (const item of result.subitems) {
3625
- addAnnotation2(item.cfi);
3671
+ addAnnotation(item.cfi);
3626
3672
  }
3627
3673
  } else if ("cfi" in result && typeof result.cfi === "string") {
3628
- addAnnotation2(result.cfi);
3674
+ addAnnotation(result.cfi);
3629
3675
  }
3630
3676
  }
3631
3677
  };