@alpaca-editor/core 1.0.4086 → 1.0.4089

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 (149) hide show
  1. package/dist/components/ui/card.d.ts +1 -1
  2. package/dist/config/config.js +10 -3
  3. package/dist/config/config.js.map +1 -1
  4. package/dist/config/types.d.ts +4 -0
  5. package/dist/editor/ContentTree.js +43 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldListField.js +12 -1
  8. package/dist/editor/FieldListField.js.map +1 -1
  9. package/dist/editor/ai/AgentTerminal.d.ts +3 -1
  10. package/dist/editor/ai/AgentTerminal.js +96 -74
  11. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  12. package/dist/editor/ai/Agents.js +50 -2
  13. package/dist/editor/ai/Agents.js.map +1 -1
  14. package/dist/editor/ai/AiResponseMessage.js +171 -75
  15. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  16. package/dist/editor/ai/AiTerminal.js +27 -14
  17. package/dist/editor/ai/AiTerminal.js.map +1 -1
  18. package/dist/editor/client/EditorShell.js +121 -17
  19. package/dist/editor/client/EditorShell.js.map +1 -1
  20. package/dist/editor/client/editContext.d.ts +4 -0
  21. package/dist/editor/client/editContext.js.map +1 -1
  22. package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +1 -0
  23. package/dist/editor/client/hooks/useSocketMessageHandler.js +54 -20
  24. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  25. package/dist/editor/client/hooks/useWorkbox.d.ts +1 -1
  26. package/dist/editor/client/hooks/useWorkbox.js +4 -4
  27. package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
  28. package/dist/editor/client/itemsRepository.d.ts +13 -1
  29. package/dist/editor/client/itemsRepository.js +34 -21
  30. package/dist/editor/client/itemsRepository.js.map +1 -1
  31. package/dist/editor/client/pageModelBuilder.js +1 -1
  32. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  33. package/dist/editor/control-center/Setup.js +12 -223
  34. package/dist/editor/control-center/Setup.js.map +1 -1
  35. package/dist/editor/control-center/setup-steps/AiSetupStep.d.ts +2 -0
  36. package/dist/editor/control-center/setup-steps/AiSetupStep.js +287 -0
  37. package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -0
  38. package/dist/editor/control-center/setup-steps/DbSetupStep.d.ts +2 -0
  39. package/dist/editor/control-center/setup-steps/DbSetupStep.js +46 -0
  40. package/dist/editor/control-center/setup-steps/DbSetupStep.js.map +1 -0
  41. package/dist/editor/control-center/setup-steps/IndexSetupStep.d.ts +2 -0
  42. package/dist/editor/control-center/setup-steps/IndexSetupStep.js +34 -0
  43. package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -0
  44. package/dist/editor/control-center/setup-steps/SettingsSetupStep.d.ts +2 -0
  45. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js +104 -0
  46. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js.map +1 -0
  47. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  48. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  49. package/dist/editor/field-types/MultiLineText.js +1 -1
  50. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  51. package/dist/editor/field-types/PictureFieldEditor.js +1 -1
  52. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  53. package/dist/editor/field-types/RawEditor.js +1 -1
  54. package/dist/editor/field-types/RawEditor.js.map +1 -1
  55. package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
  56. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  57. package/dist/editor/field-types/SingleLineText.js +1 -1
  58. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  59. package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -2
  60. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  61. package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +1 -1
  62. package/dist/editor/field-types/richtext/utils/profileServiceCache.js +16 -14
  63. package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -1
  64. package/dist/editor/menubar/toolbar-sections/CompareControls.js +1 -1
  65. package/dist/editor/menubar/toolbar-sections/CompareControls.js.map +1 -1
  66. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  67. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  68. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  69. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  70. package/dist/editor/page-editor-chrome/InlineEditor.js +25 -6
  71. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  72. package/dist/editor/page-viewer/EditorForm.js +9 -2
  73. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  74. package/dist/editor/page-viewer/PageViewerFrame.js +6 -1
  75. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  76. package/dist/editor/pageModel.d.ts +1 -0
  77. package/dist/editor/reviews/Comment.js +1 -1
  78. package/dist/editor/reviews/Comment.js.map +1 -1
  79. package/dist/editor/reviews/CommentDisplayPopover.js +3 -24
  80. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  81. package/dist/editor/reviews/CommentPopover.js +3 -23
  82. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  83. package/dist/editor/reviews/CommentView.js +2 -1
  84. package/dist/editor/reviews/CommentView.js.map +1 -1
  85. package/dist/editor/reviews/Comments.js +88 -37
  86. package/dist/editor/reviews/Comments.js.map +1 -1
  87. package/dist/editor/reviews/commentAi.js +3 -0
  88. package/dist/editor/reviews/commentAi.js.map +1 -1
  89. package/dist/editor/sidebar/Debug.js +1 -5
  90. package/dist/editor/sidebar/Debug.js.map +1 -1
  91. package/dist/editor/sidebar/ViewSelector.js +72 -6
  92. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  93. package/dist/editor/ui/Icons.d.ts +5 -0
  94. package/dist/editor/ui/Icons.js +14 -0
  95. package/dist/editor/ui/Icons.js.map +1 -1
  96. package/dist/revision.d.ts +2 -2
  97. package/dist/revision.js +2 -2
  98. package/dist/splash-screen/SplashScreen.js +2 -2
  99. package/dist/splash-screen/SplashScreen.js.map +1 -1
  100. package/dist/styles.css +0 -5
  101. package/dist/types.d.ts +2 -0
  102. package/package.json +1 -1
  103. package/src/components/ui/card.tsx +1 -1
  104. package/src/config/config.tsx +9 -2
  105. package/src/config/types.ts +5 -0
  106. package/src/editor/ContentTree.tsx +48 -23
  107. package/src/editor/FieldListField.tsx +13 -2
  108. package/src/editor/ai/AgentTerminal.tsx +118 -71
  109. package/src/editor/ai/Agents.tsx +56 -1
  110. package/src/editor/ai/AiResponseMessage.tsx +234 -78
  111. package/src/editor/ai/AiTerminal.tsx +30 -14
  112. package/src/editor/client/EditorShell.tsx +140 -25
  113. package/src/editor/client/editContext.ts +1 -0
  114. package/src/editor/client/hooks/useSocketMessageHandler.ts +70 -21
  115. package/src/editor/client/hooks/useWorkbox.ts +4 -4
  116. package/src/editor/client/itemsRepository.ts +56 -25
  117. package/src/editor/client/pageModelBuilder.ts +1 -1
  118. package/src/editor/control-center/Setup.tsx +14 -420
  119. package/src/editor/control-center/setup-steps/AiSetupStep.tsx +462 -0
  120. package/src/editor/control-center/setup-steps/DbSetupStep.tsx +84 -0
  121. package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +56 -0
  122. package/src/editor/control-center/setup-steps/SettingsSetupStep.tsx +176 -0
  123. package/src/editor/field-types/ImageFieldEditor.tsx +0 -1
  124. package/src/editor/field-types/MultiLineText.tsx +0 -1
  125. package/src/editor/field-types/PictureFieldEditor.tsx +0 -1
  126. package/src/editor/field-types/RawEditor.tsx +0 -1
  127. package/src/editor/field-types/RichTextEditorComponent.tsx +0 -1
  128. package/src/editor/field-types/SingleLineText.tsx +0 -1
  129. package/src/editor/field-types/richtext/components/ReactSlate.tsx +14 -6
  130. package/src/editor/field-types/richtext/utils/profileServiceCache.ts +42 -32
  131. package/src/editor/menubar/toolbar-sections/CompareControls.tsx +1 -0
  132. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  133. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +1 -0
  134. package/src/editor/page-editor-chrome/InlineEditor.tsx +29 -6
  135. package/src/editor/page-viewer/EditorForm.tsx +13 -2
  136. package/src/editor/page-viewer/PageViewerFrame.tsx +5 -1
  137. package/src/editor/pageModel.ts +1 -0
  138. package/src/editor/reviews/Comment.tsx +1 -1
  139. package/src/editor/reviews/CommentDisplayPopover.tsx +2 -22
  140. package/src/editor/reviews/CommentPopover.tsx +3 -24
  141. package/src/editor/reviews/CommentView.tsx +3 -2
  142. package/src/editor/reviews/Comments.tsx +162 -35
  143. package/src/editor/reviews/commentAi.ts +5 -0
  144. package/src/editor/sidebar/Debug.tsx +1 -5
  145. package/src/editor/sidebar/ViewSelector.tsx +144 -28
  146. package/src/editor/ui/Icons.tsx +55 -0
  147. package/src/revision.ts +2 -2
  148. package/src/splash-screen/SplashScreen.tsx +5 -6
  149. package/src/types.ts +3 -0
