@rspress-theme-anatole/theme-default 0.7.22 → 0.7.24

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 +456 -176
  2. package/package.json +4 -4
package/dist/bundle.js CHANGED
@@ -334,21 +334,227 @@ function useLocaleSiteData() {
334
334
  langRoutePrefix: lang === defaultLang ? '/' : (0, __WEBPACK_EXTERNAL_MODULE__rspress_shared_baa012d0__.addTrailingSlash)(lang)
335
335
  };
336
336
  }
337
+ function normalizeRoleList(value) {
338
+ if (!value) return [];
339
+ const list = Array.isArray(value) ? value : String(value).split(',');
340
+ return list.map((role) => String(role).trim().toLowerCase()).filter(Boolean);
341
+ }
342
+ // Cache for user roles fetched from userContext endpoint
343
+ let _cachedUserRoles = null;
344
+ let _userRolesFetchPromise = null;
345
+
346
+ function getUserRoles() {
347
+ // Return cached roles if available
348
+ if (_cachedUserRoles !== null) return _cachedUserRoles;
349
+ return [];
350
+ }
351
+
352
+ async function fetchUserRoles(userContextUrl) {
353
+ if (!userContextUrl) return [];
354
+ if (_cachedUserRoles !== null) return _cachedUserRoles;
355
+ if (_userRolesFetchPromise) return _userRolesFetchPromise;
356
+
357
+ _userRolesFetchPromise = (async () => {
358
+ try {
359
+ const response = await fetch(userContextUrl, { credentials: 'include' });
360
+ if (!response.ok) {
361
+ console.warn('Failed to fetch user context:', response.status);
362
+ _cachedUserRoles = [];
363
+ return [];
364
+ }
365
+ const data = await response.json();
366
+ const authMethods = ["anonymous", "internal-ad", "aos-sso", "azure-b2c"];
367
+ const userauthMethod = data?.authMethod || "";
368
+ const roles = userauthMethod === "anonymous"
369
+ ? ["anonymous"]
370
+ : authMethods.filter(m => m !== "anonymous");
371
+ _cachedUserRoles = normalizeRoleList(roles);
372
+ // Dispatch event to trigger sidebar re-render
373
+ if (typeof window !== 'undefined') {
374
+ window.dispatchEvent(new Event('UserRolesReady'));
375
+ }
376
+ return _cachedUserRoles;
377
+ } catch (e) {
378
+ console.warn('Error fetching user context:', e);
379
+ _cachedUserRoles = [];
380
+ return [];
381
+ } finally {
382
+ _userRolesFetchPromise = null;
383
+ }
384
+ })();
385
+
386
+ return _userRolesFetchPromise;
387
+ }
388
+ function isItemLocked(item, userRoles) {
389
+ const requiredRoles = normalizeRoleList(item?.roles);
390
+ // No roles required = public content (not locked)
391
+ if (!requiredRoles.length) return false;
392
+ // Has roles required but user has matching role = unlocked
393
+ if (userRoles.length && requiredRoles.some((role) => userRoles.includes(role))) return false;
394
+ // Has roles required but user doesn't have matching role = locked
395
+ return true;
396
+ }
397
+ function markLockedSidebarItems(items, userRoles) {
398
+ if (!Array.isArray(items)) return items;
399
+ return items.map((item) => {
400
+ if (!item) return null;
401
+ let nextItem = item;
402
+ const locked = isItemLocked(item, userRoles);
403
+ if (locked) {
404
+ nextItem = { ...nextItem, locked: true };
405
+ }
406
+ if ('items' in item && Array.isArray(item.items)) {
407
+ const markedItems = markLockedSidebarItems(item.items, userRoles);
408
+ nextItem = { ...nextItem, items: markedItems };
409
+ }
410
+ return nextItem;
411
+ }).filter(Boolean);
412
+ }
337
413
  function useSidebarData() {
338
414
  const { sidebar } = useLocaleSiteData();
415
+ const { siteData } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.usePageData)();
339
416
  const { pathname: rawPathname } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.useLocation)();
340
417
  const pathname = decodeURIComponent(rawPathname);
