@rspress-theme-anatole/theme-default 0.7.15 → 0.7.17

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 +362 -4
  2. package/package.json +3 -3
package/dist/bundle.js CHANGED
@@ -4576,6 +4576,182 @@ function NavVersions() {
4576
4576
  })
4577
4577
  });
4578
4578
  }
4579
+ // Utility functions for cookie management
4580
+ function setCookie(name, value, days = 7) {
4581
+ const expires = new Date();
4582
+ expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
4583
+ document.cookie = `${name}=${encodeURIComponent(JSON.stringify(value))};expires=${expires.toUTCString()};path=/`;
4584
+ }
4585
+
4586
+ function getCookie(name) {
4587
+ const nameEQ = name + "=";
4588
+ const ca = document.cookie.split(';');
4589
+ for (let i = 0; i < ca.length; i++) {
4590
+ let c = ca[i];
4591
+ while (c.charAt(0) === ' ') c = c.substring(1, c.length);
4592
+ if (c.indexOf(nameEQ) === 0) {
4593
+ try {
4594
+ return JSON.parse(decodeURIComponent(c.substring(nameEQ.length, c.length)));
4595
+ } catch (e) {
4596
+ return null;
4597
+ }
4598
+ }
4599
+ }
4600
+ return null;
4601
+ }
4602
+
4603
+ function deleteCookie(name) {
4604
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/`;
4605
+ }
4606
+
4607
+ // User Authentication Component
4608
+ function UserAuth() {
4609
+ const { siteData } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.usePageData)();
4610
+ const [user, setUser] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(null);
4611
+ const [showDropdown, setShowDropdown] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(false);
4612
+ const dropdownRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
4613
+
4614
+ // Get sign in URL from site config
4615
+ const signInUrl = siteData?.themeConfig?.auth?.signInUrl;
4616
+
4617
+ (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
4618
+ // Check for user cookie on component mount
4619
+ const userData = getCookie('user_auth');
4620
+ if (userData) {
4621
+ setUser(userData);
4622
+ }
4623
+
4624
+ // Listen for storage events to sync across tabs
4625
+ const handleStorageChange = () => {
4626
+ const userData = getCookie('user_auth');
4627
+ setUser(userData);
4628
+ };
4629
+
4630
+ window.addEventListener('storage', handleStorageChange);
4631
+ return () => window.removeEventListener('storage', handleStorageChange);
4632
+ }, []);
4633
+
4634
+ (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
4635
+ // Close dropdown when clicking outside
4636
+ function handleClickOutside(event) {
4637
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
4638
+ setShowDropdown(false);
4639
+ }
4640
+ }
4641
+
4642
+ document.addEventListener('mousedown', handleClickOutside);
4643
+ return () => document.removeEventListener('mousedown', handleClickOutside);
4644
+ }, []);
4645
+
4646
+ const handleSignIn = async () => {
4647
+ if (!signInUrl) {
4648
+ console.warn('Sign in URL not configured');
4649
+ return;
4650
+ }
4651
+
4652
+ try {
4653
+ // Open sign in URL in new window/tab
4654
+ const authWindow = window.open(signInUrl, '_blank', 'width=600,height=600');
4655
+
4656
+ // Listen for messages from auth window
4657
+ const handleMessage = (event) => {
4658
+ if (event.origin !== new URL(signInUrl).origin) return;
4659
+
4660
+ if (event.data && event.data.type === 'AUTH_SUCCESS') {
4661
+ const userData = {
4662
+ username: event.data.username,
4663
+ avatar: event.data.avatar
4664
+ };
4665
+
4666
+ setCookie('user_auth', userData);
4667
+ setUser(userData);
4668
+ authWindow.close();
4669
+
4670
+ // Remove event listener
4671
+ window.removeEventListener('message', handleMessage);
4672
+ }
4673
+ };
4674
+
4675
+ window.addEventListener('message', handleMessage);
4676
+
4677
+ // Fallback: poll for cookie changes (in case postMessage isn't used)
4678
+ const pollInterval = setInterval(() => {
4679
+ if (authWindow.closed) {
4680
+ clearInterval(pollInterval);
4681
+ window.removeEventListener('message', handleMessage);
4682
+
4683
+ // Check if user data was set
4684
+ const userData = getCookie('user_auth');
4685
+ if (userData) {
4686
+ setUser(userData);
4687
+ }
4688
+ }
4689
+ }, 1000);
4690
+
4691
+ } catch (error) {
4692
+ console.error('Sign in error:', error);
4693
+ }
4694
+ };
4695
+
4696
+ const handleSignOut = () => {
4697
+ deleteCookie('user_auth');
4698
+ setUser(null);
4699
+ setShowDropdown(false);
4700
+ };
4701
+
4702
+ if (!user) {
4703
+ return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("button", {
4704
+ onClick: handleSignIn,
4705
+ className: "py-2 px-6 text-sm font-medium text-white hover:text-text-2 transition-colors duration-200 border border-gray-300 rounded-md hover:border-gray-400",
4706
+ style: {
4707
+ backgroundColor: 'rgb(239, 72, 61)',
4708
+ color: 'white',
4709
+ },
4710
+ children: "Sign in"
4711
+ });
4712
+ }
4713
+
4714
+ return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
4715
+ className: "relative",
4716
+ ref: dropdownRef,
4717
+ children: [
4718
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("button", {
4719
+ onClick: () => setShowDropdown(!showDropdown),
4720
+ className: "flex items-center gap-2 px-3 py-2 text-sm font-medium text-text-1 hover:text-text-2 transition-colors duration-200 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800",
4721
+ children: [
4722
+ user.avatar && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("img", {
4723
+ src: user.avatar.startsWith('data:') ? user.avatar : `data:image/png;base64,${user.avatar}`,
4724
+ alt: "User Avatar",
4725
+ className: "w-6 h-6 rounded-full object-cover"
4726
+ }),
4727
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("span", {
4728
+ children: user.username
4729
+ }),
4730
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("svg", {
4731
+ className: `w-4 h-4 transition-transform ${showDropdown ? 'rotate-180' : ''}`,
4732
+ fill: "none",
4733
+ stroke: "currentColor",
4734
+ viewBox: "0 0 24 24",
4735
+ children: (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("path", {
4736
+ strokeLinecap: "round",
4737
+ strokeLinejoin: "round",
4738
+ strokeWidth: 2,
4739
+ d: "M19 9l-7 7-7-7"
4740
+ })
4741
+ })
4742
+ ]
4743
+ }),
4744
+ showDropdown && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
4745
+ className: "absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg z-50",
4746
+ children: (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("button", {
4747
+ onClick: handleSignOut,
4748
+ className: "w-full text-left px-4 py-2 text-sm text-text-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md",
4749
+ children: "Sign out"
4750
+ })
4751
+ })
4752
+ ]
4753
+ });
4754
+ }
4579
4755
  const DEFAULT_NAV_POSITION = 'right';
4580
4756
  function Nav(props) {
4581
4757
  const { beforeNavTitle, afterNavTitle, beforeNav, afterNavMenu, navTitle } = props;
@@ -4669,6 +4845,10 @@ function Nav(props) {
4669
4845
  }),
4670
4846
  hasSocialLinks && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(SocialLinks, {
4671
4847
  socialLinks: socialLinks
4848
+ }),
4849
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
4850
+ className: "ml-2",
4851
+ children: (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(UserAuth, {})
4672
4852
  })
4673
4853
  ]
4674
4854
  })
@@ -5558,9 +5738,9 @@ function SearchPanel({ focused, setFocused }) {
5558
5738
  });
5559
5739
  }
5560
5740
  };
5561
- const { siteData, page: { lang, version } } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.usePageData)();
5741
+ const { siteData, page: { lang, version, frontmatter, routePath } } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.usePageData)();
5562
5742
  const { searchPlaceholderText = 'Search Docs' } = useLocaleSiteData();
5563
- const { search, title: siteTitle } = siteData;
5743
+ const { search, title: siteTitle, base } = siteData;
5564
5744
  const versionedSearch = search && 'remote' !== search.mode && search.versioned;
5565
5745
  const DEFAULT_RESULT = [
5566
5746
  {
@@ -5572,6 +5752,100 @@ function SearchPanel({ focused, setFocused }) {
5572
5752
  const currentSuggestions = searchResult[resultTabIndex]?.result ?? [];
5573
5753
  const currentRenderType = searchResult[resultTabIndex]?.renderType ?? types_RenderType.Default;
5574
5754
  if (false === search) return null;
5755
+ // Extract related products from frontmatter for solution pages
5756
+ const getRelatedProductsFromFrontmatter = (frontmatter) => {
5757
+ if (!frontmatter?.features?.rows) return [];
5758
+
5759
+ const products = [];
5760
+
5761
+ // Loop through all rows and items in features
5762
+ frontmatter.features.rows.forEach(row => {
5763
+ if (row.items) {
5764
+ row.items.forEach(item => {
5765
+ if (item.link) {
5766
+ // Extract product name from link
5767
+ const linkPath = item.link
5768
+ .replace(/^\.\.\//, '') // Remove ../
5769
+ .replace(/^\.\//, ''); // Remove ./
5770
+
5771
+ const productName = linkPath.split('/')[0];
5772
+
5773
+ if (productName && !products.includes(productName)) {
5774
+ products.push(productName);
5775
+ }
5776
+ }
5777
+ });
5778
+ }
5779
+ });
5780
+
5781
+ return products;
5782
+ };
5783
+ // Calculate search scope
5784
+ const searchScope = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(() => {
5785
+ if (typeof window === 'undefined') return null;
5786
+
5787
+ const pathname = window.location.pathname;
5788
+ const pathParts = pathname.split('/').filter(Boolean);
5789
+
5790
+ // Get language settings from site config
5791
+ const localeLanguages = Object.values(siteData?.themeConfig?.locales || {});
5792
+ const langArr = localeLanguages.map((item) => item.lang) || [];
5793
+
5794
+ let startIndex = 0;
5795
+
5796
+ // Skip base path if present
5797
+ const baseTrimmed = (base || '').replace(/^\/|\/$/g, '');
5798
+ if (baseTrimmed && pathParts[startIndex] === baseTrimmed) {
5799
+ startIndex++;
5800
+ }
5801
+
5802
+ // Skip language prefix if present (e.g., /en/, /vn/, /zh/)
5803
+ if (langArr.includes(pathParts[startIndex])) {
5804
+ startIndex++;
5805
+ }
5806
+
5807
+ const firstSegment = pathParts[startIndex] || null;
5808
+ const secondSegment = pathParts[startIndex + 1] || null;
5809
+
5810
+ // Check if this is a solution page (contains 'solution-pages' in path)
5811
+ if (firstSegment === 'solution-pages') {
5812
+ const solutionFile = secondSegment || '';
5813
+ const solutionName = solutionFile.replace(/\.html$/, '');
5814
+
5815
+ // Get related products from frontmatter
5816
+ const relatedProducts = getRelatedProductsFromFrontmatter(frontmatter);
5817
+
5818
+ return {
5819
+ scopeType: 'solution',
5820
+ solutionName: solutionName,
5821
+ relatedProducts: relatedProducts,
5822
+ scopePath: null
5823
+ };
5824
+ }
5825
+
5826
+ // Product page: /m365/... or /d365/...
5827
+ const productName = firstSegment;
5828
+
5829
+ // Exclude common non-product paths
5830
+ const excludedPaths = ['index.html', 'index', 'what-is-coming-in-cloud', 'what-is-coming-in-cloud.html', 'solution-pages'];
5831
+
5832
+ if (productName && !excludedPaths.includes(productName)) {
5833
+
5834
+ return {
5835
+ scopeType: 'product',
5836
+ productName: productName,
5837
+ scopePath: `/${productName}`,
5838
+ relatedProducts: null
5839
+ };
5840
+ }
5841
+
5842
+ // No specific scope (homepage or other pages)
5843
+ return {
5844
+ scopeType: null,
5845
+ scopePath: null,
5846
+ relatedProducts: null
5847
+ };
5848
+ }, [focused, siteData, base, frontmatter]);
5575
5849
  const createSearcher = () => {
5576
5850
  if (pageSearcherRef.current) return pageSearcherRef.current;
5577
5851
  const pageSearcherConfig = {
@@ -5696,6 +5970,69 @@ function SearchPanel({ focused, setFocused }) {
5696
5970
  version,
5697
5971
  versionedSearch
5698
5972
  ]);
5973
+ const filterResultsByScope = (results, scope, basePath, currentLang) => {
5974
+ if (!scope || !scope.scopeType) {
5975
+ return results;
5976
+ }
5977
+
5978
+ return results.map(resultGroup => {
5979
+ const filteredResult = resultGroup.result.filter(item => {
5980
+ // Get the link path without domain
5981
+ const linkPath = removeDomain(item.link);
5982
+
5983
+ // Remove base path for comparison
5984
+ const baseTrimmed = (basePath || '').replace(/^\/|\/$/g, '');
5985
+ let cleanPath = linkPath;
5986
+ if (baseTrimmed) {
5987
+ cleanPath = cleanPath.replace(new RegExp(`^/?${baseTrimmed}/?`), '/');
5988
+ }
5989
+
5990
+ // Ensure leading slash
5991
+ if (!cleanPath.startsWith('/')) {
5992
+ cleanPath = `/${cleanPath}`;
5993
+ }
5994
+
5995
+ // Remove language prefix if present
5996
+ if (currentLang) {
5997
+ cleanPath = cleanPath.replace(new RegExp(`^/${currentLang}/`), '/');
5998
+ }
5999
+
6000
+ if (scope.scopeType === 'product') {
6001
+ // Product page: only show results from this product
6002
+ const matches = cleanPath.startsWith(scope.scopePath + '/') ||
6003
+ cleanPath === scope.scopePath ||
6004
+ cleanPath.startsWith(scope.scopePath + '.html');
6005
+ return matches;
6006
+ }
6007
+
6008
+ if (scope.scopeType === 'solution') {
6009
+ // Solution page: search across related products
6010
+ const relatedProducts = scope.relatedProducts || [];
6011
+
6012
+ if (relatedProducts.length === 0) {
6013
+ // No related products found in frontmatter, return all results
6014
+ return true;
6015
+ }
6016
+
6017
+ // Check if the result is in any of the related products
6018
+ const matches = relatedProducts.some(product => {
6019
+ return cleanPath.startsWith(`/${product}/`) ||
6020
+ cleanPath === `/${product}` ||
6021
+ cleanPath.startsWith(`/${product}.html`);
6022
+ });
6023
+
6024
+ return matches;
6025
+ }
6026
+
6027
+ return true;
6028
+ });
6029
+
6030
+ return {
6031
+ ...resultGroup,
6032
+ result: filteredResult
6033
+ };
6034
+ });
6035
+ };
5699
6036
  const handleQueryChangedImpl = async (value) => {
5700
6037
  let newQuery = value;
5701
6038
  setQuery(newQuery);
@@ -5723,7 +6060,13 @@ function SearchPanel({ focused, setFocused }) {
5723
6060
  }
5724
6061
  const currQuery = searchInputRef.current?.value;
5725
6062
  if (currQuery === newQuery) {
5726
- setSearchResult(searchResult || DEFAULT_RESULT);
6063
+ const filteredResult = filterResultsByScope(
6064
+ searchResult || DEFAULT_RESULT,
6065
+ searchScope,
6066
+ base,
6067
+ lang
6068
+ );
6069
+ setSearchResult(filteredResult);
5727
6070
  setIsSearching(false);
5728
6071
  }
5729
6072
  }
@@ -5817,6 +6160,21 @@ function SearchPanel({ focused, setFocused }) {
5817
6160
  })
5818
6161
  });
5819
6162
  };
6163
+ const getScopedPlaceholder = () => {
6164
+ if (!searchScope || !searchScope.scopeType) return searchPlaceholderText;
6165
+
6166
+ if (searchScope.scopeType === 'product') {
6167
+ return `Search in ${searchScope.productName.toUpperCase()}...`;
6168
+ }
6169
+ if (searchScope.scopeType === 'solution') {
6170
+ const formattedName = searchScope.solutionName
6171
+ .split('-')
6172
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
6173
+ .join(' ');
6174
+ return `Search in ${formattedName}...`;
6175
+ }
6176
+ return searchPlaceholderText;
6177
+ };
5820
6178
  return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(__WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.Fragment, {
5821
6179
  children: focused && (0, __WEBPACK_EXTERNAL_MODULE_react_dom_7136dc57__.createPortal)((0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
5822
6180
  className: Search_index_module.mask,
@@ -5844,7 +6202,7 @@ function SearchPanel({ focused, setFocused }) {
5844
6202
  (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("input", {
5845
6203
  className: `rspress-search-panel-input ${Search_index_module.input}`,
5846
6204
  ref: searchInputRef,
5847
- placeholder: searchPlaceholderText,
6205
+ placeholder: getScopedPlaceholder(),
5848
6206
  "aria-label": "SearchPanelInput",
5849
6207
  autoComplete: "off",
5850
6208
  autoFocus: true,
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.15",
4
+ "version": "0.7.17",
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.15",
25
- "@rspress-theme-anatole/shared": "0.7.15",
24
+ "@rspress-theme-anatole/rspress-plugin-mermaid": "0.7.17",
25
+ "@rspress-theme-anatole/shared": "0.7.17",
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",