@@ -0,0 +1,176 @@
1
+ import React, { useCallback, useState } from "react";
2
+ import { Card } from "../../../components/ui/card";
3
+ import { Button } from "../../../components/ui/button";
4
+ import { useEditContext } from "../../client/editContext";
5
+ import { ItemDescriptor } from "../../pageModel";
6
+ import { getChildren } from "../../services/contentService";
7
+ import type { ItemTreeNodeData } from "../../services/contentService";
8
+ import { contentItemId } from "../../../config/config";
9
+ import { CheckCircle, AlertCircle, Settings, RefreshCw } from "lucide-react";
10
+
11
+ type StepState = "idle" | "checking" | "success" | "error";
12
+
13
+ function findByName(
14
+ items: ItemTreeNodeData[],
15
+ name: string,
16
+ ): ItemTreeNodeData | undefined {
17
+ const target = name.toLowerCase();
18
+ return items.find(
19
+ (x) =>
20
+ (((x.displayName as string) || x.name || "") as string).toLowerCase() ===
21
+ target,
22
+ );
23
+ }
24
+
25
+ export function SettingsSetupStep() {
26
+ const editContext = useEditContext();
27
+ const [settingsState, setSettingsState] = useState<StepState>("checking");
28
+ const [settingsItem, setSettingsItem] = useState<ItemDescriptor | null>(null);
29
+ const [settingsError, setSettingsError] = useState<string | null>(null);
30
+ const [creatingSettings, setCreatingSettings] = useState(false);
31
+
32
+ const userLang = editContext?.contentEditorItem?.language || "en";
33
+
34
+ const statusIcon = useCallback((state: StepState) => {
35
+ if (state === "success")
36
+ return <CheckCircle className="h-4 w-4 text-green-600" strokeWidth={1} />;
37
+ if (state === "error")
38
+ return <AlertCircle className="h-4 w-4 text-red-600" strokeWidth={1} />;
39
+ return (
40
+ <RefreshCw
41
+ className="h-4 w-4 animate-spin text-amber-600"
42
+ strokeWidth={1}
43
+ />
44
+ );
45
+ }, []);
46
+
47
+ const resolvePathUnderContent = useCallback(
48
+ async (names: string[]): Promise<ItemDescriptor | null> => {
49
+ if (!editContext?.sessionId) return null;
50
+ let currentId = contentItemId;
51
+ for (const segment of names) {
52
+ const children = (await getChildren(
53
+ currentId,
54
+ editContext.sessionId,
55
+ [],
56
+ false,
57
+ userLang,
58
+ "path",
59
+ )) as unknown as ItemTreeNodeData[];
60
+ const next = findByName(children, segment);
61
+ if (!next) return null;
62
+ currentId = next.id;
63
+ }
64
+ return {
65
+ id: currentId,
66
+ language: userLang,
67
+ version: 0,
68
+ } as ItemDescriptor;
69
+ },
70
+ [editContext?.sessionId, userLang],
71
+ );
72
+
73
+ const checkSettingsItem = useCallback(async () => {
74
+ try {
75
+ setSettingsState("checking");
76
+ setSettingsError(null);
77
+ setSettingsItem(null);
78
+
79
+ const editorItem = await resolvePathUnderContent(["Settings", "Editor"]);
80
+ if (editorItem) {
81
+ setSettingsItem(editorItem);
82
+ setSettingsState("success");
83
+ return;
84
+ }
85
+
86
+ setSettingsState("error");
87
+ setSettingsError(
88
+ "Editor settings item not found at /sitecore/content/Settings/Editor",
89
+ );
90
+ } catch (e: any) {
91
+ setSettingsState("error");
92
+ setSettingsError(e?.message || "Failed to check settings item");
93
+ }
94
+ }, [resolvePathUnderContent]);
95
+
96
+ const createSettingsItem = useCallback(async () => {
97
+ if (!editContext?.itemsRepository) return;
98
+ try {
99
+ setCreatingSettings(true);
100
+ setSettingsError(null);
101
+
102
+ const parentDescriptor: ItemDescriptor = {
103
+ id: "{2DE1F76B-39F0-49A0-8589-8F360E0B3F7E}",
104
+ language: userLang,
105
+ version: 0,
106
+ } as any;
107
+
108
+ const branchOrTemplateId = "f133cae9-8fbc-4cdf-868e-321a533dec5b";
109
+
110
+ const created = await editContext.operations.createItem(
111
+ parentDescriptor,
112
+ branchOrTemplateId,
113
+ "Editor",
114
+ );
115
+
116
+ if (created) {
117
+ setSettingsItem(created);
118
+ setSettingsState("success");
119
+ } else {
120
+ setSettingsState("error");
121
+ setSettingsError("Failed to create settings item");
122
+ }
123
+ } catch (e: any) {
124
+ setSettingsState("error");
125
+ setSettingsError(e?.message || "Failed to create settings item");
126
+ } finally {
127
+ setCreatingSettings(false);
128
+ }
129
+ }, [editContext, userLang]);
130
+
131
+ React.useEffect(() => {
132
+ checkSettingsItem();
133
+ }, [checkSettingsItem]);
134
+
135
+ return (
136
+ <Card
137
+ icon={<Settings strokeWidth={1} className="h-5 w-5" />}
138
+ title="Editor settings"
139
+ description="Checks presence of /sitecore/content/Settings/Editor and allows creating it."
140
+ >
141
+ <div className="flex items-center justify-between gap-2">
142
+ <div className="flex items-center gap-2">
143
+ {statusIcon(settingsItem ? "success" : settingsState)}
144
+ <span className="text-sm text-gray-700">
145
+ {settingsItem
146
+ ? "Settings item present"
147
+ : settingsState === "error"
148
+ ? "Settings item missing"
149
+ : "Checking..."}
150
+ </span>
151
+ </div>
152
+ {!settingsItem && (
153
+ <Button
154
+ size="sm"
155
+ onClick={createSettingsItem}
156
+ disabled={creatingSettings}
157
+ >
158
+ {creatingSettings ? (
159
+ <RefreshCw strokeWidth={1} className="h-4 w-4 animate-spin" />
160
+ ) : (
161
+ <CheckCircle strokeWidth={1} className="h-4 w-4" />
162
+ )}
163
+ Create
164
+ </Button>
165
+ )}
166
+ </div>
167
+ {settingsError && (
168
+ <div className="mt-2 rounded border border-yellow-200 bg-yellow-50 p-2 text-xs whitespace-pre-wrap text-yellow-800">
169
+ {settingsError}
170
+ </div>
171
+ )}
172
+ </Card>
173
+ );
174
+ }
175
+
176
+ export default SettingsSetupStep;
@@ -58,7 +58,6 @@ export function ImageFieldEditor({
58
58
  refresh: "waitForQuietPeriod",
59
59
  });