341
- const sidebarData = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(() => getSidebarDataGroup(sidebar, pathname), [
418
+ const [roleVersion, setRoleVersion] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(0);
419
+
420
+ // Fetch user roles from userContext endpoint
421
+ (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
422
+ const userContextUrl = siteData?.auth?.endpoints?.userContext;
423
+ if (userContextUrl) {
424
+ fetchUserRoles(userContextUrl);
425
+ }
426
+ }, [siteData?.auth?.endpoints?.userContext]);
427
+
428
+ (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
429
+ if ('undefined' == typeof window) return;
430
+ const handler = () => setRoleVersion((v) => v + 1);
431
+ window.addEventListener('MetaRolesReady', handler);
432
+ window.addEventListener('UserRolesReady', handler);
433
+ return () => {
434
+ window.removeEventListener('MetaRolesReady', handler);
435
+ window.removeEventListener('UserRolesReady', handler);
436
+ };
437
+ }, []);
438
+ const sidebarData = (0, __WEBPACK_EXTERNAL_MODULE_react__.useMemo)(() => {
439
+ const data = getSidebarDataGroup(sidebar, pathname);
440
+ const userRoles = getUserRoles();
441
+ let dataWithRoles = data;
442
+ try {
443
+ const roleMap = 'undefined' != typeof window ? window.__META_ROLE_MAP__ : null;
444
+ if (roleMap) {
445
+ const applyRoles = (items) => {
446
+ if (!Array.isArray(items)) return items;
447
+ return items.map((item) => {
448
+ if (!item) return item;
449
+ let nextItem = item;
450
+ const key = item._fileKey;
451
+ if (key && roleMap[key]) {
452
+ nextItem = {
453
+ ...nextItem,
454
+ roles: roleMap[key]
455
+ };
456
+ }
457
+ if ('items' in nextItem && Array.isArray(nextItem.items)) {
458
+ const nextItems = applyRoles(nextItem.items);
459
+ if (nextItems !== nextItem.items) {
460
+ nextItem = {
461
+ ...nextItem,
462
+ items: nextItems
463
+ };
464
+ }
465
+ }
466
+ return nextItem;
467
+ });
468
+ };
469
+ dataWithRoles = applyRoles(data);
470
+ }
471
+ } catch (e) {
472
+ }
473
+ return markLockedSidebarItems(dataWithRoles, userRoles);
474
+ }, [
342
475
  sidebar,
343
- pathname
476
+ pathname,
477
+ roleVersion
344
478
  ]);
345
479
  return sidebarData;
346
480
  }
481
+
482
+ // Get first accessible (not locked) page from sidebar items
483
+ function getFirstAccessiblePage(items) {
484
+ if (!Array.isArray(items)) return null;
485
+
486
+ for (const item of items) {
487
+ if (!item) continue;
488
+ if ('dividerType' in item) continue;
489
+
490
+ // If item is locked, skip it
491
+ if (item.locked === true) continue;
492
+
493
+ // If item has a link and is not locked, return it
494
+ if ('link' in item && item.link) {
495
+ return { text: item.text, link: item.link };
496
+ }
497
+
498
+ // If item has children, search recursively
499
+ if ('items' in item && Array.isArray(item.items)) {
500
+ const found = getFirstAccessiblePage(item.items);
501
+ if (found) return found;
502
+ }
503
+ }
504
+
505
+ return null;
506
+ }
507
+
508
+ // Hook to redirect to first accessible page if current page is locked
509
+ function useRedirectToAccessiblePage() {
510
+ const { pathname } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.useLocation)();
511
+ const navigate = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.useNavigate)();
512
+ const items = useSidebarData();
513
+
514
+ (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
515
+ if (!items || items.length === 0) return;
516
+
517
+ // Find current page in sidebar
518
+ const findCurrentPage = (items, targetPath) => {
519
+ for (const item of items) {
520
+ if (!item) continue;
521
+ if ('dividerType' in item) continue;
522
+
523
+ if ('link' in item && item.link) {
524
+ const itemPath = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.withBase)(item.link);
525
+ if ((0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.isEqualPath)(itemPath, targetPath)) {
526
+ return item;
527
+ }
528
+ }
529
+
530
+ if ('items' in item && Array.isArray(item.items)) {
531
+ const found = findCurrentPage(item.items, targetPath);
532
+ if (found) return found;
533
+ }
534
+ }
535
+ return null;
536
+ };
537
+
538
+ const currentPage = findCurrentPage(items, pathname);
539
+
540
+ // If current page is locked, redirect to first accessible page
541
+ if (currentPage && currentPage.locked === true) {
542
+ const firstAccessible = getFirstAccessiblePage(items);
543
+ if (firstAccessible && firstAccessible.link) {
544
+ navigate((0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.withBase)(firstAccessible.link), { replace: true });
545
+ }
546
+ }
547
+ }, [items, pathname, navigate]);
548
+ }
549
+
347
550
  function usePrevNextPage() {
348
551
  const { pathname } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.useLocation)();
349
552
  const items = useSidebarData();
350
553
  const flattenTitles = [];
