@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.
- package/dist/components/ActionButton.js +2 -2
- package/dist/components/ActionButton.js.map +1 -1
- package/dist/components/SimpleLanguageSelector.js +3 -1
- package/dist/components/SimpleLanguageSelector.js.map +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/config/config.js +1 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +1 -1
- package/dist/editor/ContextMenu.js +0 -1
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/Editor.js +9 -3
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FieldListField.js +16 -24
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ImageEditButton.js +1 -1
- package/dist/editor/ImageEditButton.js.map +1 -1
- package/dist/editor/ImageEditor.js +1 -1
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/MainLayout.js +2 -2
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/Terminal.js +1 -1
- package/dist/editor/Terminal.js.map +1 -1
- package/dist/editor/Titlebar.js +0 -1
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.d.ts +26 -0
- package/dist/editor/ai/AgentCostDisplay.js +65 -0
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -0
- package/dist/editor/ai/Agents.js +59 -15
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiPromptPopover.d.ts +7 -0
- package/dist/editor/ai/AiPromptPopover.js +111 -0
- package/dist/editor/ai/AiPromptPopover.js.map +1 -0
- package/dist/editor/ai/AiResponseMessage.d.ts +1 -0
- package/dist/editor/ai/AiResponseMessage.js +101 -23
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.d.ts +15 -1
- package/dist/editor/ai/AiTerminal.js +379 -48
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/ai/editorAiContext.d.ts +0 -1
- package/dist/editor/ai/editorAiContext.js +0 -2
- package/dist/editor/ai/editorAiContext.js.map +1 -1
- package/dist/editor/client/EditorClient.d.ts +3 -2
- package/dist/editor/client/EditorClient.js +326 -68
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +6 -4
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/fieldModificationStore.d.ts +19 -0
- package/dist/editor/client/fieldModificationStore.js +125 -0
- package/dist/editor/client/fieldModificationStore.js.map +1 -0
- package/dist/editor/client/itemsRepository.d.ts +1 -1
- package/dist/editor/client/itemsRepository.js +38 -28
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -0
- package/dist/editor/client/operations.js +39 -31
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +5 -3
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/component-designer/aiContext.js +0 -2
- package/dist/editor/component-designer/aiContext.js.map +1 -1
- package/dist/editor/field-types/DropLinkEditor.js +1 -1
- package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +5 -7
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +5 -7
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +5 -7
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/hooks/useEditorSettings.d.ts +17 -0
- package/dist/editor/hooks/useEditorSettings.js +61 -0
- package/dist/editor/hooks/useEditorSettings.js.map +1 -0
- package/dist/editor/menubar/ItemActionsMenu.js +2 -2
- package/dist/editor/menubar/ItemActionsMenu.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +1 -1
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/InsertControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/InsertControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldEditedIndicators.js +4 -3
- package/dist/editor/page-editor-chrome/FieldEditedIndicators.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +9 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +0 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +1 -1
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.js +9 -8
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +7 -1
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.js +40 -6
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/reviews/Comment.js +7 -6
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +84 -12
- package/dist/editor/services/agentService.js +256 -15
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +17 -3
- package/dist/editor/services/aiService.js +5 -3
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contextService.js +0 -1
- package/dist/editor/services/contextService.js.map +1 -1
- package/dist/editor/services/systemService.d.ts +2 -1
- package/dist/editor/services/systemService.js +3 -0
- package/dist/editor/services/systemService.js.map +1 -1
- package/dist/editor/sidebar/ComponentPalette.js +1 -1
- package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
- package/dist/editor/sidebar/EditHistory.js +2 -2
- package/dist/editor/sidebar/EditHistory.js.map +1 -1
- package/dist/editor/sidebar/GraphQL.d.ts +1 -0
- package/dist/editor/sidebar/GraphQL.js +8 -2
- package/dist/editor/sidebar/GraphQL.js.map +1 -1
- package/dist/editor/sidebar/MainContentTree.js +1 -1
- package/dist/editor/sidebar/MainContentTree.js.map +1 -1
- package/dist/editor/sidebar/SEOInfo.js +1 -1
- package/dist/editor/sidebar/SEOInfo.js.map +1 -1
- package/dist/editor/sidebar/ViewSelector.d.ts +4 -1
- package/dist/editor/sidebar/ViewSelector.js +64 -48
- package/dist/editor/sidebar/ViewSelector.js.map +1 -1
- package/dist/editor/ui/PerfectTree.js +2 -11
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.d.ts +2 -0
- package/dist/editor/ui/SimpleIconButton.js +8 -4
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/page-wizard/steps/CollectStep.js +1 -1
- package/dist/page-wizard/steps/CollectStep.js.map +1 -1
- package/dist/page-wizard/steps/StructureStep.js +1 -1
- package/dist/page-wizard/steps/StructureStep.js.map +1 -1
- package/dist/page-wizard/steps/TranslateStep.js +233 -18
- package/dist/page-wizard/steps/TranslateStep.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/RecentPages.js +1 -13
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/splash-screen/SplashScreen.js +1 -1
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +88 -3
- package/dist/types.d.ts +6 -0
- package/package.json +2 -2
- package/src/components/ActionButton.tsx +3 -2
- package/src/components/SimpleLanguageSelector.tsx +6 -1
- package/src/config/config.tsx +1 -1
- package/src/config/types.ts +1 -1
- package/src/editor/ContextMenu.tsx +0 -3
- package/src/editor/Editor.tsx +11 -3
- package/src/editor/FieldListField.tsx +22 -31
- package/src/editor/ImageEditButton.tsx +1 -0
- package/src/editor/ImageEditor.tsx +1 -0
- package/src/editor/MainLayout.tsx +2 -2
- package/src/editor/Terminal.tsx +1 -1
- package/src/editor/Titlebar.tsx +0 -2
- package/src/editor/ai/AgentCostDisplay.tsx +237 -0
- package/src/editor/ai/Agents.tsx +69 -20
- package/src/editor/ai/AiPromptPopover.tsx +209 -0
- package/src/editor/ai/AiResponseMessage.tsx +201 -60
- package/src/editor/ai/AiTerminal.tsx +502 -71
- package/src/editor/ai/editorAiContext.ts +0 -3
- package/src/editor/client/EditorClient.tsx +409 -117
- package/src/editor/client/editContext.ts +7 -5
- package/src/editor/client/fieldModificationStore.ts +196 -0
- package/src/editor/client/itemsRepository.ts +41 -31
- package/src/editor/client/operations.ts +95 -76
- package/src/editor/commands/componentCommands.tsx +9 -3
- package/src/editor/commands/itemCommands.tsx +0 -1
- package/src/editor/component-designer/aiContext.ts +0 -2
- package/src/editor/field-types/DropLinkEditor.tsx +1 -1
- package/src/editor/field-types/MultiLineText.tsx +9 -9
- package/src/editor/field-types/RichTextEditorComponent.tsx +8 -8
- package/src/editor/field-types/SingleLineText.tsx +9 -9
- package/src/editor/hooks/useEditorSettings.ts +68 -0
- package/src/editor/menubar/ItemActionsMenu.tsx +3 -2
- package/src/editor/menubar/PageSelector.tsx +1 -1
- package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
- package/src/editor/menubar/toolbar-sections/InsertControls.tsx +1 -0
- package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +2 -0
- package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +2 -0
- package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +4 -3
- package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
- package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +0 -1
- package/src/editor/page-viewer/EditorForm.tsx +1 -0
- package/src/editor/page-viewer/PageViewer.tsx +9 -8
- package/src/editor/page-viewer/PageViewerFrame.tsx +7 -1
- package/src/editor/page-viewer/pageViewContext.ts +40 -5
- package/src/editor/reviews/Comment.tsx +7 -7
- package/src/editor/services/agentService.ts +405 -31
- package/src/editor/services/aiService.ts +22 -5
- package/src/editor/services/contextService.ts +0 -1
- package/src/editor/services/systemService.ts +7 -1
- package/src/editor/sidebar/ComponentPalette.tsx +4 -1
- package/src/editor/sidebar/EditHistory.tsx +2 -0
- package/src/editor/sidebar/GraphQL.tsx +19 -7
- package/src/editor/sidebar/MainContentTree.tsx +1 -1
- package/src/editor/sidebar/SEOInfo.tsx +1 -1
- package/src/editor/sidebar/ViewSelector.tsx +80 -64
- package/src/editor/ui/PerfectTree.tsx +2 -18
- package/src/editor/ui/SimpleIconButton.tsx +56 -38
- package/src/index.ts +2 -0
- package/src/page-wizard/steps/CollectStep.tsx +0 -2
- package/src/page-wizard/steps/StructureStep.tsx +3 -0
- package/src/page-wizard/steps/TranslateStep.tsx +473 -62
- package/src/revision.ts +2 -2
- package/src/splash-screen/RecentPages.tsx +0 -14
- package/src/splash-screen/SplashScreen.tsx +3 -2
- package/src/types.ts +7 -0
- package/dist/editor/ai/AiPopup.d.ts +0 -10
- package/dist/editor/ai/AiPopup.js +0 -23
- package/dist/editor/ai/AiPopup.js.map +0 -1
- package/dist/editor/ai/AiToolCall.d.ts +0 -5
- package/dist/editor/ai/AiToolCall.js +0 -28
- package/dist/editor/ai/AiToolCall.js.map +0 -1
- package/src/editor/ai/AiPopup.tsx +0 -59
- 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
|
-
|
|
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
|
-
|
|
408
|
-
...
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
427
|
-
|
|
431
|
+
// Update the field-specific store for optimized subscriptions
|
|
432
|
+
fieldModificationStore.updateField(updatedField);
|
|
428
433
|
|
|
429
|
-
|
|
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,
|
|
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(
|
|
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
|
-
//
|
|
534
|
-
//
|
|
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
|
}
|