60
60
  }}
61
- onBlur={editContext.operations.onFieldBlur}
62
61
  />
63
62
  </div>
64
63
  </div>
@@ -80,7 +80,6 @@ export function MultiLineText({
80
80
  setValue(e.target.value);
81
81
  updateFieldValue(e.target.value as string);
82
82
  }}
83
- onBlur={editContextRef.current?.operations.onFieldBlur}
84
83
  />
85
84
  );
86
85
  }
@@ -114,7 +114,6 @@ export function PictureFieldEditor({
114
114
  refresh: "waitForQuietPeriod",
115
115
  });
116
116
  }}
117
- onBlur={editContext.operations.onFieldBlur}
118
117
  />
119
118
  </div>
120
119
  </div>
@@ -48,7 +48,6 @@ export function RawEditor({
48
48
  debouncedSetFieldvalue(e.target.value as string);
49
49
  // field.value = e.target.value;
50
50
  }}
51
- onBlur={editContext?.operations.onFieldBlur}
52
51
  />
53
52
  );
54
53
  }
@@ -110,7 +110,6 @@ export function RichTextEditorComponent({
110
110
  onFocus={() => setFocused(true)}
111
111
  onBlur={() => setFocused(false)}
112
112
  readOnly={readOnly}
113
- placeholder="Enter text here..."
114
113
  profile={editorProfile}
115
114
  showControls={showControls}
116
115
  />
@@ -181,7 +181,6 @@ export function SingleLineText({
181
181
  style={{ width: "100%" }}
182
182
  onChange={handleChange}
183
183
  onSelect={handleSelect}
184
- onBlur={editContextRef.current?.operations.onFieldBlur}
185
184
  />
186
185
  {isManualRefreshEnabled && (
187
186
  <button
@@ -40,7 +40,10 @@ import { LinkEditorDialog } from "../../../LinkEditorDialog";
40
40
 
41
41
  import { htmlToSlate, slateToHtml } from "../utils/conversion";
42
42
  import { createPluginsFromConfig } from "../config/pluginFactory";
43
- import { createKeyboardHandler, generateInternalLinkUrl } from "../utils/plugins";
43
+ import {
44
+ createKeyboardHandler,
45
+ generateInternalLinkUrl,
46
+ } from "../utils/plugins";
44
47
  import { normalizeUrl } from "../../../utils/urlUtils";
45
48
 
46
49
  import { useCachedSimplifiedProfile } from "../hooks/useProfileCache";
@@ -774,11 +777,15 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
774
777
  textAlign: element.align || "left",
775
778
  };
776
779
 
777
- if (element.type === "link") {
778
- const isInternal = element.link?.type === "internal";
779
- const url = isInternal
780
- ? generateInternalLinkUrl(element.link?.itemId, element.link?.targetItemLongId, element.link?.queryString)
781
- : normalizeUrl(element.url || element.link?.url || "#");
780
+ if (element.type === "link") {
781
+ const isInternal = element.link?.type === "internal";
782
+ const url = isInternal
783
+ ? generateInternalLinkUrl(
784
+ element.link?.itemId,
785
+ element.link?.targetItemLongId,
786
+ element.link?.queryString,
787
+ )
788
+ : normalizeUrl(element.url || element.link?.url || "#");
782
789
 
783
790
  return (
784
791
  <a
@@ -965,6 +972,7 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
965
972
  )}
966
973
  <Editable
967
974
  className={classNames(
975
+ "border-gray-3 min-h-8 rounded-sm border",
968
976
  readOnly ? "bg-gray-4" : "bg-gray-5",
969
977
  "focus-shadow p-2",
970
978
  "slate-editable",
@@ -1,11 +1,14 @@
1
- import { RichTextEditorProfile } from '../types';
2
- import { mapSitecoreProfileJsonToSlate } from './profileMapper';
1
+ import { RichTextEditorProfile } from "../types";
2
+ import { mapSitecoreProfileJsonToSlate } from "./profileMapper";
3
3
 
4
4
  // Single cache for parsed profiles
5
5
  const parsedProfileCache = new Map<string, RichTextEditorProfile | null>();
6
6
 
7
7
  // Cache for in-flight requests to prevent duplicate calls
8
- const inflightRequests = new Map<string, Promise<RichTextEditorProfile | null>>();
8
+ const inflightRequests = new Map<
9
+ string,
10
+ Promise<RichTextEditorProfile | null>
11
+ >();
9
12
 
10
13
  // Cache statistics
11
14
  interface ServiceCacheStats {
@@ -17,7 +20,7 @@ interface ServiceCacheStats {
17
20
  let serviceStats: ServiceCacheStats = {
18
21
  hits: 0,
19
22
  misses: 0,
20
- cacheSize: 0
23
+ cacheSize: 0,
21
24
  };
22
25
 
23
26
  /**
@@ -26,60 +29,67 @@ let serviceStats: ServiceCacheStats = {
26
29
  */
27
30
  export async function getCachedParsedProfile(
28
31
  profilePath: string,
29
- serviceCall: (path: string) => Promise<any>
32
+ serviceCall: (path: string) => Promise<any>,
30
33
  ): Promise<RichTextEditorProfile | null> {
31
- console.log(`[ProfileServiceCache] Getting parsed profile for path: ${profilePath}`);
32
-
34
+ //console.log(`[ProfileServiceCache] Getting parsed profile for path: ${profilePath}`);
35
+
33
36
  // Check if we already have a cached result
34
37
  if (parsedProfileCache.has(profilePath)) {
35
38
  serviceStats.hits++;
36
39
  const cachedResult = parsedProfileCache.get(profilePath)!;
37
- console.log(`[ProfileServiceCache] Cache hit for ${profilePath}`);
40
+ //console.log(`[ProfileServiceCache] Cache hit for ${profilePath}`);
38
41
  return cachedResult;
39
42
  }
40
43
 
41
44
  // Check if there's already an in-flight request for this profile
42
45
  if (inflightRequests.has(profilePath)) {
43
46
  serviceStats.hits++;
44
- console.log(`[ProfileServiceCache] In-flight request found for ${profilePath}`);
47
+ //console.log(
48
+ // `[ProfileServiceCache] In-flight request found for ${profilePath}`,
49
+ //);
45
50
  return inflightRequests.get(profilePath)!;
46
51
  }
47
52
 
48
53
  // Make the service call, parse, and cache the result
49
54
  serviceStats.misses++;
50
- console.log(`[ProfileServiceCache] Making service call for ${profilePath}`);
51
-
55
+ // console.log(`[ProfileServiceCache] Making service call for ${profilePath}`);
56
+
52
57
  const servicePromise = serviceCall(profilePath)
53
- .then(response => {
54
- console.log(`[ProfileServiceCache] Service call completed for ${profilePath}`, response);
55
-
58
+ .then((response) => {
59
+ //console.log(`[ProfileServiceCache] Service call completed for ${profilePath}`, response);
60
+
56
61
  let parsedProfile: RichTextEditorProfile | null = null;
57
-
62
+
58
63
  if (response?.data) {
59
64
  const jsonString = JSON.stringify(response.data);
60
- console.log(`[ProfileServiceCache] Parsing profile for ${profilePath}, JSON length: ${jsonString.length}`);
61
-
65
+ //console.log(`[ProfileServiceCache] Parsing profile for ${profilePath}, JSON length: ${jsonString.length}`);
66
+
62
67
  parsedProfile = mapSitecoreProfileJsonToSlate(jsonString);
63
-
68
+
64
69
  if (parsedProfile) {
65
- console.log(`[ProfileServiceCache] Successfully parsed profile for ${profilePath}`);
70
+ //console.log(`[ProfileServiceCache] Successfully parsed profile for ${profilePath}`);
66
71
  } else {
67
- console.warn(`[ProfileServiceCache] Failed to parse profile for ${profilePath}`);
72
+ console.warn(
73
+ `[ProfileServiceCache] Failed to parse profile for ${profilePath}`,
74
+ );
68
75
  }
69
76
  }
70
-
77
+
71
78
  // Cache the parsed result (even if null)
72
79
  parsedProfileCache.set(profilePath, parsedProfile);
73
80
  serviceStats.cacheSize = parsedProfileCache.size;
74
-
81
+
75
82
  // Remove from in-flight requests
76
83
  inflightRequests.delete(profilePath);
77
-
78
- console.log(`[ProfileServiceCache] Cached parsed profile for ${profilePath}, cache size: ${serviceStats.cacheSize}`);
84
+
85
+ //console.log( `[ProfileServiceCache] Cached parsed profile for ${profilePath}, cache size: ${serviceStats.cacheSize}`, );
79
86
  return parsedProfile;
80
87
  })
81
- .catch(error => {
82
- console.error(`[ProfileServiceCache] Service call failed for ${profilePath}:`, error);
88
+ .catch((error) => {
89
+ console.error(
90
+ `[ProfileServiceCache] Service call failed for ${profilePath}:`,
91
+ error,
92
+ );
83
93
  // Remove from in-flight requests on error
84
94
  inflightRequests.delete(profilePath);
85
95
  throw error;
@@ -97,7 +107,7 @@ export async function getCachedParsedProfile(
97
107
  */
98
108
  export async function getCachedRichTextProfile(
99
109
  profilePath: string,
100
- serviceCall: (path: string) => Promise<any>
110
+ serviceCall: (path: string) => Promise<any>,
101
111
  ): Promise<string | null> {
102
112
  const parsedProfile = await getCachedParsedProfile(profilePath, serviceCall);
103
113
  return parsedProfile ? JSON.stringify(parsedProfile) : null;
@@ -107,13 +117,13 @@ export async function getCachedRichTextProfile(
107
117
  * Clear all caches
108
118
  */
109
119
  export function clearServiceCache(): void {
110
- console.log('[ProfileServiceCache] Clearing all caches');
120
+ console.log("[ProfileServiceCache] Clearing all caches");
111
121
  parsedProfileCache.clear();
112
122
  inflightRequests.clear();
113
123
  serviceStats = {
114
124
  hits: 0,
115
125
  misses: 0,
116
- cacheSize: 0
126
+ cacheSize: 0,
117
127
  };
118
128
  }
119
129
 
@@ -124,11 +134,11 @@ export function evictProfileFromServiceCache(profilePath: string): boolean {
124
134
  console.log(`[ProfileServiceCache] Evicting profile: ${profilePath}`);
125
135
  const evicted = parsedProfileCache.delete(profilePath);
126
136
  inflightRequests.delete(profilePath);
127
-
137
+
128
138
  if (evicted) {
129
139
  serviceStats.cacheSize = parsedProfileCache.size;
130
140
  }
131
-
141
+
132
142
  return evicted;
133
143
  }
134
144
 
@@ -151,4 +161,4 @@ export function getCachedProfilePaths(): string[] {
151
161
  */
152
162
  export function isProfilePathCached(profilePath: string): boolean {
153
163
  return parsedProfileCache.has(profilePath);
154
- }
164
+ }
@@ -12,6 +12,7 @@ export function CompareControls() {
12
12
  icon={<CompareIcon className="h-6 w-6 p-1" />}
13
13
  label="Compare"
14
14
  size="large"
15
+ data-testid="compare-mode-button"
15
16
  className={
16
17
  editContext.compareMode ? "text-gray-600" : "hover:text-gray-600"
17
18
  }
@@ -43,6 +43,7 @@ export function EditControls({
43
43
  icon={<EyeIcon className="h-6 w-6 p-1" strokeWidth={1} />}
44
44
  label="Preview"
45
45
  size="large"
46
+ data-testid="preview-mode-button"
46
47
  selected={editContext.mode === "preview"}
47
48
  onClick={() => editContext.setMode("preview")}
48
49
  />
@@ -48,6 +48,7 @@ export function ViewportControls() {
48
48
  icon={<CompareIcon className="h-6 w-6 p-1" />}
49
49
  label="Compare"
50
50
  size="large"
51
+ data-testid="compare-mode-button"
51
52
  className={
52
53
  editContext.compareMode ? "text-gray-600" : "hover:text-gray-600"
53
54
  }
@@ -516,7 +516,7 @@ export function InlineEditor({
516
516
  // First, process modified fields directly (from updateFields functionality)
517
517
  modifiedFieldsContext?.modifiedFields.forEach((field) => {
518
518
  const elements = doc.querySelectorAll(
519
- `[data-fieldid="${field.fieldId}"][data-itemid="${field.item.id}"][data-language="${field.item.language}"][data-version="${field.item.version}"`,
519
+ `[data-fieldid="${field.fieldId}"][data-itemid="${field.item.id}"][data-language="${field.item.language}"][data-version="${field.item.version}"]`,
520
520
  );
521
521
 
522
522
  elements?.forEach(async (element) => {
@@ -528,7 +528,10 @@ export function InlineEditor({
528
528
  const fieldType = realField?.type;
529
529
 
530
530
  if (fieldType && isTextFieldType(fieldType)) {
531
- element.innerHTML = field?.value ? (field.value as string) : "";
531
+ const next = field?.value ? (field.value as string) : "";
532
+ if (element.innerHTML !== next) {
533
+ element.innerHTML = next;
534
+ }
532
535
  }
533
536
  }
534
537
  });
@@ -590,6 +593,18 @@ export function InlineEditor({
590
593
  if (!fieldId || !itemId || !language || !versionStr) return;
591
594
  const version = parseInt(versionStr, 10);
592
595
 
596
+ // Skip if this field currently has local modifications to avoid stomping fresh edits
597
+ const isModifiedNow = modifiedFieldsContext?.modifiedFields?.some(
598
+ (m: any) =>
599
+ m.fieldId === fieldId &&
600
+ m.item.id === itemId &&
601
+ m.item.language === language &&
602
+ m.item.version === version,
603
+ );
604
+ if (isModifiedNow) {
605
+ return;
606
+ }
607
+
593
608
  // Build an item descriptor.
594
609
  const descriptor = { id: itemId, language, version };
595
610
 
@@ -619,7 +634,9 @@ export function InlineEditor({
619
634
 
620
635
  // If showSuggestedEdits is false, update with the base value.
621
636
  if (!context.showSuggestedEdits && context.mode !== "suggestions") {
622
- fieldElement.innerHTML = originalValue;
637
+ if (fieldElement.innerHTML !== originalValue) {
638
+ fieldElement.innerHTML = originalValue;
639
+ }
623
640
 
624
641
  return;
625
642
  }
@@ -672,7 +689,9 @@ export function InlineEditor({
672
689
 
673
690
  // If showSuggestedEditsDiff is false, show only the merged text
674
691
  if (!context.showSuggestedEditsDiff) {
675
- fieldElement.innerHTML = mergedValue;
692
+ if (fieldElement.innerHTML !== mergedValue) {
693
+ fieldElement.innerHTML = mergedValue;
694
+ }
676
695
  return;
677
696
  }
678
697
 
@@ -695,7 +714,9 @@ export function InlineEditor({
695
714
  });
696
715
 
697
716
  // Update the element's innerHTML with the diff markup.
698
- fieldElement.innerHTML = diffHTML;
717
+ if (fieldElement.innerHTML !== diffHTML) {
718
+ fieldElement.innerHTML = diffHTML;
719
+ }
699
720
  });
700
721
  };
701
722
 
@@ -791,7 +812,9 @@ export function InlineEditor({
791
812
  }
792
813
 
793
814
  // write it in
794
- el.innerHTML = value;
815
+ if (el.innerHTML !== value) {
816
+ el.innerHTML = value;
817
+ }
795
818
  });
796
819
  }, [context.mode, context.showSuggestedEdits, context.suggestedEdits]);
797
820
 
@@ -1,4 +1,4 @@
1
- import { useEditContext } from "../client/editContext";
1
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
2
2
  import { getComponentById } from "../componentTreeHelper";
3
3
 
4
4
  import { FieldList, ItemFields } from "../FieldList";
@@ -28,6 +28,7 @@ export function EditorForm({
28
28
  initialActiveTab?: string | null;
29
29
  }) {
30
30
  const editContext = useEditContext()!;
31
+ const fieldsContext = useFieldsEditContext();
31
32
  if (!pageViewContext) pageViewContext = editContext.pageView;
32
33
 
33
34
  const insertMode =
@@ -337,7 +338,17 @@ export function EditorForm({
337
338
  });
338
339
 
339
340
  return (
340
- <div className="flex h-full flex-col" data-testid="editor-sidepanel">
341
+ <div
342
+ className="flex h-full flex-col"
343
+ data-testid="editor-sidepanel"
344
+ onClickCapture={(e) => {
345
+ const target = e.target as HTMLElement;
346
+ const inField = target.closest("[data-field-id]");
347
+ if (!inField) {
348
+ fieldsContext?.setFocusedField(undefined, false);
349
+ }
350
+ }}
351
+ >
341
352
  <h1 className="border-gray-3 flex h-12 items-center justify-center border-b p-2">
342
353
  {component && component !== pageViewContext.page?.rootComponent && (
343
354
  <SimpleIconButton
@@ -572,6 +572,10 @@ export function PageViewerFrame({
572
572
  );
573
573
  // Don't prevent default - we want the browser's natural cursor positioning
574
574
  }
575
+ } else {
576
+ // Clicked inside iframe but not on a field: clear focused field
577
+ fieldsContextRef.current?.setFocusedField(undefined, false);
578
+ fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
575
579
  }
576
580
  }
577
581
  //Forward events so primereact overlays can close
@@ -818,8 +822,8 @@ export function PageViewerFrame({
818
822
  () => {
819
823
  // Block blur event if it was triggered by clicking on a field
820
824
  if (blockBlurEventRef.current < Date.now()) {
821
- //editContext.setFocusedField(undefined, false);
822
825
  fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
826
+ editContextRef.current?.operations.onFieldBlur?.();
823
827
  } else {
824
828
  blockBlurEventRef.current = 0;
825
829
  }
@@ -45,6 +45,7 @@ export type ItemDescriptor = {
45
45
  language: string;
46
46
  version: number;
47
47
  name?: string;
48
+ path?: string;
48
49
  };
49
50
 
50
51
  export type Field = {
@@ -122,7 +122,7 @@ export function Comment({
122
122
  }
123
123
  placeholder="Add a comment..."
124
124
  submitLabel={comment.isNew ? "Comment" : "Save"}
125
- showTagSelector={false} // Tags not shown in sidebar editor for now
125
+ showTagSelector={true}
126
126
  />
127
127
  </div>
128
128
  );
@@ -15,7 +15,6 @@ import {
15
15
  resolveComment,
16
16
  unresolveComment,
17
17
  createOrUpdateComment,
18
- getAvailableCommentTags,
19
18
  } from "../services/reviewsService";
20
19
  import { CommentView } from "./CommentView";
21
20
  import { CommentEditor } from "./CommentEditor";
@@ -38,29 +37,10 @@ export function CommentDisplayPopover({
38
37
  const [isSaving, setIsSaving] = useState(false);
39
38
  const [editText, setEditText] = useState(comment.text);
40
39
  const [deleteConfirm, setDeleteConfirm] = useState(false);
41
- const [availableTags, setAvailableTags] = useState<
42
- { label: string; color: string }[]
43
- >([]);
40
+ const availableTags = editContext?.availableCommentTags || [];
44
41
  const contentRef = React.useRef<HTMLDivElement | null>(null);
45
42
 
46
- // Load available tags
47
- React.useEffect(() => {
48
- let cancelled = false;
49
- const loadTags = async () => {
50
- try {
51
- const descriptor = editContext?.contentEditorItem?.descriptor;
52
- if (!descriptor) return;
53
- const tags = await getAvailableCommentTags(descriptor);
54
- if (!cancelled) setAvailableTags(tags || []);
55
- } catch {
56
- if (!cancelled) setAvailableTags([]);
57
- }
58
- };
59
- loadTags();
60
- return () => {
61
- cancelled = true;
62
- };
63
- }, [editContext?.contentEditorItem?.descriptor?.id]);
43
+ // Tags are now provided by editContext.availableCommentTags
64
44
 
65
45
  const canDelete =
66
46
  !comment.isNew && comment.author === editContext?.user?.name;