351
554
  const walk = (sidebarItem) => {
555
+ // Skip locked items
556
+ if (sidebarItem.locked === true) return;
557
+
352
558
  if ('items' in sidebarItem) {
353
559
  if (sidebarItem.link) flattenTitles.push({
354
560
  text: sidebarItem.text,
@@ -553,9 +759,29 @@ function SidebarGroup(props) {
553
759
  const initialState = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)('collapsed' in item && item.collapsed);
554
760
  const active = item.link && activeMatcher(item.link);
555
761
  const { collapsed, collapsible = true } = item;
762
+ const isLocked = item.locked === true;
763
+
764
+ // Lock icon SVG for groups
765
+ const GroupLockIcon = () => (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("svg", {
766
+ xmlns: "http://www.w3.org/2000/svg",
767
+ width: "14",
768
+ height: "14",
769
+ viewBox: "0 0 24 24",
770
+ fill: "none",
771
+ stroke: "currentColor",
772
+ strokeWidth: "2",
773
+ strokeLinecap: "round",
774
+ strokeLinejoin: "round",
775
+ style: { marginLeft: '6px', opacity: 0.6, flexShrink: 0 },
776
+ children: [
777
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
778
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
779
+ ]
780
+ });
781
+
556
782
  const collapsibleIcon = (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
557
783
  style: {
558
- cursor: 'pointer',
784
+ cursor: isLocked ? 'not-allowed' : 'pointer',
559
785
  transition: 'transform 0.2s ease-out',
560
786
  transform: collapsed ? 'rotate(0deg)' : 'rotate(90deg)'
561
787
  },
@@ -593,6 +819,7 @@ function SidebarGroup(props) {
593
819
  }, []);
594
820
  const toggleCollapse = (e) => {
595
821
  e.stopPropagation();
822
+ if (isLocked) return; // Prevent collapse toggle for locked items
596
823
  setSidebarData((sidebarData) => {
597
824
  const newSidebarData = [
598
825
  ...sidebarData
@@ -610,23 +837,29 @@ function SidebarGroup(props) {
610
837
  className: "rspress-sidebar-section mt-0.5 block",
611
838
  "data-context": item.context,
612
839
  style: {
613
- marginLeft: 0 === depth ? 0 : '18px'
840
+ marginLeft: 0 === depth ? 0 : '18px',
841
+ opacity: isLocked ? 0.6 : 1
614
842
  },
615
843
  children: [
616
844
  (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsxs)("div", {
617
845
  className: `rspress-sidebar-collapse flex items-center ${active ? Sidebar_index_module.menuItemActive : Sidebar_index_module.menuItem}`,
618
846
  "data-context": item.context,
619
- onMouseEnter: () => item.link && preloadLink(item.link),
847
+ onMouseEnter: () => !isLocked && item.link && preloadLink(item.link),
620
848
  onClick: (e) => {
849
+ if (isLocked) {
850
+ e.preventDefault();
851
+ e.stopPropagation();
852
+ return;
853
+ }
621
854
  if (item.link) navigate((0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.withBase)((0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.normalizeHrefInRuntime)(item.link)));
622
855
  collapsible && toggleCollapse(e);
623
856
  },
624
857
  style: {
625
858
  // borderRadius: 0 === depth ? '0 var(--rp-radius) var(--rp-radius) 0' : void 0,
626
- cursor: collapsible || item.link ? 'pointer' : 'normal'
859
+ cursor: isLocked ? 'not-allowed' : (collapsible || item.link ? 'pointer' : 'normal')
627
860
  },
628
861
  children: [
629
- collapsible && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
862
+ collapsible && !isLocked && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
630
863
  className: `${Sidebar_index_module.collapseContainer} p-2 rounded-xl`,
631
864
  style: {
632
865
  paddingRight: 0
@@ -635,7 +868,7 @@ function SidebarGroup(props) {
635
868
  children: collapsibleIcon
636
869
  }),
637
870
  (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsxs)("h2", {
638
- className: "py-2 px-2 text-sm font-medium flex",
871
+ className: "py-2 px-2 text-sm font-medium flex items-center",
639
872
  style: {
640
873
  ...highlightTitleStyle
641
874
  },
@@ -651,12 +884,13 @@ function SidebarGroup(props) {
651
884
  letterSpacing: '-1%'
652
885
  },
653
886
  children: renderInlineMarkdown(item.text)
654
- })
887
+ }),
888
+ isLocked && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(GroupLockIcon, {})
655
889
  ]
656
890
  })
657
891
  ]
658
892
  }),
659
- (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
893
+ !isLocked && (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
660
894
  ref: containerRef,
661
895
  className: "transition-all duration-300 ease-in-out",
662
896
  style: {
@@ -692,6 +926,7 @@ function SidebarItem(props) {
692
926
  const { item, depth = 0, activeMatcher, id, setSidebarData } = props;
693
927
  const active = 'link' in item && item.link && activeMatcher(item.link);
694
928
  const ref = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
929
+ const isLocked = item.locked === true;
695
930
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
696
931
  if (active) ref.current?.scrollIntoView({
697
932
  block: 'center'
@@ -705,6 +940,65 @@ function SidebarItem(props) {
705
940
  collapsed: item.collapsed,
706
941
  setSidebarData: setSidebarData
707
942
  }, `${item.text}-${id}`);
943
+
944
+ // Lock icon SVG
945
+ const LockIcon = () => (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("svg", {
946
+ xmlns: "http://www.w3.org/2000/svg",
947
+ width: "14",
948
+ height: "14",
949
+ viewBox: "0 0 24 24",
950
+ fill: "none",
951
+ stroke: "currentColor",
952
+ strokeWidth: "2",
953
+ strokeLinecap: "round",
954
+ strokeLinejoin: "round",
955
+ style: { marginLeft: '6px', opacity: 0.6, flexShrink: 0 },
956
+ children: [
957
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
958
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
959
+ ]
960
+ });
961
+
962
+ if (isLocked) {
963
+ // Locked item - show lock icon and prevent navigation
964
+ return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
965
+ className: Sidebar_index_module.menuLink,
966
+ onClick: (e) => {
967
+ e.preventDefault();
968
+ e.stopPropagation();
969
+ },
970
+ style: { cursor: 'not-allowed' },
971
+ children: (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsxs)("div", {
972
+ ref: ref,
973
+ className: `${Sidebar_index_module.menuItem} mt-0.5 py-2 px-3 font-medium flex items-center justify-between`,
974
+ style: {
975
+ fontSize: 0 === depth ? '14px' : '13px',
976
+ marginLeft: 0 === depth ? 0 : '18px',
977
+ opacity: 0.6,
978
+ ...0 === depth ? highlightTitleStyle : {}
979
+ },
980
+ children: [
981
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsxs)("span", {
982
+ className: "flex items-center",
983
+ children: [
984
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(__WEBPACK_EXTERNAL_MODULE__theme_75e53063__.Tag, {
985
+ tag: item.tag
986
+ }),
987
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("span", {
988
+ style: {
989
+ fontSize: 0 === depth ? '14px' : '13px',
990
+ fontWeight: 400
991
+ },
992
+ children: renderInlineMarkdown(item.text)
993
+ })
994
+ ]
995
+ }),
996
+ (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(LockIcon, {})
997
+ ]
998
+ })
999
+ });
1000
+ }
1001
+
708
1002
  return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)(__WEBPACK_EXTERNAL_MODULE__theme_75e53063__.Link, {
709
1003
  href: (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.normalizeHrefInRuntime)(item.link),
710
1004
  className: Sidebar_index_module.menuLink,
@@ -1659,6 +1953,7 @@ const Layout = (props) => {
1659
1953
  const { pageType, lang: currentLang, title: articleTitle, frontmatter = {} } = page;
1660
1954
  const localesData = useLocaleSiteData();
1661
1955
  useRedirect4FirstVisit();
1956
+ useRedirectToAccessiblePage();
1662
1957
  let title = frontmatter.title ?? articleTitle;
1663
1958
  const mainTitle = siteData.title || localesData.title || '';
1664
1959
  title = title &&
@@ -4581,11 +4876,6 @@ function NavVersions() {
4581
4876
  });
4582
4877
  }
4583
4878
  // Utility functions for cookie management
4584
- function setCookie(name, value, days = 7) {
4585
- const expires = new Date();
4586
- expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
4587
- document.cookie = `${name}=${encodeURIComponent(JSON.stringify(value))};expires=${expires.toUTCString()};path=/`;
4588
- }
4589
4879
  function getCookie(name) {
4590
4880
  const nameEQ = name + "=";
4591
4881
  const ca = document.cookie.split(';');
@@ -4605,9 +4895,6 @@ function getCookie(name) {
4605
4895
  }
4606
4896
  return null;
4607
4897
  }
4608
- function deleteCookie(name) {
4609
- document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/`;
4610
- }
4611
4898
  // Decode Base64 string (supports Unicode)
4612
4899
  function decodeBase64(base64String) {
4613
4900
  try {
@@ -4636,6 +4923,87 @@ function decodeBase64(base64String) {
4636
4923
  return null;
4637
4924
  }
4638
4925
  }
4926
+ // Generate initials from username (e.g., "Jake Thai" -> "JT")
4927
+ function getInitialsFromName(name) {
4928
+ if (!name) return '?';
4929
+ const parts = name.trim().split(/\s+/);
4930
+ if (parts.length === 1) {
4931
+ return parts[0].charAt(0).toUpperCase();
4932
+ }
4933
+ return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
4934
+ }
4935
+
4936
+ // Generate a consistent color based on the username
4937
+ function getAvatarColor(name) {
4938
+ // Microsoft Fluent UI persona colors
4939
+ const colors = [
4940
+ '#0078D4', // Microsoft Blue
4941
+ '#107C10', // Green
4942
+ '#5C2D91', // Purple
4943
+ '#B4009E', // Magenta
4944
+ '#D83B01', // Orange
4945
+ '#E74856', // Red
4946
+ '#00B7C3', // Teal
4947
+ '#0099BC', // Cyan
4948
+ '#7A7574', // Gray
4949
+ '#69797E', // Dark Gray
4950
+ '#498205', // Lime Green
4951
+ '#847545', // Gold
4952
+ '#C239B3', // Pink
4953
+ '#038387', // Dark Teal
4954
+ '#8764B8', // Light Purple
4955
+ '#CA5010', // Burnt Orange
4956
+ ];
4957
+ // Generate a hash from the name for consistent color
4958
+ let hash = 0;
4959
+ const str = name || 'User';
4960
+ for (let i = 0; i < str.length; i++) {
4961
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
4962
+ }
4963
+ return colors[Math.abs(hash) % colors.length];
4964
+ }
4965
+
4966
+ // Generate avatar as base64 PNG image
4967
+ function generateAvatarBase64(username, size = 64) {
4968
+ if (typeof document === 'undefined') return null;
4969
+
4970
+ const initials = getInitialsFromName(username);
4971
+ const bgColor = getAvatarColor(username);
4972
+
4973
+ // Use higher resolution for crisp rendering
4974
+ const scale = 2;
4975
+ const actualSize = size * scale;
4976
+
4977
+ // Create canvas
4978
+ const canvas = document.createElement('canvas');
4979
+ canvas.width = actualSize;
4980
+ canvas.height = actualSize;
4981
+ const ctx = canvas.getContext('2d');
4982
+
4983
+ // Enable anti-aliasing
4984
+ ctx.imageSmoothingEnabled = true;
4985
+ ctx.imageSmoothingQuality = 'high';
4986
+
4987
+ // Draw circle background
4988
+ ctx.beginPath();
4989
+ ctx.arc(actualSize / 2, actualSize / 2, actualSize / 2, 0, Math.PI * 2);
4990
+ ctx.fillStyle = bgColor;
4991
+ ctx.fill();
4992
+
4993
+ // Draw initials text
4994
+ ctx.fillStyle = '#FFFFFF';
4995
+
4996
+ const fontSize = actualSize * 0.42;
4997
+ ctx.font = `400 ${fontSize}px "Segoe UI", "Segoe UI Web", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif`;
4998
+ ctx.textAlign = 'center';
4999
+ ctx.textBaseline = 'middle';
5000
+
5001
+ // Slight vertical adjustment for better visual centering
5002
+ ctx.fillText(initials, actualSize / 2, actualSize / 2 + (fontSize * 0.04));
5003
+
5004
+ // Return as base64
5005
+ return canvas.toDataURL('image/png');
5006
+ }
4639
5007
  // Parse KBGateway-User-Context cookie (Base64 encoded)
4640
5008
  function parseKBGatewayUserContext(cookieValue) {
4641
5009
  if (!cookieValue) return null;
@@ -4661,7 +5029,7 @@ function parseKBGatewayUserContext(cookieValue) {
4661
5029
  return {
4662
5030
  username: username,
4663
5031
  email: parsed.email || null,
4664
- avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAYCAYAAAAPtVbGAAAAAXNSR0IArs4c6QAAAsJJREFUSEullU1IFHEYxp9ndpc+KHLzAyyDIIjO+TEzqWDtbFYXK/Bi0Cm61Ck6iEFsFEhFtyKiY2SBh0AIyZ1VQ2P/q3hMKIgOkUJahgRG6+4bo7kfzs7OSnPb+b/v8/s/78csscVHLP0T7dShUmkSNa5BoCMTvMexyZmNGG6FIRE9CnIElE7GUyMbuXLSPIKsvAegFeh9pa0anN9bg0SNHxCEQSwhrqoJiDgalpH1uOwAbXWhYohEmpvBwFReTEzaKSXRliaINu1VEdqKlUPWb1sYL7SVJifMFmiS+m+IWGYPIM9dQtQuomr/AJa+rHpAhmirroqciNtFTtMph0T04yBHi0H8iXBDDQcHM74QscxeQPo9p1DYx0Sy/98AvATQCOABbfW44hEWy3BKESgz6hnaKlhuFco6Ecu4A+CG7y4J7jKhej2b73UgjY0hhEMrPi420jOYW97J2dk/pfQ8nUjUvAWRm74ucoXnbcaTJeNLQny22JtrqwAB1/aXhkTMh6BcqdhF3s0jxpNXN+e5INLdHSixXI65Z9DQj2PJj3jXehTI9EFwznWR8IGgsxuF790Qy3gN4ExB0AraTu1iLOYqg7S27saOzHcAofx2YphxVZhf/BWWGDRMru1FHh6SPRxOLXtOYYdxEEF8LjgXtKkgY/neFDkRS58G2JRLELxiQp33641YxgcAhwvyZphQOZ0cRE631yKd/lYkGEjv45uZeX+I3gZwoiguFKrj8MSC8y4PsYwxAB1FgZKtYWLKqXnZRzrb65FJz20KektbremtQSTSUg1qiy4lTZo4ksr9V3v2JWpcguCp63x1ey3HxxfXIZaRBGC4gsgFxpN1vk4sw/mc5Ccsn6BoK5NytqMKv34veQoRXYyrIU8XEf0yyCee+duCeymWPgewvuxts+zhaPLF5hixzOuA3Pfp2PxfA8n5ENhQazEAAAAASUVORK5CYII="
5032
+ avatar: generateAvatarBase64(username)
4665
5033
  };
4666
5034
  } catch (e) {
4667
5035
  console.warn('Failed to parse KBGateway-User-Context:', e);
@@ -4678,7 +5046,7 @@ function parseKBGatewayUserContext(cookieValue) {
4678
5046
  return {
4679
5047
  username: username,
4680
5048
  email: parsed.email || null,
4681
- avatar: parsed.Avatar || parsed.avatar || null
5049
+ avatar: generateAvatarBase64(username)
4682
5050
  };
4683
5051
  } catch (e2) {
4684
5052
  console.warn('Failed to parse as JSON:', e2);
@@ -4688,54 +5056,12 @@ function parseKBGatewayUserContext(cookieValue) {
4688
5056
  }
4689
5057
  // LocalStorage key for user data
4690
5058
  const USER_STORAGE_KEY = 'kb_user_data';
4691
- // Get user data from localStorage
4692
- function getUserFromStorage() {
4693
- if (typeof localStorage === 'undefined') return null;
4694
-
4695
- try {
4696
- const storedData = localStorage.getItem(USER_STORAGE_KEY);
4697
- if (storedData) {
4698
- return JSON.parse(storedData);
4699
- }
4700
- } catch (e) {
4701
- console.warn('Failed to get user from localStorage:', e);
4702
- localStorage.removeItem(USER_STORAGE_KEY);
4703
- }
4704
- return null;
4705
- }
4706
- // Save user data to localStorage
4707
- function saveUserToStorage(userData) {
4708
- if (typeof localStorage === 'undefined') return;
4709
-
4710
- try {
4711
- if (userData) {
4712
- localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(userData));
4713
- } else {
4714
- localStorage.removeItem(USER_STORAGE_KEY);
4715
- }
4716
- } catch (e) {
4717
- console.warn('Failed to save user to localStorage:', e);
4718
- }
4719
- }
4720
- // Remove user data from localStorage
4721
- function removeUserFromStorage() {
4722
- if (typeof localStorage === 'undefined') return;
4723
- localStorage.removeItem(USER_STORAGE_KEY);
4724
- }
4725
- // Sync cookie to localStorage and return user data
4726
- function syncCookieToStorage(cookieName) {
4727
- const cookieValue = getCookie(cookieName);
4728
- const userData = parseKBGatewayUserContext(cookieValue);
4729
-
4730
- if (userData) {
4731
- saveUserToStorage(userData);
4732
- } else {
4733
- removeUserFromStorage();
4734
- }
4735
-
4736
- return userData;
4737
- }
5059
+
4738
5060
  // User Authentication Component
5061
+ // Cache user data globally to prevent flicker between page navigations
5062
+ let cachedUserData = null;
5063
+ let isClientInitialized = false;
5064
+
4739
5065
  function UserAuth() {
4740
5066
  const { siteData } = (0, __WEBPACK_EXTERNAL_MODULE__rspress_runtime_0abd3046__.usePageData)();
4741
5067
  const dropdownRef = (0, __WEBPACK_EXTERNAL_MODULE_react__.useRef)(null);
@@ -4743,52 +5069,54 @@ function UserAuth() {
4743
5069
  const authEnabled = siteData?.auth?.enabled !== false;
4744
5070
  const showSignInButton = siteData?.auth?.showSignInButton !== false;
4745
5071
 
4746
- // Get sign in URL from site config
5072
+ // Get sign in /sign out URL from site config
4747
5073
  const signInUrl = siteData?.auth?.endpoints?.signIn;
5074
+ const signOutUrl = siteData?.auth?.endpoints?.signOut;
4748
5075
 
4749
5076
  // Cookie name for KBGateway
4750
5077
  const KB_COOKIE_NAME = 'KBGateway-User-Context';
4751
5078
 
4752
- const [user, setUser] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(() => {
5079
+ // Helper function to get user from cookie directly
5080
+ const getUserFromCookie = () => {
4753
5081
  if (typeof document === 'undefined') return null;
4754
-
4755
- // 1. First try to get from localStorage (instant, no flicker)
4756
- const storedUser = getUserFromStorage();
4757
- if (storedUser) {
4758
- return storedUser;
5082
+ const cookieValue = getCookie(KB_COOKIE_NAME);
5083
+ return parseKBGatewayUserContext(cookieValue);
5084
+ };
5085
+
5086
+ // Initialize from cache first, then cookie - prevents flicker on navigation
5087
+ const [user, setUser] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(() => {
5088
+ // Use cached data first for instant render
5089
+ if (cachedUserData) return cachedUserData;
5090
+ // Fall back to cookie (only works on client)
5091
+ if (typeof document !== 'undefined') {
5092
+ const cookieUser = getUserFromCookie();
5093
+ if (cookieUser) {
5094
+ cachedUserData = cookieUser;
5095
+ isClientInitialized = true;
5096
+ }
5097
+ return cookieUser;
4759
5098
  }
4760
-
4761
- // 2. If not in localStorage, check cookie and sync
4762
- return syncCookieToStorage(KB_COOKIE_NAME);
5099
+ return null;
4763
5100
  });
4764
5101
 
5102
+ // Track if component has mounted (for SSR hydration)
5103
+ const [isMounted, setIsMounted] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(isClientInitialized);
5104
+
4765
5105
  const [showDropdown, setShowDropdown] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(false);
4766
5106
 
4767
- // Sync cookie to localStorage on mount and periodically check for changes
5107
+ // Check cookie on mount and update cache
4768
5108
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
4769
- // Sync cookie to localStorage on mount
4770
- const userData = syncCookieToStorage(KB_COOKIE_NAME);
5109
+ const userData = getUserFromCookie();
4771
5110
  if (userData) {
5111
+ cachedUserData = userData;
4772
5112
  setUser(userData);
5113
+ } else if (isClientInitialized) {
5114
+ // Cookie was removed (signed out) - only clear if we were initialized
5115
+ cachedUserData = null;
5116
+ setUser(null);
4773
5117
  }
4774
-
4775
- // Listen for storage events to sync across tabs
4776
- const handleStorageChange = (e) => {
4777
- if (e.key === USER_STORAGE_KEY) {
4778
- if (e.newValue) {
4779
- try {
4780
- setUser(JSON.parse(e.newValue));
4781
- } catch (err) {
4782
- setUser(null);
4783
- }
4784
- } else {
4785
- setUser(null);
4786
- }
4787
- }
4788
- };
4789
-
4790
- window.addEventListener('storage', handleStorageChange);
4791
- return () => window.removeEventListener('storage', handleStorageChange);
5118
+ isClientInitialized = true;
5119
+ setIsMounted(true);
4792
5120
  }, []);
4793
5121
 
4794
5122
  (0, __WEBPACK_EXTERNAL_MODULE_react__.useEffect)(() => {
@@ -4803,96 +5131,48 @@ function UserAuth() {
4803
5131
  return () => document.removeEventListener('mousedown', handleClickOutside);
4804
5132
  }, []);
4805
5133
 
4806
- const handleSignIn = async () => {
5134
+ const handleSignIn = () => {
4807
5135
  if (!signInUrl) {
4808
5136
  console.warn('Sign in URL not configured');
4809
5137
  return;
4810
5138
  }
4811
5139
 
4812
- try {
4813
- // Center the popup window
4814
- const width = 600;
4815
- const height = 600;
4816
- const left = window.top.outerWidth / 2 + window.top.screenX - width / 2;
4817
- const top = window.top.outerHeight / 2 + window.top.screenY - height / 2;
4818
-
4819
- // Open sign in URL in new window/tab
4820
- const authWindow = window.open(signInUrl, '_blank', `width=${width},height=${height},left=${left},top=${top}`);
4821
-
4822
- // Listen for messages from auth window
4823
- const handleMessage = (event) => {
4824
- if (event.origin !== new URL(signInUrl).origin) return;
4825
-
4826
- if (event.data && event.data.type === 'AUTH_SUCCESS') {
4827
- const userData = {
4828
- username: event.data.username,
4829
- email: event.data.email,
4830
- avatar: event.data.avatar
4831
- };
4832
-
4833
- // Save to localStorage
4834
- saveUserToStorage(userData);
4835
- setUser(userData);
4836
- authWindow.close();
4837
-
4838
- // Remove event listener
4839
- window.removeEventListener('message', handleMessage);
4840
- }
4841
- };
4842
-
4843
- window.addEventListener('message', handleMessage);
4844
-
4845
- // Poll for KBGateway-User-Context cookie
4846
- const pollInterval = setInterval(() => {
4847
- // Sync cookie to localStorage and get user data
4848
- const userData = syncCookieToStorage(KB_COOKIE_NAME);
4849
-
4850
- if (userData) {
4851
- setUser(userData);
4852
- clearInterval(pollInterval);
4853
- window.removeEventListener('message', handleMessage);
4854
-
4855
- // Close popup if still open
4856
- if (authWindow && !authWindow.closed) {
4857
- authWindow.close();
4858
- }
4859
- }
4860
-
4861
- // Also check if popup was closed manually
4862
- if (authWindow && authWindow.closed) {
4863
- clearInterval(pollInterval);
4864
- window.removeEventListener('message', handleMessage);
4865
-
4866
- // Final sync cookie to localStorage
4867
- const finalUserData = syncCookieToStorage(KB_COOKIE_NAME);
4868
- if (finalUserData) {
4869
- setUser(finalUserData);
4870
- }
4871
- }
4872
- }, 500); // Check every 500ms
4873
-
4874
- // Timeout after 5 minutes
4875
- setTimeout(() => {
4876
- clearInterval(pollInterval);
4877
- window.removeEventListener('message', handleMessage);
4878
- }, 300000);
4879
-
4880
-
4881
- } catch (error) {
4882
- console.error('Sign in error:', error);
4883
- }
5140
+ // Redirect directly to sign in URL
5141
+ window.location.href = signInUrl;
4884
5142
  };
4885
5143
 
4886
5144
  const handleSignOut = () => {
4887
- deleteCookie(KB_COOKIE_NAME);
4888
- removeUserFromStorage();
4889
- setUser(null);
4890
- setShowDropdown(false);
5145
+ if (!signOutUrl) {
5146
+ console.warn('Sign out URL not configured');
5147
+ return;
5148
+ }
5149
+ fetch(signOutUrl, {
5150
+ method: 'POST',
5151
+ credentials: 'include',
5152
+ })
5153
+ .then(() => {
5154
+ cachedUserData = null; // Clear cache on sign out
5155
+ setUser(null);
5156
+ setShowDropdown(false);
5157
+
5158
+ document.location = '/'; // Redirect to home after sign out
5159
+ })
5160
+ .catch((error) => {
5161
+ console.error('Error signing out:', error);
5162
+ });
4891
5163
  };
4892
5164
 
4893
5165
  if (!authEnabled) {
4894
5166
  return null;
4895
5167
  }
5168
+
5169
+ // Don't render anything until client is mounted to prevent hydration mismatch
5170
+ if (!isMounted) {
5171
+ // Return a placeholder with same dimensions to prevent layout shift
5172
+ return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
5173
+ style: { width: '28px', height: '28px' }
5174
+ });
5175
+ }
4896
5176
 
4897
5177
  if (!user) {
4898
5178
  if (!showSignInButton) {
@@ -4909,7 +5189,7 @@ function UserAuth() {
4909
5189
  });
4910
5190
  }
4911
5191
 
4912
- const userAvatar = user.avatar ?? "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAYCAYAAAAPtVbGAAAAAXNSR0IArs4c6QAAAsJJREFUSEullU1IFHEYxp9ndpc+KHLzAyyDIIjO+TEzqWDtbFYXK/Bi0Cm61Ck6iEFsFEhFtyKiY2SBh0AIyZ1VQ2P/q3hMKIgOkUJahgRG6+4bo7kfzs7OSnPb+b/v8/s/78csscVHLP0T7dShUmkSNa5BoCMTvMexyZmNGG6FIRE9CnIElE7GUyMbuXLSPIKsvAegFeh9pa0anN9bg0SNHxCEQSwhrqoJiDgalpH1uOwAbXWhYohEmpvBwFReTEzaKSXRliaINu1VEdqKlUPWb1sYL7SVJifMFmiS+m+IWGYPIM9dQtQuomr/AJa+rHpAhmirroqciNtFTtMph0T04yBHi0H8iXBDDQcHM74QscxeQPo9p1DYx0Sy/98AvATQCOABbfW44hEWy3BKESgz6hnaKlhuFco6Ecu4A+CG7y4J7jKhej2b73UgjY0hhEMrPi420jOYW97J2dk/pfQ8nUjUvAWRm74ucoXnbcaTJeNLQny22JtrqwAB1/aXhkTMh6BcqdhF3s0jxpNXN+e5INLdHSixXI65Z9DQj2PJj3jXehTI9EFwznWR8IGgsxuF790Qy3gN4ExB0AraTu1iLOYqg7S27saOzHcAofx2YphxVZhf/BWWGDRMru1FHh6SPRxOLXtOYYdxEEF8LjgXtKkgY/neFDkRS58G2JRLELxiQp33641YxgcAhwvyZphQOZ0cRE631yKd/lYkGEjv45uZeX+I3gZwoiguFKrj8MSC8y4PsYwxAB1FgZKtYWLKqXnZRzrb65FJz20KektbremtQSTSUg1qiy4lTZo4ksr9V3v2JWpcguCp63x1ey3HxxfXIZaRBGC4gsgFxpN1vk4sw/mc5Ccsn6BoK5NytqMKv34veQoRXYyrIU8XEf0yyCee+duCeymWPgewvuxts+zhaPLF5hixzOuA3Pfp2PxfA8n5ENhQazEAAAAASUVORK5CYII=";
5192
+ const userAvatar = user.avatar;
4913
5193
 
4914
5194
  return (0, __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime_225474f2__.jsx)("div", {
4915
5195
  className: "relative",
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.22",
4
+ "version": "0.7.24",
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.22",
25
- "@rspress-theme-anatole/shared": "0.7.22",
24
+ "@rspress-theme-anatole/rspress-plugin-mermaid": "0.7.24",
25
+ "@rspress-theme-anatole/shared": "0.7.24",
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",