@rspress-theme-anatole/theme-default 0.7.33 → 0.7.35

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.
Files changed (2) hide show
  1. package/dist/bundle.js +204 -115
  2. package/package.json +3 -3
package/dist/bundle.js CHANGED
@@ -2560,6 +2560,13 @@ class LocalProvider {
2560
2560
  #cjkIndex;
2561
2561
  #cyrillicIndex;
2562
2562
  #fetchPromise;
2563
+ #readyPromise;
2564
+ #shouldUseCjkIndex(lang) {
2565
+ return /^(ja|zh|ko)(-|$)/i.test(lang ?? '');
2566
+ }
2567
+ #shouldUseCyrillicIndex(lang) {
2568
+ return /^(ru|uk|bg|sr|mk|be|kk|ky|tg|mn)(-|$)/i.test(lang ?? '');
2569
+ }
2563
2570
  async #getPages(lang, version) {
2564
2571
  const searchIndexGroupID = `${version ?? ''}###${lang ?? ''}`;
2565
2572
  if (!__WEBPACK_EXTERNAL_MODULE_virtual_search_index_hash_00b0989e__["default"][searchIndexGroupID]) return [];
@@ -2587,39 +2594,55 @@ class LocalProvider {
2587
2594
  return this.#fetchPromise;
2588
2595
  }
2589
2596
  async init(options) {
2590
- const pagesForSearch = (await this.fetchSearchIndex(options)).map((page) => ({
2591
- ...page,
2592
- normalizedContent: normalizeTextCase(page.content),
2593
- headers: page.toc.map((header) => normalizeTextCase(header.text)).join(' '),
2594
- normalizedTitle: normalizeTextCase(page.title)
2595
- }));
2596
- const createOptions = {
2597
- tokenize: 'full',
2598
- document: {
2599
- id: 'id',
2600
- store: true,
2601
- index: [
2602
- 'normalizedTitle',
2603
- 'headers',
2604
- 'normalizedContent'
2605
- ]
2606
- },
2607
- cache: 100
2608
- };
2609
- this.#index = new __WEBPACK_EXTERNAL_MODULE_flexsearch__["default"].Document(createOptions);
2610
- this.#cjkIndex = new __WEBPACK_EXTERNAL_MODULE_flexsearch__["default"].Document({
2611
- ...createOptions,
2612
- tokenize: (str) => tokenize(str, cjkRegex)
2613
- });
2614
- this.#cyrillicIndex = new __WEBPACK_EXTERNAL_MODULE_flexsearch__["default"].Document({
2615
- ...createOptions,
2616
- tokenize: (str) => tokenize(str, LocalProvider_cyrillicRegex)
2617
- });
2618
- for (const item of pagesForSearch) {
2619
- this.#index.addAsync(item.id, item);
2620
- this.#cjkIndex.addAsync(item.id, item);
2621
- this.#cyrillicIndex.addAsync(item.id, item);
2597
+ if (this.#readyPromise) return this.#readyPromise;
2598
+ this.#readyPromise = (async () => {
2599
+ const { currentLang } = options;
2600
+ const pagesForSearch = (await this.fetchSearchIndex(options)).map((page) => ({
2601
+ ...page,
2602
+ normalizedContent: normalizeTextCase(page.content),
2603
+ headers: page.toc.map((header) => normalizeTextCase(header.text)).join(' '),
2604
+ normalizedTitle: normalizeTextCase(page.title)
2605
+ }));
2606
+ const createOptions = {
2607
+ tokenize: 'full',
2608
+ document: {
2609
+ id: 'id',
2610
+ store: true,
2611
+ index: [
2612
+ 'normalizedTitle',
2613
+ 'headers',
2614
+ 'normalizedContent'
2615
+ ]
2616
+ },
2617
+ cache: 100
2618
+ };
2619
+ this.#index = new __WEBPACK_EXTERNAL_MODULE_flexsearch__["default"].Document(createOptions);
2620
+ this.#cjkIndex = this.#shouldUseCjkIndex(currentLang) ? new __WEBPACK_EXTERNAL_MODULE_flexsearch__["default"].Document({
2621
+ ...createOptions,
2622
+ tokenize: (str) => tokenize(str, cjkRegex)
2623
+ }) : void 0;
2624
+ this.#cyrillicIndex = this.#shouldUseCyrillicIndex(currentLang) ? new __WEBPACK_EXTERNAL_MODULE_flexsearch__["default"].Document({
2625
+ ...createOptions,
2626
+ tokenize: (str) => tokenize(str, LocalProvider_cyrillicRegex)
2627
+ }) : void 0;
2628
+ const addTasks = [];
2629
+ for (const item of pagesForSearch) {
2630
+ addTasks.push(this.#index.addAsync(item.id, item));
2631
+ this.#cjkIndex && addTasks.push(this.#cjkIndex.addAsync(item.id, item));
2632
+ this.#cyrillicIndex && addTasks.push(this.#cyrillicIndex.addAsync(item.id, item));
2633
+ }
2634
+ await Promise.all(addTasks);
2635
+ })();
2636
+ try {
2637
+ await this.#readyPromise;
2638
+ } catch (error) {
2639
+ this.#readyPromise = void 0;
2640
+ this.#index = void 0;
2641
+ this.#cjkIndex = void 0;
2642
+ this.#cyrillicIndex = void 0;
2643
+ throw error;
2622
2644
  }
