@alpaca-editor/core 1.0.3937 → 1.0.3939

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 (81) hide show
  1. package/dist/editor/ContentTree.js +12 -8
  2. package/dist/editor/ContentTree.js.map +1 -1
  3. package/dist/editor/ContextMenu.d.ts +1 -1
  4. package/dist/editor/ContextMenu.js +17 -3
  5. package/dist/editor/ContextMenu.js.map +1 -1
  6. package/dist/editor/FieldActionsOverlay.d.ts +17 -0
  7. package/dist/editor/FieldActionsOverlay.js +148 -0
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -0
  9. package/dist/editor/FieldHistory.d.ts +2 -1
  10. package/dist/editor/FieldHistory.js +11 -8
  11. package/dist/editor/FieldHistory.js.map +1 -1
  12. package/dist/editor/FieldListField.js +14 -17
  13. package/dist/editor/FieldListField.js.map +1 -1
  14. package/dist/editor/FieldListFieldWithFallbacks.js +10 -5
  15. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  16. package/dist/editor/PictureEditor.js +28 -2
  17. package/dist/editor/PictureEditor.js.map +1 -1
  18. package/dist/editor/Titlebar.js +18 -9
  19. package/dist/editor/Titlebar.js.map +1 -1
  20. package/dist/editor/ai/AiTerminal.js +27 -41
  21. package/dist/editor/ai/AiTerminal.js.map +1 -1
  22. package/dist/editor/client/EditorClient.js +48 -18
  23. package/dist/editor/client/EditorClient.js.map +1 -1
  24. package/dist/editor/client/editContext.d.ts +1 -1
  25. package/dist/editor/client/editContext.js.map +1 -1
  26. package/dist/editor/client/itemsRepository.js +126 -90
  27. package/dist/editor/client/itemsRepository.js.map +1 -1
  28. package/dist/editor/menubar/BrowseHistory.js +3 -4
  29. package/dist/editor/menubar/BrowseHistory.js.map +1 -1
  30. package/dist/editor/menubar/PageSelector.js +37 -9
  31. package/dist/editor/menubar/PageSelector.js.map +1 -1
  32. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +1 -1
  33. package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
  34. package/dist/editor/page-viewer/PageViewerFrame.js +98 -3
  35. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  36. package/dist/editor/pageModel.d.ts +14 -0
  37. package/dist/editor/reviews/Comment.js +3 -2
  38. package/dist/editor/reviews/Comment.js.map +1 -1
  39. package/dist/editor/services/editService.d.ts +1 -1
  40. package/dist/editor/services/editService.js +2 -1
  41. package/dist/editor/services/editService.js.map +1 -1
  42. package/dist/editor/ui/Icons.js +1 -1
  43. package/dist/editor/ui/Icons.js.map +1 -1
  44. package/dist/editor/ui/ItemList.d.ts +16 -0
  45. package/dist/editor/ui/ItemList.js +19 -0
  46. package/dist/editor/ui/ItemList.js.map +1 -0
  47. package/dist/editor/ui/ItemSearch.js +2 -12
  48. package/dist/editor/ui/ItemSearch.js.map +1 -1
  49. package/dist/editor/ui/SimpleTabs.js +1 -1
  50. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  51. package/dist/page-wizard/WizardSteps.js +10 -0
  52. package/dist/page-wizard/WizardSteps.js.map +1 -1
  53. package/dist/revision.d.ts +2 -2
  54. package/dist/revision.js +2 -2
  55. package/dist/styles.css +3 -8
  56. package/package.json +1 -1
  57. package/src/editor/ContentTree.tsx +15 -12
  58. package/src/editor/ContextMenu.tsx +20 -2
  59. package/src/editor/FieldActionsOverlay.tsx +307 -0
  60. package/src/editor/FieldHistory.tsx +9 -8
  61. package/src/editor/FieldListField.tsx +29 -29
  62. package/src/editor/FieldListFieldWithFallbacks.tsx +11 -5
  63. package/src/editor/PictureEditor.tsx +66 -1
  64. package/src/editor/Titlebar.tsx +22 -11
  65. package/src/editor/ai/AiTerminal.tsx +42 -53
  66. package/src/editor/client/EditorClient.tsx +62 -18
  67. package/src/editor/client/editContext.ts +5 -1
  68. package/src/editor/client/itemsRepository.ts +151 -115
  69. package/src/editor/menubar/BrowseHistory.tsx +7 -16
  70. package/src/editor/menubar/PageSelector.tsx +91 -66
  71. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +1 -1
  72. package/src/editor/page-viewer/PageViewerFrame.tsx +143 -2
  73. package/src/editor/pageModel.ts +12 -0
  74. package/src/editor/reviews/Comment.tsx +5 -6
  75. package/src/editor/services/editService.ts +2 -0
  76. package/src/editor/ui/Icons.tsx +1 -0
  77. package/src/editor/ui/ItemList.tsx +76 -0
  78. package/src/editor/ui/ItemSearch.tsx +9 -46
  79. package/src/editor/ui/SimpleTabs.tsx +1 -1
  80. package/src/page-wizard/WizardSteps.tsx +13 -0
  81. package/src/revision.ts +2 -2
