@alpaca-editor/core 1.0.4027 → 1.0.4030

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 (220) hide show
  1. package/dist/components/ActionButton.js +2 -2
  2. package/dist/components/ActionButton.js.map +1 -1
  3. package/dist/components/SimpleLanguageSelector.js +3 -1
  4. package/dist/components/SimpleLanguageSelector.js.map +1 -1
  5. package/dist/components/ui/button.d.ts +1 -1
  6. package/dist/config/config.js +1 -1
  7. package/dist/config/config.js.map +1 -1
  8. package/dist/config/types.d.ts +1 -1
  9. package/dist/editor/ContextMenu.js +0 -1
  10. package/dist/editor/ContextMenu.js.map +1 -1
  11. package/dist/editor/Editor.js +9 -3
  12. package/dist/editor/Editor.js.map +1 -1
  13. package/dist/editor/FieldListField.js +16 -24
  14. package/dist/editor/FieldListField.js.map +1 -1
  15. package/dist/editor/ImageEditButton.js +1 -1
  16. package/dist/editor/ImageEditButton.js.map +1 -1
  17. package/dist/editor/ImageEditor.js +1 -1
  18. package/dist/editor/ImageEditor.js.map +1 -1
  19. package/dist/editor/MainLayout.js +2 -2
  20. package/dist/editor/MainLayout.js.map +1 -1
  21. package/dist/editor/Terminal.js +1 -1
  22. package/dist/editor/Terminal.js.map +1 -1
  23. package/dist/editor/Titlebar.js +0 -1
  24. package/dist/editor/Titlebar.js.map +1 -1
  25. package/dist/editor/ai/AgentCostDisplay.d.ts +26 -0
  26. package/dist/editor/ai/AgentCostDisplay.js +65 -0
  27. package/dist/editor/ai/AgentCostDisplay.js.map +1 -0
  28. package/dist/editor/ai/Agents.js +59 -15
  29. package/dist/editor/ai/Agents.js.map +1 -1
  30. package/dist/editor/ai/AiPromptPopover.d.ts +7 -0
  31. package/dist/editor/ai/AiPromptPopover.js +111 -0
  32. package/dist/editor/ai/AiPromptPopover.js.map +1 -0
  33. package/dist/editor/ai/AiResponseMessage.d.ts +1 -0
  34. package/dist/editor/ai/AiResponseMessage.js +101 -23
  35. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  36. package/dist/editor/ai/AiTerminal.d.ts +15 -1
  37. package/dist/editor/ai/AiTerminal.js +379 -48
  38. package/dist/editor/ai/AiTerminal.js.map +1 -1
  39. package/dist/editor/ai/editorAiContext.d.ts +0 -1
  40. package/dist/editor/ai/editorAiContext.js +0 -2
  41. package/dist/editor/ai/editorAiContext.js.map +1 -1
  42. package/dist/editor/client/EditorClient.d.ts +3 -2
  43. package/dist/editor/client/EditorClient.js +326 -68
  44. package/dist/editor/client/EditorClient.js.map +1 -1
  45. package/dist/editor/client/editContext.d.ts +6 -4
  46. package/dist/editor/client/editContext.js.map +1 -1
  47. package/dist/editor/client/fieldModificationStore.d.ts +19 -0
  48. package/dist/editor/client/fieldModificationStore.js +125 -0
  49. package/dist/editor/client/fieldModificationStore.js.map +1 -0
  50. package/dist/editor/client/itemsRepository.d.ts +1 -1
  51. package/dist/editor/client/itemsRepository.js +38 -28
  52. package/dist/editor/client/itemsRepository.js.map +1 -1
  53. package/dist/editor/client/operations.d.ts +1 -0
  54. package/dist/editor/client/operations.js +39 -31
  55. package/dist/editor/client/operations.js.map +1 -1
  56. package/dist/editor/commands/componentCommands.js +5 -3
  57. package/dist/editor/commands/componentCommands.js.map +1 -1
  58. package/dist/editor/commands/itemCommands.js.map +1 -1
  59. package/dist/editor/component-designer/aiContext.js +0 -2
  60. package/dist/editor/component-designer/aiContext.js.map +1 -1
  61. package/dist/editor/field-types/DropLinkEditor.js +1 -1
  62. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  63. package/dist/editor/field-types/MultiLineText.js +5 -7
  64. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  65. package/dist/editor/field-types/RichTextEditorComponent.js +5 -7
  66. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  67. package/dist/editor/field-types/SingleLineText.js +5 -7
  68. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  69. package/dist/editor/hooks/useEditorSettings.d.ts +17 -0
  70. package/dist/editor/hooks/useEditorSettings.js +61 -0
  71. package/dist/editor/hooks/useEditorSettings.js.map +1 -0
  72. package/dist/editor/menubar/ItemActionsMenu.js +2 -2
  73. package/dist/editor/menubar/ItemActionsMenu.js.map +1 -1
  74. package/dist/editor/menubar/PageSelector.js +1 -1
  75. package/dist/editor/menubar/PageSelector.js.map +1 -1
  76. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  77. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  78. package/dist/editor/menubar/toolbar-sections/InsertControls.js +1 -1
  79. package/dist/editor/menubar/toolbar-sections/InsertControls.js.map +1 -1
  80. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +1 -1
  81. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  82. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  83. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  84. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js +4 -3
  85. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js.map +1 -1
  86. package/dist/editor/page-editor-chrome/FrameMenu.js +9 -1
  87. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  88. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +0 -1
  89. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  90. package/dist/editor/page-viewer/EditorForm.js +1 -1
  91. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  92. package/dist/editor/page-viewer/PageViewer.js +9 -8
  93. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  94. package/dist/editor/page-viewer/PageViewerFrame.js +7 -1
  95. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  96. package/dist/editor/page-viewer/pageViewContext.js +40 -6
  97. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  98. package/dist/editor/reviews/Comment.js +7 -6
  99. package/dist/editor/reviews/Comment.js.map +1 -1
  100. package/dist/editor/services/agentService.d.ts +84 -12
  101. package/dist/editor/services/agentService.js +256 -15
  102. package/dist/editor/services/agentService.js.map +1 -1
  103. package/dist/editor/services/aiService.d.ts +17 -3
  104. package/dist/editor/services/aiService.js +5 -3
  105. package/dist/editor/services/aiService.js.map +1 -1
  106. package/dist/editor/services/contextService.js +0 -1
  107. package/dist/editor/services/contextService.js.map +1 -1
  108. package/dist/editor/services/systemService.d.ts +2 -1
  109. package/dist/editor/services/systemService.js +3 -0
  110. package/dist/editor/services/systemService.js.map +1 -1
  111. package/dist/editor/sidebar/ComponentPalette.js +1 -1
  112. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  113. package/dist/editor/sidebar/EditHistory.js +2 -2
  114. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  115. package/dist/editor/sidebar/GraphQL.d.ts +1 -0
  116. package/dist/editor/sidebar/GraphQL.js +8 -2
  117. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  118. package/dist/editor/sidebar/MainContentTree.js +1 -1
  119. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  120. package/dist/editor/sidebar/SEOInfo.js +1 -1
  121. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  122. package/dist/editor/sidebar/ViewSelector.d.ts +4 -1
  123. package/dist/editor/sidebar/ViewSelector.js +64 -48
  124. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  125. package/dist/editor/ui/PerfectTree.js +2 -11
  126. package/dist/editor/ui/PerfectTree.js.map +1 -1
  127. package/dist/editor/ui/SimpleIconButton.d.ts +2 -0
  128. package/dist/editor/ui/SimpleIconButton.js +8 -4
  129. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  130. package/dist/index.d.ts +2 -0
  131. package/dist/index.js +2 -0
  132. package/dist/index.js.map +1 -1
  133. package/dist/page-wizard/steps/CollectStep.js +1 -1
  134. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  135. package/dist/page-wizard/steps/StructureStep.js +1 -1
  136. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  137. package/dist/page-wizard/steps/TranslateStep.js +233 -18
  138. package/dist/page-wizard/steps/TranslateStep.js.map +1 -1
  139. package/dist/revision.d.ts +2 -2
  140. package/dist/revision.js +2 -2
  141. package/dist/splash-screen/RecentPages.js +1 -13
  142. package/dist/splash-screen/RecentPages.js.map +1 -1
  143. package/dist/splash-screen/SplashScreen.js +1 -1
  144. package/dist/splash-screen/SplashScreen.js.map +1 -1
  145. package/dist/styles.css +88 -3
  146. package/dist/types.d.ts +6 -0
  147. package/package.json +2 -2
  148. package/src/components/ActionButton.tsx +3 -2
  149. package/src/components/SimpleLanguageSelector.tsx +6 -1
  150. package/src/config/config.tsx +1 -1
  151. package/src/config/types.ts +1 -1
  152. package/src/editor/ContextMenu.tsx +0 -3
  153. package/src/editor/Editor.tsx +11 -3
  154. package/src/editor/FieldListField.tsx +22 -31
  155. package/src/editor/ImageEditButton.tsx +1 -0
  156. package/src/editor/ImageEditor.tsx +1 -0
  157. package/src/editor/MainLayout.tsx +2 -2
  158. package/src/editor/Terminal.tsx +1 -1
  159. package/src/editor/Titlebar.tsx +0 -2
  160. package/src/editor/ai/AgentCostDisplay.tsx +237 -0
  161. package/src/editor/ai/Agents.tsx +69 -20
  162. package/src/editor/ai/AiPromptPopover.tsx +209 -0
  163. package/src/editor/ai/AiResponseMessage.tsx +201 -60
  164. package/src/editor/ai/AiTerminal.tsx +502 -71
  165. package/src/editor/ai/editorAiContext.ts +0 -3
  166. package/src/editor/client/EditorClient.tsx +409 -117
  167. package/src/editor/client/editContext.ts +7 -5
  168. package/src/editor/client/fieldModificationStore.ts +196 -0
  169. package/src/editor/client/itemsRepository.ts +41 -31
  170. package/src/editor/client/operations.ts +95 -76
  171. package/src/editor/commands/componentCommands.tsx +9 -3
  172. package/src/editor/commands/itemCommands.tsx +0 -1
  173. package/src/editor/component-designer/aiContext.ts +0 -2
  174. package/src/editor/field-types/DropLinkEditor.tsx +1 -1
  175. package/src/editor/field-types/MultiLineText.tsx +9 -9
  176. package/src/editor/field-types/RichTextEditorComponent.tsx +8 -8
  177. package/src/editor/field-types/SingleLineText.tsx +9 -9
  178. package/src/editor/hooks/useEditorSettings.ts +68 -0
  179. package/src/editor/menubar/ItemActionsMenu.tsx +3 -2
  180. package/src/editor/menubar/PageSelector.tsx +1 -1
  181. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  182. package/src/editor/menubar/toolbar-sections/InsertControls.tsx +1 -0
  183. package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +2 -0
  184. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +2 -0
  185. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +4 -3
  186. package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
  187. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +0 -1
  188. package/src/editor/page-viewer/EditorForm.tsx +1 -0
  189. package/src/editor/page-viewer/PageViewer.tsx +9 -8
  190. package/src/editor/page-viewer/PageViewerFrame.tsx +7 -1
  191. package/src/editor/page-viewer/pageViewContext.ts +40 -5
  192. package/src/editor/reviews/Comment.tsx +7 -7
  193. package/src/editor/services/agentService.ts +405 -31
  194. package/src/editor/services/aiService.ts +22 -5
  195. package/src/editor/services/contextService.ts +0 -1
  196. package/src/editor/services/systemService.ts +7 -1
  197. package/src/editor/sidebar/ComponentPalette.tsx +4 -1
  198. package/src/editor/sidebar/EditHistory.tsx +2 -0
  199. package/src/editor/sidebar/GraphQL.tsx +19 -7
  200. package/src/editor/sidebar/MainContentTree.tsx +1 -1
  201. package/src/editor/sidebar/SEOInfo.tsx +1 -1
  202. package/src/editor/sidebar/ViewSelector.tsx +80 -64
  203. package/src/editor/ui/PerfectTree.tsx +2 -18
  204. package/src/editor/ui/SimpleIconButton.tsx +56 -38
  205. package/src/index.ts +2 -0
  206. package/src/page-wizard/steps/CollectStep.tsx +0 -2
  207. package/src/page-wizard/steps/StructureStep.tsx +3 -0
  208. package/src/page-wizard/steps/TranslateStep.tsx +473 -62
  209. package/src/revision.ts +2 -2
  210. package/src/splash-screen/RecentPages.tsx +0 -14
  211. package/src/splash-screen/SplashScreen.tsx +3 -2
  212. package/src/types.ts +7 -0
  213. package/dist/editor/ai/AiPopup.d.ts +0 -10
  214. package/dist/editor/ai/AiPopup.js +0 -23
  215. package/dist/editor/ai/AiPopup.js.map +0 -1
  216. package/dist/editor/ai/AiToolCall.d.ts +0 -5
  217. package/dist/editor/ai/AiToolCall.js +0 -28
  218. package/dist/editor/ai/AiToolCall.js.map +0 -1
  219. package/src/editor/ai/AiPopup.tsx +0 -59
  220. package/src/editor/ai/AiToolCall.tsx +0 -71