2645
+ return this.#readyPromise;
2623
2646
  }
2624
2647
  async search(query) {
2625
2648
  const { keyword, limit } = query;
@@ -2632,11 +2655,12 @@ class LocalProvider {
2632
2655
  'normalizedContent'
2633
2656
  ]
2634
2657
  };
2635
- const searchResult = await Promise.all([
2658
+ const searchTasks = [
2636
2659
  this.#index?.searchAsync(keyword, options),
2637
2660
  this.#cjkIndex?.searchAsync(keyword, options),
2638
2661
  this.#cyrillicIndex?.searchAsync(keyword, options)
2639
- ]);
2662
+ ].filter(Boolean);
2663
+ const searchResult = await Promise.all(searchTasks);
2640
2664
  const combinedSearchResult = [];
2641
2665
  const pushedId = new Set();
2642
2666
  function insertCombinedSearchResult(resultFromOneSearchIndex) {
@@ -2716,6 +2740,7 @@ class PageSearcher {
2716
2740
  return this.#provider?.fetchSearchIndex(this.#options);
2717
2741
  }
2718
2742
  async match(keyword, limit = 7) {
2743
+ await this.init();
2719
2744
  const searchResult = await this.#provider?.search({
2720
2745
  keyword,
2721
2746
  limit
@@ -6412,6 +6437,7 @@ const KEY_CODE = {
6412
6437
  SEARCH: 'KeyK',
6413
6438
  ESC: 'Escape'
6414
6439
  };
6440
+ const SEARCH_PREWARM_EVENT = 'rspress-search-prewarm';
6415
6441
  const useDebounce = (cb) => {
6416
6442
  const cbRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(cb);
6417
6443
  cbRef.current = cb;
@@ -6463,98 +6489,92 @@ function SearchPanel({ focused, setFocused }) {
6463
6489
  const currentSuggestions = searchResult[resultTabIndex]?.result ?? [];
6464
6490
  const currentRenderType = searchResult[resultTabIndex]?.renderType ?? types_RenderType.Default;
6465
6491
  if (false === search) return null;
6466
- // Extract related products from frontmatter for solution pages
6492
+ const SCOPE_EXCLUDED_PATHS = new Set([
6493
+ 'index.html',
6494
+ 'index',
6495
+ 'what-is-coming-in-cloud',
6496
+ 'what-is-coming-in-cloud.html',
6497
+ 'solution-pages'
6498
+ ]);
6499
+ const normalizeScopeSegments = (rawPath) => {
6500
+ if (!rawPath || 'string' != typeof rawPath) return [];
6501
+ if (/^(https?:)?\/\//i.test(rawPath)) return [];
6502
+ return rawPath.split('#')[0].split('?')[0].replace(/\\/g, '/').split('/').filter(Boolean).filter((segment) => '.' !== segment && '..' !== segment);
6503
+ };
6504
+ const normalizeProductName = (value) => {
6505
+ const normalizedValue = value?.replace(/\.html$/, '').trim();
6506
+ return normalizedValue || null;
6507
+ };
6508
+ const extractProductNameFromLink = (link) => {
6509
+ const segments = normalizeScopeSegments(link);
6510
+ if (!segments.length) return null;
6511
+ const productPageIndex = segments.indexOf('product-page');
6512
+ if (productPageIndex >= 0) return normalizeProductName(segments[productPageIndex + 1] || '');
6513
+ const firstSegment = normalizeProductName(segments[0] || '');
6514
+ if (!firstSegment || SCOPE_EXCLUDED_PATHS.has(firstSegment)) return null;
6515
+ return firstSegment;
6516
+ };
6467
6517
  const getRelatedProductsFromFrontmatter = (frontmatter) => {
6518
+ const explicitProducts = Array.isArray(frontmatter?.relatedProducts) ? frontmatter.relatedProducts.map((item) => normalizeProductName(String(item))).filter(Boolean) : [];
6519
+ if (explicitProducts.length) return Array.from(new Set(explicitProducts));
6468
6520
  if (!frontmatter?.features?.rows) return [];
6469
-
6470
6521
  const products = [];
6471
-
6472
- // Loop through all rows and items in features
6473
- frontmatter.features.rows.forEach(row => {
6474
- if (row.items) {
6475
- row.items.forEach(item => {
6476
- if (item.link) {
6477
- // Extract product name from link
6478
- const linkPath = item.link
6479
- .replace(/^\.\.\//, '') // Remove ../
6480
- .replace(/^\.\//, ''); // Remove ./
6481
-
6482
- const productName = linkPath.split('/')[0];
6483
-
6484
- if (productName && !products.includes(productName)) {
6485
- products.push(productName);
6486
- }
6487
- }
6488
- });
6489
- }
6522
+ frontmatter.features.rows.forEach((row) => {
6523
+ if (row.items) row.items.forEach((item) => {
6524
+ const productName = extractProductNameFromLink(item.link);
6525
+ if (productName && !products.includes(productName)) products.push(productName);
6526
+ });
6490
6527
  });
6491
-
6492
6528
  return products;
6493
6529
  };
6494
- // Calculate search scope
6495
- const searchScope = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(() => {
6496
- if (typeof window === 'undefined') return null;
6497
-
6498
- const pathname = window.location.pathname;
6530
+ const getScopedPathInfo = (pathname, basePath, localeLangs) => {
6499
6531
  const pathParts = pathname.split('/').filter(Boolean);
6500
-
6501
- // Get language settings from site config
6502
- const localeLanguages = Object.values(siteData?.themeConfig?.locales || {});
6503
- const langArr = localeLanguages.map((item) => item.lang) || [];
6504
-
6505
6532
  let startIndex = 0;
6506
-
6507
- // Skip base path if present
6508
- const baseTrimmed = (base || '').replace(/^\/|\/$/g, '');
6509
- if (baseTrimmed && pathParts[startIndex] === baseTrimmed) {
6510
- startIndex++;
6511
- }
6512
-
6513
- // Skip language prefix if present (e.g., /en/, /vn/, /zh/)
6514
- if (langArr.includes(pathParts[startIndex])) {
6515
- startIndex++;
6516
- }
6517
-
6533
+ const baseTrimmed = (basePath || '').replace(/^\/|\/$/g, '');
6534
+ if (baseTrimmed && pathParts[startIndex] === baseTrimmed) startIndex++;
6535
+ if (localeLangs.includes(pathParts[startIndex])) startIndex++;
6518
6536
  const firstSegment = pathParts[startIndex] || null;
6519
- const secondSegment = pathParts[startIndex + 1] || null;
6520
-
6521
- // Check if this is a solution page (contains 'solution-pages' in path)
6522
- if (firstSegment === 'solution-pages') {
6523
- const solutionFile = secondSegment || '';
6524
- const solutionName = solutionFile.replace(/\.html$/, '');
6525
-
6526
- // Get related products from frontmatter
6527
- const relatedProducts = getRelatedProductsFromFrontmatter(frontmatter);
6528
-
6529
- return {
6530
- scopeType: 'solution',
6531
- solutionName: solutionName,
6532
- relatedProducts: relatedProducts,
6533
- scopePath: null
6537
+ const secondSegment = normalizeProductName(pathParts[startIndex + 1] || '');
6538
+ if ('solution-pages' === firstSegment) return {
6539
+ scopeType: 'solution',
6540
+ solutionName: secondSegment,
6541
+ relatedProducts: getRelatedProductsFromFrontmatter(frontmatter),
6542
+ scopePath: null
6543
+ };
6544
+ if ('product-page' === firstSegment) {
6545
+ if (!secondSegment) return {
6546
+ scopeType: null,
6547
+ scopePath: null,
6548
+ relatedProducts: null
6534
6549
  };
6535
- }
6536
-
6537
- // Product page: /m365/... or /d365/...
6538
- const productName = firstSegment;
6539
-
6540
- // Exclude common non-product paths
6541
- const excludedPaths = ['index.html', 'index', 'what-is-coming-in-cloud', 'what-is-coming-in-cloud.html', 'solution-pages'];
6542
-
6543
- if (productName && !excludedPaths.includes(productName)) {
6544
6550
  return {
6545
6551
  scopeType: 'product',
6546
- productName: productName,
6547
- scopePath: `/${productName}`,
6552
+ productName: secondSegment,
6553
+ scopePath: `/${secondSegment}`,
6548
6554
  relatedProducts: null
6549
6555
  };
6550
6556
  }
6551
-
6552
- // No specific scope (homepage or other pages)
6557
+ const productName = normalizeProductName(firstSegment || '');
6558
+ if (productName && !SCOPE_EXCLUDED_PATHS.has(productName)) return {
6559
+ scopeType: 'product',
6560
+ productName: productName,
6561
+ scopePath: `/${productName}`,
6562
+ relatedProducts: null
6563
+ };
6553
6564
  return {
6554
6565
  scopeType: null,
6555
6566
  scopePath: null,
6556
6567
  relatedProducts: null
6557
6568
  };
6569
+ };
6570
+ // Calculate search scope
6571
+ const searchScope = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(() => {
6572
+ if (typeof window === 'undefined') return null;
6573
+ const pathname = window.location.pathname;
6574
+ // Get language settings from site config
6575
+ const localeLanguages = Object.values(siteData?.themeConfig?.locales || {});
6576
+ const langArr = localeLanguages.map((item) => item.lang) || [];
6577
+ return getScopedPathInfo(pathname, base, langArr);
6558
6578
  }, [focused, siteData, base, frontmatter]);
6559
6579
  const createSearcher = () => {
6560
6580
  if (pageSearcherRef.current) return pageSearcherRef.current;
@@ -6571,12 +6591,29 @@ function SearchPanel({ focused, setFocused }) {
6571
6591
  pageSearcherConfigRef.current = pageSearcherConfig;
6572
6592
  return pageSearcherRef.current;
6573
6593
  };
6574
- async function initSearch() {
6575
- if ('initial' !== initStatus) return;
6594
+ async function ensureSearcherReady() {
6576
6595
  const searcher = createSearcher();
6596
+ if ('inited' === initStatus) return searcher;
6597
+ if ('initing' === initStatus) {
6598
+ await searcher.init();
6599
+ return searcher;
6600
+ }
6577
6601
  setInitStatus('initing');
6578
- await searcher.init();
6579
- setInitStatus('inited');
6602
+ try {
6603
+ await searcher.init();
6604
+ setInitStatus('inited');
6605
+ return searcher;
6606
+ } catch (error) {
6607
+ console.error(error);
6608
+ pageSearcherRef.current = null;
6609
+ pageSearcherConfigRef.current = null;
6610
+ setInitStatus('initial');
6611
+ throw error;
6612
+ }
6613
+ }
6614
+ async function initSearch() {
6615
+ if ('initial' !== initStatus) return;
6616
+ const searcher = await ensureSearcherReady();
6580
6617
  const query = searchInputRef.current?.value;
6581
6618
  if (query) {
6582
6619
  const matched = await searcher.match(query);
@@ -6660,12 +6697,33 @@ function SearchPanel({ focused, setFocused }) {
6660
6697
  focused
6661
6698
  ]);
6662
6699
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
6663
- if ('requestIdleCallback' in window && !pageSearcherRef.current) window.requestIdleCallback(() => {
6664
- const searcher = createSearcher();
6665
- searcher.fetchSearchIndex();
6666
- });
6700
+ if ('undefined' === typeof window) return;
6701
+ let idleCallbackId;
6702
+ const prewarm = () => {
6703
+ ensureSearcherReady().catch(() => {});
6704
+ };
6705
+ const onPrewarm = () => {
6706
+ prewarm();
6707
+ };
6708
+ window.addEventListener(SEARCH_PREWARM_EVENT, onPrewarm);
6709
+ if (!pageSearcherRef.current) {
6710
+ if ('requestIdleCallback' in window) idleCallbackId = window.requestIdleCallback(() => {
6711
+ prewarm();
6712
+ });
6713
+ else idleCallbackId = window.setTimeout(() => {
6714
+ prewarm();
6715
+ }, 300);
6716
+ }
6717
+ return () => {
6718
+ window.removeEventListener(SEARCH_PREWARM_EVENT, onPrewarm);
6719
+ if (void 0 !== idleCallbackId) {
6720
+ if ('cancelIdleCallback' in window) window.cancelIdleCallback(idleCallbackId);
6721
+ else window.clearTimeout(idleCallbackId);
6722
+ }
6723
+ };
6667
6724
  }, []);
6668
6725
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
6726
+ if (!pageSearcherConfigRef.current) return;
6669
6727
  const { currentLang, currentVersion } = pageSearcherConfigRef.current ?? {};
6670
6728
  const isLangChanged = lang !== currentLang;
6671
6729
  const isVersionChanged = versionedSearch && version !== currentVersion;
@@ -6680,6 +6738,9 @@ function SearchPanel({ focused, setFocused }) {
6680
6738
  version,
6681
6739
  versionedSearch
6682
6740
  ]);
6741
+ const DEFAULT_SEARCH_LIMIT = 7;
6742
+ const SCOPED_SEARCH_LIMIT = 50;
6743
+ const SCOPED_SEARCH_RETRY_LIMIT = 100;
6683
6744
  const filterResultsByScope = (results, scope, basePath, currentLang) => {
6684
6745
  if (!scope || !scope.scopeType) {
6685
6746
  return results;
@@ -6743,18 +6804,38 @@ function SearchPanel({ focused, setFocused }) {
6743
6804
  };
6744
6805
  });
6745
6806
  };
6807
+ const hasSearchResults = (results) => results.some((resultGroup) => (resultGroup?.result?.length ?? 0) > 0);
6808
+ const getScopedSearchLimit = (scope, retry = false) => {
6809
+ if (!scope?.scopeType) return DEFAULT_SEARCH_LIMIT;
6810
+ return retry ? SCOPED_SEARCH_RETRY_LIMIT : SCOPED_SEARCH_LIMIT;
6811
+ };
6746
6812
  const handleQueryChangedImpl = async (value) => {
6747
6813
  let newQuery = value;
6748
6814
  setQuery(newQuery);
6749
6815
  if (search && 'remote' === search.mode && search.searchLoading) setIsSearching(true);
6750
6816
  if (newQuery) {
6817
+ try {
6818
+ await ensureSearcherReady();
6819
+ } catch (_error) {
6820
+ setIsSearching(false);
6821
+ return;
6822
+ }
6751
6823
  const searchResult = [];
6752
6824
  if ('beforeSearch' in __WEBPACK_EXTERNAL_MODULE_virtual_search_hooks_9d01d01f__) {
6753
6825
  const key = 'beforeSearch';
6754
6826
  const transformedQuery = await __WEBPACK_EXTERNAL_MODULE_virtual_search_hooks_9d01d01f__[key](newQuery);
6755
6827
  if (transformedQuery) newQuery = transformedQuery;
6756
6828
  }
6757
- const defaultSearchResult = await pageSearcherRef.current?.match(newQuery);
6829
+ let defaultSearchResult = await pageSearcherRef.current?.match(newQuery, getScopedSearchLimit(searchScope));
6830
+ let filteredDefaultSearchResult = filterResultsByScope(
6831
+ defaultSearchResult || DEFAULT_RESULT,
6832
+ searchScope,
6833
+ base,
6834
+ lang
6835
+ );
6836
+ if (searchScope?.scopeType && !hasSearchResults(filteredDefaultSearchResult)) {
6837
+ defaultSearchResult = await pageSearcherRef.current?.match(newQuery, getScopedSearchLimit(searchScope, true));
6838
+ }
6758
6839
  if (defaultSearchResult) searchResult.push(...defaultSearchResult);
6759
6840
  if ('onSearch' in __WEBPACK_EXTERNAL_MODULE_virtual_search_hooks_9d01d01f__) {
6760
6841
  const key = 'onSearch';
@@ -6963,10 +7044,16 @@ function Search() {
6963
7044
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
6964
7045
  setMetaKey(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl');
6965
7046
  }, []);
7047
+ const triggerSearchPrewarm = () => {
7048
+ if ('undefined' === typeof window) return;
7049
+ window.dispatchEvent(new CustomEvent(SEARCH_PREWARM_EVENT));
7050
+ };
6966
7051
  return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsxs)(__WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.Fragment, {
6967
7052
  children: [
6968
7053
  (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
6969
7054
  className: `rspress-nav-search-button ${Search_index_module.navSearchButton}`,
7055
+ onMouseEnter: triggerSearchPrewarm,
7056
+ onFocusCapture: triggerSearchPrewarm,
6970
7057
  onClick: () => setFocused(true),
6971
7058
  children: (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsxs)("button", {
6972
7059
  children: [
@@ -6997,6 +7084,8 @@ function Search() {
6997
7084
  }),
6998
7085
  (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
6999
7086
  className: Search_index_module.mobileNavSearchButton,
7087
+ onMouseEnter: triggerSearchPrewarm,
7088
+ onTouchStart: triggerSearchPrewarm,
7000
7089
  onClick: () => setFocused(true),
7001
7090
  children: (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(SvgWrapper, {
7002
7091
  icon: __WEBPACK_EXTERNAL_MODULE__theme_assets_search_1c295ce0__["default"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rspress-theme-anatole/theme-default",
3
3
  "author": "Anatole Tong",
4
- "version": "0.7.33",
4
+ "version": "0.7.35",
5
5
  "license": "MIT",
6
6
  "sideEffects": [
7
7
  "*.css",
@@ -21,8 +21,8 @@
21
21
  "types": "./dist/bundle.d.ts",
22
22
  "dependencies": {
23
23
  "@mdx-js/react": "2.3.0",
24
- "@rspress-theme-anatole/rspress-plugin-mermaid": "0.7.33",
25
- "@rspress-theme-anatole/shared": "0.7.33",
24
+ "@rspress-theme-anatole/rspress-plugin-mermaid": "0.7.35",
25
+ "@rspress-theme-anatole/shared": "0.7.35",
26
26
  "@rspress/runtime": "1.43.8",
27
27
  "body-scroll-lock": "4.0.0-beta.0",
28
28
  "copy-to-clipboard": "^3.3.3",