@parhelia/core 0.1.12601 → 0.1.12612

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.
@@ -21,6 +21,7 @@ import ConfirmationDialog from "../ConfirmationDialog";
21
21
  import { getItemDescriptor } from "../utils";
22
22
  import { EditContextMenu } from "../ContextMenu";
23
23
  import { InlineAiTrigger } from "../ai/InlineAiTrigger";
24
+ import { EditorFormHintPopover } from "../page-viewer/EditorFormHintPopover";
24
25
  import { FieldEditorPopup } from "../FieldEditorPopup";
25
26
  import { ConcurrentUserLimitDialog } from "../ConcurrentUserLimitDialog";
26
27
  import { post } from "../services/serviceHelper";
@@ -54,6 +55,11 @@ import { useMediaSelector } from "./hooks/useMediaSelector";
54
55
  import { useGlobalEditorKeyDown } from "./hooks/useGlobalEditorKeyDown";
55
56
  import { useStartupChecks } from "../settings/status/index";
56
57
  import { FeatureGate, LicenseFeatures, LicenseProvider, LicenseOverlay, } from "../../licensing";
58
+ // Sentinel written to the `sidebar` URL param when the user has explicitly closed
59
+ // every sidebar. Distinguishes "no preference yet" (param absent) from "user wants
60
+ // nothing open" (param present with this value) so reload can honor the user's intent.
61
+ const SIDEBAR_NONE_SENTINEL = "none";
62
+ const sidebarUrlValue = (ids) => ids.length ? ids.join(",") : SIDEBAR_NONE_SENTINEL;
57
63
  function AgentsSlotContextBridge({ slot }) {
58
64
  const editContext = useEditContext();
59
65
  const slotContext = useEditorSlotContext({
@@ -178,8 +184,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
178
184
  const [openSidebars, setOpenSidebars] = useState(() => {
179
185
  const sidebarParam = searchParams.get("sidebar");
180
186
  let sidebars = [];
181
- if (sidebarParam) {
182
- sidebars = sidebarParam.split(",").filter(Boolean);
187
+ // Presence check (not truthiness) so the sentinel "none" is respected across reloads.
188
+ if (sidebarParam !== null) {
189
+ sidebars =
190
+ sidebarParam === SIDEBAR_NONE_SENTINEL
191
+ ? []
192
+ : sidebarParam.split(",").filter(Boolean);
183
193
  }
184
194
  else {
185
195
  sidebars = [...(configuration.editor.defaultOpenSidebars ?? [])];
@@ -504,6 +514,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
504
514
  });
505
515
  }, [editorSlots, showComponentNavigatorDefault]);
506
516
  const [showAgentsPanel, setShowAgentsPanel] = useState(userPreferences.showAgentsPanel ?? false);
517
+ const [editorFormHidden, setEditorFormHiddenState] = useState(userPreferences.editorFormHidden ?? false);
518
+ const [editorFormHintSeen, setEditorFormHintSeenState] = useState(userPreferences.editorFormHintSeen ?? false);
519
+ const [editorFormHintVisible, setEditorFormHintVisible] = useState(false);
520
+ const editorFormToggleButtonRef = useRef(null);
507
521
  const [showMinimap, setShowMinimap] = useState(userPreferences.showMinimap ?? true);
508
522
  const [showHelpTerminal, setShowHelpTerminal] = useState(false);
509
523
  const [helpTerminalInitialPrompt, setHelpTerminalInitialPrompt] = useState(undefined);
@@ -978,6 +992,26 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
978
992
  setShowMinimap(newValue);
979
993
  setUserPreferences({ showMinimap: newValue });
980
994
  }, [showMinimap, setUserPreferences]);
995
+ const handleSetEditorFormHidden = useCallback((value) => {
996
+ setEditorFormHiddenState(value);
997
+ setUserPreferences({ editorFormHidden: value });
998
+ }, [setUserPreferences]);
999
+ const markEditorFormHintSeen = useCallback(() => {
1000
+ setEditorFormHintSeenState(true);
1001
+ setUserPreferences({ editorFormHintSeen: true });
1002
+ }, [setUserPreferences]);
1003
+ const showEditorFormHint = useCallback(() => {
1004
+ setEditorFormHintVisible(true);
1005
+ }, []);
1006
+ const dismissEditorFormHint = useCallback(() => {
1007
+ setEditorFormHintVisible(false);
1008
+ setEditorFormHintSeenState((prev) => {
1009
+ if (prev)
1010
+ return prev;
1011
+ setUserPreferences({ editorFormHintSeen: true });
1012
+ return true;
1013
+ });
1014
+ }, [setUserPreferences]);
981
1015
  const handleSetShowHelpTerminal = useCallback((value) => {
982
1016
  const newValue = typeof value === "function" ? value(showHelpTerminal) : value;
983
1017
  setShowHelpTerminal(newValue);
@@ -1094,7 +1128,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1094
1128
  lastUrlRef.current = window.location.href;
1095
1129
  const keepAliveUrl = "/parhelia/keepalive";
1096
1130
  const runSessionCheck = () => {
1097
- fetch(keepAliveUrl + "?ts=" + Date.now())
1131
+ fetch(keepAliveUrl + "?ts=" + Date.now(), {
1132
+ credentials: "include",
1133
+ headers: { "Cache-Control": "no-cache" },
1134
+ })
1098
1135
  .then((response) => {
1099
1136
  if (response.headers.get("X-Parhelia-Session-Revoked") === "true") {
1100
1137
  window.dispatchEvent(new CustomEvent("parhelia:session-revoked", {
@@ -1102,15 +1139,23 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1102
1139
  }));
1103
1140
  return;
1104
1141
  }
1105
- if (response.status === 401 || response.status === 403) {
1106
- toast.error("Your session has expired", {
1107
- description: "Please login again to continue editing.",
1108
- action: {
1109
- label: "Login",
1110
- onClick: () => (window.location.href = "/sitecore/login"),
1142
+ // A redirected response (e.g. to /sitecore/login) or an explicit
1143
+ // 401/403 both mean the user is no longer authenticated. Let a
1144
+ // single event-driven toast handle the UI.
1145
+ const finalUrl = response.url || "";
1146
+ const redirectedToLogin = response.redirected &&
1147
+ (/\/sitecore\/login/i.test(finalUrl) ||
1148
+ /[?&]returnUrl=/i.test(finalUrl));
1149
+ if (redirectedToLogin ||
1150
+ response.status === 401 ||
1151
+ response.status === 403) {
1152
+ window.dispatchEvent(new CustomEvent("parhelia:session-expired", {
1153
+ detail: {
1154
+ reason: redirectedToLogin
1155
+ ? "login-redirect"
1156
+ : `status-${response.status}`,
1111
1157
  },
1112
- duration: Infinity,
1113
- });
1158
+ }));
1114
1159
  }
1115
1160
  })
1116
1161
  .catch((error) => console.error("Keep Alive error:", error));
@@ -1974,16 +2019,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
1974
2019
  if (!isWorkspaceTransitioning && current.get("workspace") !== viewName) {
1975
2020
  current.set("workspace", viewName);
1976
2021
  }
1977
- // Sync sidebar state
2022
+ // Sync sidebar state. Always write a value (sentinel when empty) so that an
2023
+ // explicit "user closed everything" state survives a reload.
1978
2024
  const currentSidebars = current.get("sidebar") ?? "";
1979
- const newSidebars = openSidebars.join(",");
2025
+ const newSidebars = sidebarUrlValue(openSidebars);
1980
2026
  if (currentSidebars !== newSidebars) {
1981
- if (newSidebars) {
1982
- current.set("sidebar", newSidebars);
1983
- }
1984
- else {
1985
- current.delete("sidebar");
1986
- }
2027
+ current.set("sidebar", newSidebars);
1987
2028
  }
1988
2029
  if (showHelpTerminal) {
1989
2030
  current.set("help", selectedHelpSectionId ?? "contents");
@@ -2679,6 +2720,31 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2679
2720
  window.removeEventListener("parhelia:session-revoked", handleRevoked);
2680
2721
  };
2681
2722
  }, [promptSessionReconnect]);
2723
+ // Show a single "session expired" toast with a Login action when any
2724
+ // service call (or the keepalive check) detects that the user is no
2725
+ // longer authenticated. Using a stable toast id coalesces the toast so a
2726
+ // burst of failing calls does not spam the UI with duplicate toasts.
2727
+ useEffect(() => {
2728
+ if (typeof window === "undefined")
2729
+ return;
2730
+ const handleExpired = () => {
2731
+ toast.error("Your session has expired", {
2732
+ id: "session-expired",
2733
+ description: "Please login again to continue editing.",
2734
+ action: {
2735
+ label: "Login",
2736
+ onClick: () => {
2737
+ window.location.href = "/sitecore/login";
2738
+ },
2739
+ },
2740
+ duration: Infinity,
2741
+ });
2742
+ };
2743
+ window.addEventListener("parhelia:session-expired", handleExpired);
2744
+ return () => {
2745
+ window.removeEventListener("parhelia:session-expired", handleExpired);
2746
+ };
2747
+ }, []);
2682
2748
  const sendSocketMessage = useCallback((message) => {
2683
2749
  if (socketInstanceRef.current &&
2684
2750
  socketInstanceRef.current.readyState === WebSocket.OPEN) {
@@ -2795,7 +2861,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2795
2861
  setUserPreferences({ showAgentsPanel: false });
2796
2862
  }
2797
2863
  startTransition(() => {
2798
- updateUrl({ sidebar: newSidebars.join(",") || undefined });
2864
+ updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
2799
2865
  });
2800
2866
  return;
2801
2867
  }
@@ -2818,7 +2884,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2818
2884
  setMobileEditorPanelOpenRaw(false);
2819
2885
  }
2820
2886
  startTransition(() => {
2821
- updateUrl({ sidebar: newSidebars.join(",") || undefined });
2887
+ updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
2822
2888
  });
2823
2889
  }, [
2824
2890
  updateUrl,
@@ -2856,7 +2922,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2856
2922
  setMobileEditorPanelOpenRaw(false);
2857
2923
  }
2858
2924
  startTransition(() => {
2859
- updateUrl({ sidebar: newSidebars.join(",") || undefined });
2925
+ updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
2860
2926
  });
2861
2927
  }, [
2862
2928
  updateUrl,
@@ -2936,7 +3002,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
2936
3002
  openSidebarsRef.current = nextOpen;
2937
3003
  setOpenSidebars(nextOpen);
2938
3004
  startTransition(() => {
2939
- updateUrl({ sidebar: nextOpen.join(",") || undefined });
3005
+ updateUrl({ sidebar: sidebarUrlValue(nextOpen) });
2940
3006
  });
2941
3007
  setSidebarStacks((prev) => {
2942
3008
  const normalized = normalizeSidebarStacks(nextOpen, prev);
@@ -3039,14 +3105,23 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3039
3105
  panels: resolvedPanels,
3040
3106
  };
3041
3107
  }, [getSidebarsForWorkspace, workspaceId]);
3108
+ // Track the last workspaceId this effect saw so we only auto-apply
3109
+ // `currentWorkspace.defaultSidebars` on an actual workspace change. On initial mount
3110
+ // the user's explicit empty state (sidebar=none in URL) must not be overwritten.
3111
+ const prevWorkspaceIdForDefaultsRef = useRef(null);
3042
3112
  useEffect(() => {
3043
3113
  if (!currentWorkspace.supportsSidebars) {
3044
3114
  return;
3045
3115
  }
3116
+ const prev = prevWorkspaceIdForDefaultsRef.current;
3117
+ prevWorkspaceIdForDefaultsRef.current = workspaceId;
3118
+ const isWorkspaceChange = prev !== null && prev !== workspaceId;
3046
3119
  const allowedIds = new Set(getSidebarsForWorkspace(workspaceId).map((sidebar) => sidebar.id));
3047
3120
  const currentOpen = openSidebarsRef.current;
3048
3121
  let nextOpen = currentOpen.filter((id) => allowedIds.has(id));
3049
- if (nextOpen.length === 0 && currentWorkspace.defaultSidebars?.length) {
3122
+ if (isWorkspaceChange &&
3123
+ nextOpen.length === 0 &&
3124
+ currentWorkspace.defaultSidebars?.length) {
3050
3125
  nextOpen = currentWorkspace.defaultSidebars.filter((id) => allowedIds.has(id));
3051
3126
  }
3052
3127
  const unchanged = nextOpen.length === currentOpen.length &&
@@ -3058,7 +3133,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3058
3133
  setOpenSidebars(nextOpen);
3059
3134
  setSidebarStacks((prev) => normalizeSidebarStacks(nextOpen, prev));
3060
3135
  startTransition(() => {
3061
- updateUrl({ sidebar: nextOpen.join(",") || undefined });
3136
+ updateUrl({ sidebar: sidebarUrlValue(nextOpen) });
3062
3137
  });
3063
3138
  }, [
3064
3139
  currentWorkspace,
@@ -4247,6 +4322,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4247
4322
  setComponentNavigatorOpenForSlot,
4248
4323
  showAgentsPanel,
4249
4324
  setShowAgentsPanel: handleSetShowAgentsPanel,
4325
+ editorFormHidden,
4326
+ setEditorFormHidden: handleSetEditorFormHidden,
4327
+ editorFormHintSeen,
4328
+ markEditorFormHintSeen,
4329
+ editorFormHintVisible,
4330
+ showEditorFormHint,
4331
+ dismissEditorFormHint,
4332
+ editorFormToggleButtonRef,
4250
4333
  showMinimap,
4251
4334
  setShowMinimap: handleSetShowMinimap,
4252
4335
  showHelpTerminal,
@@ -4410,6 +4493,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4410
4493
  handleSetShowComponentNavigator,
4411
4494
  showAgentsPanel,
4412
4495
  handleSetShowAgentsPanel,
4496
+ editorFormHidden,
4497
+ handleSetEditorFormHidden,
4498
+ editorFormHintSeen,
4499
+ markEditorFormHintSeen,
4500
+ editorFormHintVisible,
4501
+ showEditorFormHint,
4502
+ dismissEditorFormHint,
4503
+ editorFormToggleButtonRef,
4413
4504
  showMinimap,
4414
4505
  handleSetShowMinimap,
4415
4506
  showHelpTerminal,
@@ -4716,6 +4807,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4716
4807
  if (!open) {
4717
4808
  setConcurrentUserLimitError(null);
4718
4809
  }
4719
- }, sessionId: sessionId, currentUsers: concurrentUserLimitError?.currentUsers ?? 0, maxUsers: concurrentUserLimitError?.maxUsers ?? 0, message: concurrentUserLimitError?.message ?? "", onRetry: handleRetryConnection, isAdministrator: userInfo.user.isAdministrator === true }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: quickSwitcherEntries.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef }), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(InlineAiTrigger, {}) }), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef }), _jsx(LicenseOverlay, {})] }) }) }) }) }));
4810
+ }, sessionId: sessionId, currentUsers: concurrentUserLimitError?.currentUsers ?? 0, maxUsers: concurrentUserLimitError?.maxUsers ?? 0, message: concurrentUserLimitError?.message ?? "", onRetry: handleRetryConnection, isAdministrator: userInfo.user.isAdministrator === true }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: quickSwitcherEntries.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef }), _jsx(EditorFormHintPopover, {}), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(InlineAiTrigger, {}) }), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef }), _jsx(LicenseOverlay, {})] }) }) }) }) }));
4720
4811
  }
4721
4812
  //# sourceMappingURL=EditorShell.js.map