@@ -121,72 +121,60 @@ export function AiTerminal({
121
121
  terminalCallback: (text: React.ReactNode, finished: boolean) => void,
122
122
  isFinished: boolean,
123
123
  ) {
124
- const currentMessages = messagesRef.current;
125
124
  const updatedMessages = response.messages;
126
125
 
127
- // Merge updatedMessages into currentMessages if they exist
126
+ // Replace the conversation history with the authoritative response from AI
128
127
  if (updatedMessages && Array.isArray(updatedMessages)) {
129
- // Ensure each message has the required properties
130
- updatedMessages.forEach((message) => {
131
- const existingMessageIndex = currentMessages.findIndex(
132
- (m) => m.id === message.id,
133
- );
128
+ const formattedMessages = updatedMessages.map((message) => {
134
129
  const formattedContent = message.content
135
130
  .trim()
136
131
  .replaceAll("\n", "<br>")
137
132
  ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
138
133
 
139
- if (existingMessageIndex !== -1) {
140
- // Update existing message
141
- currentMessages[existingMessageIndex] = {
142
- ...currentMessages[existingMessageIndex],
143
- ...message,
144
- content: message.content,
145
- formattedContent: formattedContent,
146
- tool_calls:
147
- message.tool_calls ||
148
- currentMessages[existingMessageIndex]?.tool_calls ||
149
- [],
150
- };
151
- } else {
152
- // Add new message
153
- currentMessages.push({
154
- ...message,
155
- content: formattedContent,
156
- tool_calls: message.tool_calls || [], // Ensure toolCalls exists
157
- });
158
- }
134
+ return {
135
+ ...message,
136
+ content: message.content,
137
+ formattedContent: formattedContent,
138
+ tool_calls: message.tool_calls || [],
139
+ };
159
140
  });
160
- }
161
141
 
162
- // Update the messages state
163
- setMessages([...currentMessages]);
164
- setResponseMessages([...currentMessages]);
165
-
166
- terminalCallback(
167
- <AiResponseMessage
168
- messages={currentMessages}
169
- editOperations={response.editOperations}
170
- finished={isFinished}
171
- />,
172
- isFinished,
173
- );
142
+ // Update the messages state with the complete conversation from AI
143
+ setMessages([...formattedMessages]);
144
+ setResponseMessages([...formattedMessages]);
145
+
146
+ terminalCallback(
147
+ <AiResponseMessage
148
+ messages={formattedMessages}
149
+ editOperations={response.editOperations}
150
+ finished={isFinished}
151
+ />,
152
+ isFinished,
153
+ );
154
+ } else {
155
+ // Fallback: if no messages in response, keep current state
156
+ terminalCallback(
157
+ <AiResponseMessage
158
+ messages={messagesRef.current}
159
+ editOperations={response.editOperations}
160
+ finished={isFinished}
161
+ />,
162
+ isFinished,
163
+ );
164
+ }
174
165
  }
175
166
 
176
167
  async function commandHandler(
177
168
  text: string,
178
169
  callback: (text: React.ReactNode, finished: boolean) => void,
179
170
  ) {
180
- const newMessages = [
181
- ...messagesRef.current,
182
- {
183
- id: Date.now(), // Add unique id
184
- content: text,
185
- role: "user",
186
- name: "user",
187
- tool_calls: [], // Add empty toolCalls array
188
- },
189
- ];
171
+ const userMessage = {
172
+ id: Date.now(), // Add unique id
173
+ content: text,
174
+ role: "user",
175
+ name: "user",
176
+ tool_calls: [], // Add empty toolCalls array
177
+ };
190
178
 
191
179
  const context = createAiContext({ editContext });
192
180
 
@@ -194,8 +182,8 @@ export function AiTerminal({
194
182
  const selectedText = editContext?.selectedRange?.text || null;
195
183
  if (!activeProfile || !model) return;
196
184
 
197
- setResponseMessages([]);
198
-
185
+ // Build complete message history for API call
186
+ const conversationHistory = [...messagesRef.current, userMessage];
199
187
  const messages = [
200
188
  ...(options?.hiddenSystemPrompt
201
189
  ? [
@@ -208,7 +196,7 @@ export function AiTerminal({
208
196
  },
209
197
  ]
210
198
  : []),
211
- ...newMessages,
199
+ ...conversationHistory,
212
200
  ];
213
201
 
214
202
  const response = await executePrompt(
@@ -318,6 +306,7 @@ export function AiTerminal({
318
306
  ref={terminalRef}
319
307
  onReset={() => {
320
308
  setMessages([]);
309
+ setResponseMessages([]);
321
310
  setResponse(undefined);
322
311
  }}
323
312
  infobar={
@@ -701,29 +701,71 @@ export function EditorClient({
701
701
  }, [user]);
702
702
 
703
703
  useEffect(() => {
704
- let socket: WebSocket = (globalThis as any).editorSocket;
704
+ let reconnectTimeout: NodeJS.Timeout | null = null;
705
+ let reconnectAttempts = 0;
705
706
 
706
- if (
707
- socket &&
708
- (socket.readyState === WebSocket.OPEN ||
709
- socket.readyState === WebSocket.CONNECTING)
710
- )
711
- return;
707
+ const connectWebSocket = () => {
708
+ let socket: WebSocket = (globalThis as any).editorSocket;
709
+
710
+ if (
711
+ socket &&
712
+ (socket.readyState === WebSocket.OPEN ||
713
+ socket.readyState === WebSocket.CONNECTING)
714
+ )
715
+ return;
712
716
 
713
- socket = connectSocket(sessionId);
717
+ socket = connectSocket(sessionId);
714
718
 
715
- // Connection opened
716
- socket.addEventListener("open", () => {
717
- console.log("Connected!");
718
- sendClientInfo();
719
- requestQuota();
720
- //TODO: Load clients
721
- });
719
+ // Connection opened
720
+ socket.addEventListener("open", () => {
721
+ console.log("Connected!");
722
+ reconnectAttempts = 0; // Reset attempts on successful connection
723
+ sendClientInfo();
724
+ requestQuota();
725
+ //TODO: Load clients
726
+ });
727
+
728
+ // Listen for messages
729
+ socket.addEventListener("message", messageHandler);
730
+
731
+ // Handle connection close
732
+ socket.addEventListener("close", (event) => {
733
+ console.log("WebSocket connection closed:", event.code, event.reason);
734
+
735
+ // Only attempt to reconnect if it wasn't a clean close
736
+ if (event.code !== 1000) {
737
+ // Start with 1 second, increase exponentially up to 30 seconds
738
+ const delay = Math.min(
739
+ 1000 * Math.pow(2, Math.min(reconnectAttempts, 5)),
740
+ 30000,
741
+ );
742
+ console.log(
743
+ `Attempting to reconnect in ${delay}ms... (attempt ${reconnectAttempts + 1})`,
744
+ );
745
+
746
+ reconnectTimeout = setTimeout(() => {
747
+ reconnectAttempts++;
748
+ connectWebSocket();
749
+ }, delay);
750
+ }
751
+ });
722
752
 
723
- // Listen for messages
724
- socket.addEventListener("message", messageHandler);
753
+ // Handle connection errors
754
+ socket.addEventListener("error", (error) => {
755
+ console.error("WebSocket error:", error);
756
+ });
725
757
 
726
- (globalThis as any).editorSocket = socket;
758
+ (globalThis as any).editorSocket = socket;
759
+ };
760
+
761
+ connectWebSocket();
762
+
763
+ // Cleanup function
764
+ return () => {
765
+ if (reconnectTimeout) {
766
+ clearTimeout(reconnectTimeout);
767
+ }
768
+ };
727
769
  }, []);
728
770
 
729
771
  useEffect(() => {
@@ -1790,6 +1832,7 @@ export function EditorClient({
1790
1832
  triggerFieldAction: async (
1791
1833
  fieldDescriptor: FieldDescriptor,
1792
1834
  actionButton: FieldButton,
1835
+ parameters?: Record<string, string>,
1793
1836
  ) => {
1794
1837
  const field = await itemsRepository.getField(fieldDescriptor);
1795
1838
 
@@ -1831,6 +1874,7 @@ export function EditorClient({
1831
1874
  actionButton.action,
1832
1875
  editContext!.sessionId,
1833
1876
  selectedRange?.text || "",
1877
+ parameters || {},
1834
1878
  (data: any) => {
1835
1879
  op.message = data.responseText;
1836
1880
  setActiveFieldActions((prevFieldActions) => [
@@ -235,7 +235,11 @@ export type EditContextType = {
235
235
  unlockField: (field: FieldDescriptor) => void;
236
236
 
237
237
  readonly: boolean;
238
- triggerFieldAction: (field: FieldDescriptor, action: FieldButton) => void;
238
+ triggerFieldAction: (
239
+ field: FieldDescriptor,
240
+ action: FieldButton,
241
+ parameters?: Record<string, string>,
242
+ ) => void;
239
243
  activeFieldActions: FieldAction[];
240
244
  showContextMenu(e: any, menuItems: MenuItem[]): void;
241
245
  setCurrentOverlay: React.Dispatch<React.SetStateAction<any>>;
@@ -82,52 +82,59 @@ export function useItemsRepository(
82
82
  }
83
83
 
84
84
  const fetchPromise = (async () => {
85
- const fetchedItems = await fetchItems(itemsToFetch);
86
-
87
- if (!fetchedItems) return;
88
-
89
- fetchedItems.forEach((item, index) => {
90
- if (!item) return;
91
- const descriptor = getItemDescriptor(item);
92
- const itemToStore = {
93
- ...item,
94
- descriptor,
95
- };
96
-
97
- itemsMap.current.set(generateKey(item), itemToStore);
98
-
99
- if (itemsToFetch[index]?.version === 0) {
100
- const key = generateKey(itemsToFetch[index]);
101
- itemsMap.current.set(key, itemToStore);
102
- }
103
-
104
- if (item.fields) {
105
- item.fields.forEach((field) => {
106
- if (field.fallbackChain) {
107
- const descriptor = getItemDescriptor(item);
108
- field.fallbackChain.forEach((fallback) => {
109
- const prev = fallbackDependencyMap.current.get(
110
- getItemIdLanguageKey(fallback.item),
111
- );
112
-
113
- if (
114
- !prev?.find(
115
- (x) =>
116
- x.id === descriptor.id &&
117
- x.language === descriptor.language &&
118
- x.version === descriptor.version,
119
- )
120
- ) {
121
- fallbackDependencyMap.current.set(
85
+ try {
86
+ const fetchedItems = await fetchItems(itemsToFetch);
87
+
88
+ if (!fetchedItems) return;
89
+
90
+ fetchedItems.forEach((item, index) => {
91
+ if (!item) return;
92
+ const descriptor = getItemDescriptor(item);
93
+ const itemToStore = {
94
+ ...item,
95
+ descriptor,
96
+ };
97
+
98
+ const primaryKey = generateKey(item);
99
+ itemsMap.current.set(primaryKey, itemToStore);
100
+
101
+ // Only store version 0 if the current item isn't already version 0
102
+ if (itemsToFetch[index]?.version === 0 && item.version !== 0) {
103
+ const versionZeroKey = generateKey(itemsToFetch[index]);
104
+ itemsMap.current.set(versionZeroKey, itemToStore);
105
+ }
106
+
107
+ if (item.fields) {
108
+ item.fields.forEach((field) => {
109
+ if (field.fallbackChain) {
110
+ const descriptor = getItemDescriptor(item);
111
+ field.fallbackChain.forEach((fallback) => {
112
+ const prev = fallbackDependencyMap.current.get(
122
113
  getItemIdLanguageKey(fallback.item),
123
- [...(prev || []), descriptor],
124
114
  );
125
- }
126
- });
127
- }
128
- });
129
- }
130
- });
115
+
116
+ if (
117
+ !prev?.find(
118
+ (x) =>
119
+ x.id === descriptor.id &&
120
+ x.language === descriptor.language &&
121
+ x.version === descriptor.version,
122
+ )
123
+ ) {
124
+ fallbackDependencyMap.current.set(
125
+ getItemIdLanguageKey(fallback.item),
126
+ [...(prev || []), descriptor],
127
+ );
128
+ }
129
+ });
130
+ }
131
+ });
132
+ }
133
+ });
134
+ } catch (error) {
135
+ console.error("Error fetching items:", error);
136
+ throw error;
137
+ }
131
138
  })();
132
139
 
133
140
  // Store the promise for each item being fetched
@@ -145,7 +152,7 @@ export function useItemsRepository(
145
152
  });
146
153
  }
147
154
  },
148
- [itemsMap.current],
155
+ [],
149
156
  );
150
157
 
151
158
  const getItems = useCallback(
@@ -168,55 +175,7 @@ export function useItemsRepository(
168
175
  .map((x) => map.current.get(generateKey(x)))
169
176
  .filter((x): x is FullItem => x !== undefined);
170
177
  },
171
- [itemsMap.current, fetchItemsFromServer],
172
- );
173
-
174
- const getItemsStubs = useCallback(
175
- async (descriptors: ItemDescriptor[]): Promise<ItemStub[]> => {
176
- const keys = descriptors.map((x) => ({
177
- key: generateKey(x),
178
- descriptor: x,
179
- }));
180
-
181
- // First try to get stubs from full items if available
182
- const result: ItemStub[] = [];
183
- const itemsToFetch: ItemDescriptor[] = [];
184
-
185
- for (const { key, descriptor } of keys) {
186
- // If we have the full item, use it
187
- const fullItem = itemsMap.current.get(key);
188
- if (fullItem) {
189
- result.push(fullItem);
190
- continue;
191
- }
192
-
193
- // If we have a cached stub, use it
194
- const cachedStub = stubsMap.current.get(key);
195
- if (cachedStub) {
196
- result.push(cachedStub);
197
- continue;
198
- }
199
-
200
- // Otherwise, fetch it
201
- itemsToFetch.push(descriptor);
202
- }
203
-
204
- if (itemsToFetch.length > 0) {
205
- await fetchStubsFromServer(itemsToFetch);
206
-
207
- // Add fetched stubs to the result
208
- for (const descriptor of itemsToFetch) {
209
- const key = generateKey(descriptor);
210
- const stub = stubsMap.current.get(key);
211
- if (stub) {
212
- result.push(stub);
213
- }
214
- }
215
- }
216
-
217
- return result;
218
- },
219
- [itemsMap.current, stubsMap.current],
178
+ [fetchItemsFromServer],
220
179
  );
221
180
 
222
181
  const fetchStubsFromServer = useCallback(
@@ -242,19 +201,26 @@ export function useItemsRepository(
242
201
  }
243
202
 
244
203
  const fetchPromise = (async () => {
245
- const fetchedStubs = await fetchItemStubs(itemsToFetch);
204
+ try {
205
+ const fetchedStubs = await fetchItemStubs(itemsToFetch);
246
206
 
247
- if (!fetchedStubs) return;
207
+ if (!fetchedStubs) return;
248
208
 
249
- fetchedStubs.forEach((stub, index) => {
250
- if (!stub) return;
251
- stubsMap.current.set(generateKey(stub), stub);
209
+ fetchedStubs.forEach((stub, index) => {
210
+ if (!stub) return;
211
+ const primaryKey = generateKey(stub);
212
+ stubsMap.current.set(primaryKey, stub);
252
213
 
253
- if (itemsToFetch[index]?.version === 0) {
254
- const key = generateKey(itemsToFetch[index]);
255
- stubsMap.current.set(key, stub);
256
- }
257
- });
214
+ // Only store version 0 if the current stub isn't already version 0
215
+ if (itemsToFetch[index]?.version === 0 && stub.version !== 0) {
216
+ const versionZeroKey = generateKey(itemsToFetch[index]);
217
+ stubsMap.current.set(versionZeroKey, stub);
218
+ }
219
+ });
220
+ } catch (error) {
221
+ console.error("Error fetching stubs:", error);
222
+ throw error;
223
+ }
258
224
  })();
259
225
 
260
226
  // Store the promise for each item being fetched
@@ -272,15 +238,55 @@ export function useItemsRepository(
272
238
  });
273
239
  }
274
240
  },
275
- [stubsMap.current],
241
+ [],
276
242
  );
277
243
 
278
- const getField = useCallback(
279
- async (fieldDescriptor: FieldDescriptor): Promise<Field | undefined> => {
280
- const item = await getItem(fieldDescriptor.item);
281
- return item?.fields.find((x) => x.id === fieldDescriptor.fieldId);
244
+ const getItemsStubs = useCallback(
245
+ async (descriptors: ItemDescriptor[]): Promise<ItemStub[]> => {
246
+ const keys = descriptors.map((x) => ({
247
+ key: generateKey(x),
248
+ descriptor: x,
249
+ }));
250
+
251
+ // First try to get stubs from full items if available
252
+ const result: ItemStub[] = [];
253
+ const itemsToFetch: ItemDescriptor[] = [];
254
+
255
+ for (const { key, descriptor } of keys) {
256
+ // If we have the full item, use it
257
+ const fullItem = itemsMap.current.get(key);
258
+ if (fullItem) {
259
+ result.push(fullItem);
260
+ continue;
261
+ }
262
+
263
+ // If we have a cached stub, use it
264
+ const cachedStub = stubsMap.current.get(key);
265
+ if (cachedStub) {
266
+ result.push(cachedStub);
267
+ continue;
268
+ }
269
+
270
+ // Otherwise, fetch it
271
+ itemsToFetch.push(descriptor);
272
+ }
273
+
274
+ if (itemsToFetch.length > 0) {
275
+ await fetchStubsFromServer(itemsToFetch);
276
+
277
+ // Add fetched stubs to the result
278
+ for (const descriptor of itemsToFetch) {
279
+ const key = generateKey(descriptor);
280
+ const stub = stubsMap.current.get(key);
281
+ if (stub) {
282
+ result.push(stub);
283
+ }
284
+ }
285
+ }
286
+
287
+ return result;
282
288
  },
283
- [itemsMap.current],
289
+ [fetchStubsFromServer],
284
290
  );
285
291
 
286
292
  const getItem = useCallback(
@@ -291,6 +297,19 @@ export function useItemsRepository(
291
297
  [getItems],
292
298
  );
293
299
 
300
+ const getField = useCallback(
301
+ async (fieldDescriptor: FieldDescriptor): Promise<Field | undefined> => {
302
+ try {
303
+ const item = await getItem(fieldDescriptor.item);
304
+ return item?.fields.find((x) => x.id === fieldDescriptor.fieldId);
305
+ } catch (error) {
306
+ console.error("Error getting field:", error);
307
+ return undefined;
308
+ }
309
+ },
310
+ [getItem],
311
+ );
312
+
294
313
  const updateFieldValue = useCallback(
295
314
  async (
296
315
  field: FieldDescriptor,
@@ -305,7 +324,21 @@ export function useItemsRepository(
305
324
  const fieldToUpdate = item.fields.find((x) => x.id === field.fieldId);
306
325
  if (!fieldToUpdate) return;
307
326
 
308
- itemsMap.current.set(generateKey(field.item), { ...item });
327
+ // Update the field value in the item copy
328
+ const updatedItem = {
329
+ ...item,
330
+ fields: item.fields.map((f) =>
331
+ f.id === field.fieldId
332
+ ? {
333
+ ...f,
334
+ rawValue: rawValue !== undefined ? rawValue : String(value),
335
+ value,
336
+ }
337
+ : f,
338
+ ),
339
+ };
340
+
341
+ itemsMap.current.set(generateKey(field.item), updatedItem);
309
342
 
310
343
  setModifiedFields((prevModifiedFields) => {
311
344
  const val = rawValue !== undefined ? rawValue : value;
@@ -353,7 +386,7 @@ export function useItemsRepository(
353
386
 
354
387
  // setRevision((prev) => prev + 1);
355
388
  },
356
- [itemsMap, fetchItemsFromServer],
389
+ [getItem, setModifiedFields, setLastEditedFields],
357
390
  );
358
391
 
359
392
  const refreshItems = useCallback(
@@ -426,7 +459,7 @@ export function useItemsRepository(
426
459
  items.map((x) => ({ item: x, action: "update" })),
427
460
  );
428
461
  },
429
- [setRevision, stubsMap.current],
462
+ [setRevision, fetchItemsFromServer, setModifiedFields],
430
463
  );
431
464
 
432
465
  const onFieldSaved = useCallback(
@@ -453,7 +486,7 @@ export function useItemsRepository(
453
486
  }
454
487
  triggerItemsChangedEvent([{ item: field.item, action: "update" }]);
455
488
  },
456
- [setModifiedFields],
489
+ [setModifiedFields, refreshItems],
457
490
  );
458
491
 
459
492
  const onItemsDeleted = useCallback(
@@ -473,7 +506,7 @@ export function useItemsRepository(
473
506
  );
474
507
  setRevision((prev) => prev + 1);
475
508
  },
476
- [itemsMap, setRevision],
509
+ [setRevision],
477
510
  );
478
511
 
479
512
  const subscribeItemsChanged = useCallback(
@@ -524,7 +557,10 @@ export function useItemsRepository(
524
557
  getField,
525
558
  revision,
526
559
  updateFieldValue,
560
+ refreshItems,
561
+ onFieldSaved,
527
562
  onItemsDeleted,
563
+ subscribeItemsChanged,
528
564
  clear,
529
565
  ]);
530
566
 
@@ -2,6 +2,7 @@ import { useEditContext } from "../client/editContext";
2
2
 
3
3
  import { ItemDescriptor } from "../pageModel";
4
4
  import { ResultItem } from "../ui/ItemSearch";
5
+ import { ItemList } from "../ui/ItemList";
5
6
 
6
7
  export function BrowseHistory({
7
8
  setHoveredItem,
@@ -17,21 +18,11 @@ export function BrowseHistory({
17
18
  const history = editContext.browseHistory;
18
19
 
19
20
  return (
20
- <div className="overflow-y-auto flex flex-col gap-1 flex-1">
21
- {history
22
- .filter((x, i) => x.language && i < 7)
23
- .map((x) => (
24
- <div
25
- key={x.id + x.language}
26
- className="text-xs cursor-pointer hover:text-gray-400 flex gap-1"
27
- onMouseEnter={() => setHoveredItem(x)}
28
- onMouseLeave={() => setHoveredItem(undefined)}
29
- onClick={() => itemSelected(x)}
30
- >
31
- {x.icon && <img src={x.icon} width="16" height="16" />} {x.path}{" "}
32
- <span className="text-500">({x.language})</span>
33
- </div>
34
- ))}
35
- </div>
21
+ <ItemList
22
+ items={history}
23
+ onItemHover={setHoveredItem}
24
+ onItemClick={itemSelected}
25
+ maxItems={12}
26
+ />
36
27
  );
37
28
  }