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.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 filter2 = SHOW_ELEMENT | SHOW_TEXT | SHOW_CDATA_SECTION;
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, filter2, { acceptNode: acceptNode2 });
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 resolveNavigation = (target) => {
3473
- if (typeof target === "number") {
3474
- return { index: target };
3475
- } else if (typeof target === "object") {
3476
- const [index, anchor] = sectionProgress.getSection(target.fraction);
3477
- return { index, anchor };
3478
- } else if (isCFI.test(target)) {
3479
- if (book.resolveCFI) {
3480
- return book.resolveCFI(target);
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
- } else {
3488
- const href = book.resolveHref?.(target);
3489
- if (href === null) return;
3490
- return href;
3307
+ setAnnotations((prev) => [...prev, cfi]);
3491
3308
  }
3492
- };
3493
- async function goTo(target) {
3494
- const resolved = resolveNavigation(target);
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: { book, sectionProgress, tocProgress, rendererRef, goTo, resolveNavigation, next, previous }, children });
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-fixed-layout" : "foliate-paginator";
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
- function useSearch(query) {
3619
- const { book, tocProgress, rendererRef, resolveNavigation } = useReader();
3620
- const [loading, setLoading] = React.useState(false);
3621
- const [results, setResults] = React.useState();
3622
- const [annotations, setAnnotations] = React.useState([]);
3623
- React.useEffect(() => {
3624
- if (!rendererRef.current) return;
3625
- for (const cfi of annotations) {
3626
- const content = rendererRef.current?.getContents();
3627
- for (const item of content) {
3628
- if ("overlayer" in item) {
3629
- const { overlayer } = item;
3630
- overlayer.remove(cfi);
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
- setAnnotations([]);
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 = new Search(book, language, tocProgress);
3655
- const searchIterator = search2.search({ query });
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
- searchResults.push({ label: result.label, href: result.subitems[0].cfi });
3665
- setResults([...searchResults]);
3711
+ setResults((prev) => [...prev ?? [], { label: result.label, href: result.subitems[0].cfi }]);
3666
3712
  for (const item of result.subitems) {
3667
- addAnnotation2(item.cfi);
3713
+ addAnnotation(item.cfi);
3668
3714
  }
3669
3715
  } else if ("cfi" in result && typeof result.cfi === "string") {
3670
- addAnnotation2(result.cfi);
3716
+ addAnnotation(result.cfi);
3671
3717
  }
3672
3718
  }
3673
3719
  };