@@ -35,6 +35,7 @@ import {
35
35
  SuggestedEdit,
36
36
  UserInfo,
37
37
  UserPreferences,
38
+ EditorSettings,
38
39
  } from "../../types";
39
40
 
40
41
  import { ConfirmationProps } from "../ConfirmationDialog";
@@ -153,6 +154,7 @@ export type EditContextType = {
153
154
  itemLanguages: LanguageVersions[];
154
155
  itemVersions: Version[];
155
156
  view?: EditorView;
157
+ visibleViews: EditorView[];
156
158
 
157
159
  viewName: string;
158
160
  previousViewName?: string;
@@ -247,15 +249,12 @@ export type EditContextType = {
247
249
  setCurrentOverlay: React.Dispatch<React.SetStateAction<any>>;
248
250
  currentOverlay?: any;
249
251
 
250
- showAiPopup: (
251
- event: MouseEvent<HTMLElement>,
252
- aiTerminalOptions?: AiTerminalOptions,
253
- ) => void;
254
252
  showFieldEditorPopup: (fields: Field[], sections: string[], ev: any) => void;
255
253
  showEditorFormPopup: (
256
254
  event: MouseEvent<HTMLElement>,
257
255
  activeTab?: string,
258
256
  ) => void;
257
+
259
258
  validationResult: ValidationResult[] | undefined;
260
259
  contentEditorItem: FullItem | undefined;
261
260
 
@@ -315,7 +314,6 @@ export type EditContextType = {
315
314
  setCompareTo: React.Dispatch<
316
315
  React.SetStateAction<ItemDescriptor | undefined>
317
316
  >;
318
- lastEditedFields: EditedField[];
319
317
  revision?: string;
320
318
  user?: User;
321
319
  statusMessage: React.ReactNode;
@@ -369,6 +367,7 @@ export type EditContextType = {
369
367
  webSocketMessages: WebSocketMessage[];
370
368
  clearWebSocketMessages: () => void;
371
369
  userInfo: UserInfo;
370
+ editorSettings?: EditorSettings;
372
371
  setUserPreferences: (preferences: Partial<UserPreferences>) => void;
373
372
  favorites: any[];
374
373
  loadFavorites: () => Promise<void>;
@@ -409,7 +408,10 @@ export type ModifiedField = FieldDescriptor & {
409
408
 
410
409
  export type ModifiedFieldsContextType = {
411
410
  modifiedFields: ModifiedField[];
411
+ recentEdits: EditedField[];
412
412
  clear: () => void;
413
+ clearRecentEdits: () => void;
414
+ addRecentEdit: (edit: EditedField) => void;
413
415
  };
414
416
 
415
417
  export type EditedField = FieldDescriptor & {
@@ -0,0 +1,196 @@
1
+ import { useEffect, useState, useRef } from "react";
2
+ import { ModifiedField } from "./editContext";
3
+
4
+ // Field-specific subscription store that doesn't rely on React context
5
+ class FieldModificationStore {
6
+ private subscribers = new Map<
7
+ string,
8
+ Set<(field: ModifiedField | undefined) => void>
9
+ >();
10
+ private fieldData = new Map<string, ModifiedField>();
11
+
12
+ private getFieldKey(
13
+ fieldId: string,
14
+ itemId: string,
15
+ language: string,
16
+ version: number,
17
+ ): string {
18
+ return `${fieldId}:${itemId}:${language}:${version}`;
19
+ }
20
+
21
+ // Subscribe to a specific field's changes
22
+ subscribe(
23
+ fieldId: string,
24
+ itemId: string,
25
+ language: string,
26
+ version: number,
27
+ callback: (field: ModifiedField | undefined) => void,
28
+ ): () => void {
29
+ const key = this.getFieldKey(fieldId, itemId, language, version);
30
+
31
+ if (!this.subscribers.has(key)) {
32
+ this.subscribers.set(key, new Set());
33
+ }
34
+
35
+ this.subscribers.get(key)!.add(callback);
36
+
37
+ // Immediately call with current value
38
+ callback(this.fieldData.get(key));
39
+
40
+ // Return unsubscribe function
41
+ return () => {
42
+ const subscribers = this.subscribers.get(key);
43
+ if (subscribers) {
44
+ subscribers.delete(callback);
45
+ if (subscribers.size === 0) {
46
+ this.subscribers.delete(key);
47
+ }
48
+ }
49
+ };
50
+ }
51
+
52
+ // Update a specific field (called from itemsRepository)
53
+ updateField(field: ModifiedField): void {
54
+ const key = this.getFieldKey(
55
+ field.fieldId,
56
+ field.item.id,
57
+ field.item.language,
58
+ field.item.version,
59
+ );
60
+ const previousField = this.fieldData.get(key);
61
+
62
+ // Check for actual changes to avoid unnecessary re-renders
63
+ const hasChanged =
64
+ !previousField ||
65
+ previousField.value !== field.value ||
66
+ previousField.isDirty !== field.isDirty ||
67
+ previousField.saveSequence !== field.saveSequence;
68
+
69
+ // Always update the stored field data
70
+ this.fieldData.set(key, field);
71
+
72
+ if (hasChanged) {
73
+ // Notify only subscribers for this specific field
74
+ const subscribers = this.subscribers.get(key);
75
+ if (subscribers) {
76
+ subscribers.forEach((callback) => callback(field));
77
+ }
78
+ }
79
+ }
80
+
81
+ // Remove a field (when it's no longer modified)
82
+ removeField(
83
+ fieldId: string,
84
+ itemId: string,
85
+ language: string,
86
+ version: number,
87
+ ): void {
88
+ const key = this.getFieldKey(fieldId, itemId, language, version);
89
+ this.fieldData.delete(key);
90
+
91
+ // Notify subscribers that field is no longer modified
92
+ const subscribers = this.subscribers.get(key);
93
+ if (subscribers) {
94
+ subscribers.forEach((callback) => callback(undefined));
95
+ }
96
+ }
97
+
98
+ // Clear all fields
99
+ clear(): void {
100
+ this.fieldData.clear();
101
+ // Notify all subscribers that their fields are no longer modified
102
+ this.subscribers.forEach((subscribers) => {
103
+ subscribers.forEach((callback) => callback(undefined));
104
+ });
105
+ }
106
+
107
+ // Get current field data (for context compatibility)
108
+ getAllFields(): ModifiedField[] {
109
+ return Array.from(this.fieldData.values());
110
+ }
111
+
112
+ // Bulk update (for when itemsRepository updates multiple fields)
113
+ updateFields(fields: ModifiedField[]): void {
114
+ // Track which keys we've updated
115
+ const updatedKeys = new Set<string>();
116
+
117
+ // Update all provided fields
118
+ fields.forEach((field) => {
119
+ const key = this.getFieldKey(
120
+ field.fieldId,
121
+ field.item.id,
122
+ field.item.language,
123
+ field.item.version,
124
+ );
125
+ updatedKeys.add(key);
126
+
127
+ const previousField = this.fieldData.get(key);
128
+ if (
129
+ !previousField ||
130
+ previousField.value !== field.value ||
131
+ previousField.isDirty !== field.isDirty ||
132
+ previousField.saveSequence !== field.saveSequence
133
+ ) {
134
+ this.fieldData.set(key, field);
135
+
136
+ const subscribers = this.subscribers.get(key);
137
+ if (subscribers) {
138
+ subscribers.forEach((callback) => callback(field));
139
+ }
140
+ }
141
+ });
142
+
143
+ // Remove fields that are no longer in the update
144
+ for (const [key, field] of this.fieldData.entries()) {
145
+ if (!updatedKeys.has(key)) {
146
+ this.fieldData.delete(key);
147
+ const subscribers = this.subscribers.get(key);
148
+ if (subscribers) {
149
+ subscribers.forEach((callback) => callback(undefined));
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ // Global store instance
157
+ const fieldModificationStore = new FieldModificationStore();
158
+
159
+ // Hook for field-specific subscriptions - only rerenders when THIS field changes
160
+ export function useFieldModification(
161
+ fieldId: string,
162
+ itemId: string,
163
+ language: string,
164
+ version: number,
165
+ ) {
166
+ const [modifiedField, setModifiedField] = useState<ModifiedField | undefined>(
167
+ undefined,
168
+ );
169
+ const [isModified, setIsModified] = useState(false);
170
+ const [isSaved, setIsSaved] = useState(false);
171
+
172
+ useEffect(() => {
173
+ const unsubscribe = fieldModificationStore.subscribe(
174
+ fieldId,
175
+ itemId,
176
+ language,
177
+ version,
178
+ (field) => {
179
+ setModifiedField(field);
180
+ setIsModified(!!field);
181
+ // isSaved logic:
182
+ // - If field exists and is not dirty → saved = true (field was modified and saved)
183
+ // - If field exists and is dirty → saved = false (field was modified but not saved)
184
+ // - If field doesn't exist → saved = false (field never modified, no indicator shown)
185
+ setIsSaved(field ? !field.isDirty : false);
186
+ },
187
+ );
188
+
189
+ return unsubscribe;
190
+ }, [fieldId, itemId, language, version]);
191
+
192
+ return { modifiedField, isModified, isSaved };
193
+ }
194
+
195
+ // Export store for itemsRepository to use
196
+ export { fieldModificationStore };
@@ -12,6 +12,7 @@ import { getItemDescriptor } from "../utils";
12
12
  import { EditedField, ModifiedField } from "./editContext";
13
13
  import { User } from "../../types";
14
14
  import { templatesRootItemId } from "../../config/config";
15
+ import { fieldModificationStore } from "./fieldModificationStore";
15
16
 
16
17
  export type ItemChange = {
17
18
  item: ItemDescriptor;
@@ -96,7 +97,7 @@ const isTemplateChange = async (
96
97
 
97
98
  export function useItemsRepository(
98
99
  setModifiedFields: React.Dispatch<React.SetStateAction<ModifiedField[]>>,
99
- setLastEditedFields: React.Dispatch<React.SetStateAction<EditedField[]>>,
100
+ addRecentEdit: (edit: EditedField) => void,
100
101
  ): ItemsRepository {
101
102
  const itemsMap = useRef<Map<string, FullItem>>(new Map());
102
103
  const stubsMap = useRef<Map<string, ItemStub>>(new Map());
@@ -403,47 +404,42 @@ export function useItemsRepository(
403
404
  x.item.language === field.item.language &&
404
405
  x.item.version === field.item.version,
405
406
  );
407
+
408
+ let newModifiedFields: ModifiedField[];
409
+ let updatedField: ModifiedField;
410
+
406
411
  if (!modifiedField) {
407
- return [
408
- ...prevModifiedFields,
409
- {
410
- ...field,
411
- value: val,
412
- isDirty: isDirty,
413
- modifiedBy: user,
414
- timestamp: Date.now(),
415
- saveSequence: currentSaveSequence,
416
- },
417
- ];
412
+ updatedField = {
413
+ ...field,
414
+ value: val,
415
+ isDirty: isDirty,
416
+ modifiedBy: user,
417
+ timestamp: Date.now(),
418
+ saveSequence: currentSaveSequence,
419
+ };
420
+ newModifiedFields = [...prevModifiedFields, updatedField];
418
421
  } else {
419
422
  modifiedField.isDirty = isDirty;
420
423
  modifiedField.value = val;
421
424
  modifiedField.modifiedBy = user;
422
425
  modifiedField.timestamp = Date.now();
423
426
  modifiedField.saveSequence = currentSaveSequence;
427
+ updatedField = { ...modifiedField }; // Create new object to ensure change detection
428
+ newModifiedFields = [...prevModifiedFields];
424
429
  }
425
430
 
426
- return [...prevModifiedFields];
427
- });
431
+ // Update the field-specific store for optimized subscriptions
432
+ fieldModificationStore.updateField(updatedField);
428
433
 
429
- setLastEditedFields((prevEditedFields) => {
430
- return [
431
- ...prevEditedFields.filter(
432
- (x) =>
433
- x.timestamp > Date.now() - 30000 &&
434
- (x.fieldId !== field.fieldId ||
435
- x.item.id !== field.item.id ||
436
- x.item.language !== field.item.language ||
437
- x.item.version !== field.item.version),
438
- ),
439
- { ...field, timestamp: Date.now(), user },
440
- ];
434
+ return newModifiedFields;
441
435
  });
442
436
 
437
+ // addRecentEdit({ ...field, timestamp: Date.now(), user });
438
+
443
439
  // setRevision((prev) => prev + 1);
444
440
  return currentSaveSequence;
445
441
  },
446
- [getItem, setModifiedFields, setLastEditedFields],
442
+ [getItem, setModifiedFields, addRecentEdit],
447
443
  );
448
444
 
449
445
  const refreshItems = useCallback(
@@ -509,7 +505,12 @@ export function useItemsRepository(
509
505
  return field.rawValue !== x.value;
510
506
  };
511
507
 
512
- setModifiedFields(updateModifiedFields);
508
+ setModifiedFields((prevFields) => {
509
+ const newFields = updateModifiedFields(prevFields);
510
+ // Update the field-specific store with the new fields
511
+ fieldModificationStore.updateFields(newFields);
512
+ return newFields;
513
+ });
513
514
  setRevision((prev) => prev + 1);
514
515
  }
515
516
  triggerItemsChangedEvent(
@@ -530,14 +531,23 @@ export function useItemsRepository(
530
531
  x.item.version === field.item.version,
531
532
  );
532
533
  if (modifiedField) {
533
- // Only mark as not dirty if this save operation corresponds to the most recent edit
534
- // or if the value matches and this is the latest save operation
535
- if (
534
+ // If the field is already clean (isDirty: false), the save was successful
535
+ // so we should update the UI state even if save sequences don't match exactly
536
+ if (!modifiedField.isDirty) {
537
+ // Create new object to ensure change detection works
538
+ const savedField = { ...modifiedField };
539
+ fieldModificationStore.updateField(savedField);
540
+ }
541
+ // Otherwise, check save sequence for validation
542
+ else if (
536
543
  modifiedField.saveSequence === saveSequence ||
537
544
  (modifiedField.value === value &&
538
545
  modifiedField.saveSequence <= saveSequence)
539
546
  ) {
540
547
  modifiedField.isDirty = false;
548
+ // Create new object to ensure change detection works
549
+ const savedField = { ...modifiedField };
550
+ fieldModificationStore.updateField(savedField);
541
551
  }
542
552
  return [...prevModifiedFields];
543
553
  }