@designcrowd/library.brand-page 5.18.1-undo-33 → 5.18.1
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/lib/src/{CreatedOnBrandCrowd-de-de-986OpXvU.js → CreatedOnBrandCrowd-de-de-DFpjIAUv.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CTZuZ366.js → CreatedOnBrandCrowd-en-us-BapIT2xO.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-CHrTgX9-.js → CreatedOnBrandCrowd-es-es-DaSf3Dt6.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-xJAvxQ-x.js → CreatedOnBrandCrowd-fr-ca-Cvgei7yn.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-xJAvxQ-x.js → CreatedOnBrandCrowd-fr-fr-Cvgei7yn.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-lpRIGIcs.js → CreatedOnBrandCrowd-pt-br-Duxd2NmR.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-lpRIGIcs.js → CreatedOnBrandCrowd-pt-pt-Duxd2NmR.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CZuim3ee.js → CreatedOnDcom-de-de-DXwNbruv.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-DG4I5f4s.js → CreatedOnDcom-en-us-CkNGwN-p.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-Hpieh1wX.js → CreatedOnDcom-es-es-Csyf5yhI.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DWr5P-df.js → CreatedOnDcom-fr-ca-GOIUmIdy.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-CHfOyS6V.js → CreatedOnDcom-fr-fr-DF63cL2a.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-bQwCCDDI.js → CreatedOnDcom-pt-br-Ct6FWj7D.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-bo-i_h0N.js → CreatedOnDcom-pt-pt-D22yb8Ci.js} +1 -1
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +0 -10
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +0 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +2 -23
- package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +0 -3
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.js +0 -11
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +2 -7
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +0 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +1 -20
- package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +0 -20
- package/lib/src/brand-page-maker/composables/useEventTracker.js +0 -17
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +2 -41
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +5 -301
- package/lib/src/brand-page-maker/stores/logo-store/logo-store.d.ts +0 -1
- package/lib/src/brand-page-maker/stores/user/user-store.js +0 -1
- package/lib/src/bundled-translations-CnareLWC.js +175 -0
- package/lib/src/bundles/bundled-translations.json +1 -3
- package/lib/src/constants/ui.d.ts +0 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +15 -20
- package/lib/src/css/library.brand-page-brandPage.min.css +16 -20
- package/lib/src/css/library.brand-page-designCom.min.css +15 -20
- package/lib/src/css/library.brand-page-preview.min.css +15 -20
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-DNHBgZyy.js → lib-_iwoSD1Z.js} +34559 -35149
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +2 -87
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +0 -5
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.stories.js +0 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +2 -23
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +0 -16
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +2 -7
- package/lib/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.d.ts +0 -3
- package/lib/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.js +2 -19
- package/lib/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.js +8 -18
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +0 -64
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.js +0 -6
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +0 -19
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.js +0 -11
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +2 -10
- package/lib/src/shared/logo-settings/store/editable-logo-store.d.ts +1 -5
- package/lib/src/shared/logo-settings/store/editable-logo-store.js +0 -31
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +2 -24
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +2 -10
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +2 -23
- package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +0 -16
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +2 -23
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +0 -16
- package/package.json +1 -1
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +0 -11
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +3 -2
- package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +0 -1
- package/src/brand-page-maker/components/cookie-collection/CookieSettings.vue +17 -18
- package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.ts +0 -13
- package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.vue +3 -26
- package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.json +1 -3
- package/src/brand-page-maker/components/save-contact/SaveContact.vue +19 -12
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +0 -10
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +1 -24
- package/src/brand-page-maker/composables/useEventTracker.ts +0 -29
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +0 -335
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +4 -364
- package/src/brand-page-maker/stores/logo-store/logo-store.ts +0 -5
- package/src/brand-page-maker/stores/user/user-store.ts +0 -1
- package/src/bundles/bundled-translations.json +1 -3
- package/src/constants/ui.ts +0 -1
- package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +0 -5
- package/src/shared/content-settings/components/content-settings/ContentSettings.stories.ts +0 -1
- package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.ts +2 -19
- package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.vue +1 -1
- package/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.ts +10 -26
- package/src/shared/content-settings/components/logo-selection-field/LogoSelection.vue +48 -63
- package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +0 -4
- package/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.ts +0 -7
- package/src/shared/content-settings/components/text-field/TextField.vue +0 -1
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +4 -33
- package/src/shared/logo-settings/components/EditLogoSettings.mixin.ts +0 -11
- package/src/shared/logo-settings/store/editable-logo-store.ts +1 -42
- package/lib/src/brand-page-maker/composables/useUndoRedoFlag.d.ts +0 -16
- package/lib/src/brand-page-maker/composables/useUndoRedoFlag.js +0 -22
- package/lib/src/brand-page-maker/stores/history/history-store.d.ts +0 -83
- package/lib/src/brand-page-maker/stores/history/history-store.js +0 -459
- package/lib/src/bundled-translations-DQVbIb4i.js +0 -175
- package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +0 -13
- package/lib/src/metadata-builder/builders/reorder-pages-builder.js +0 -49
- package/src/brand-page-maker/components/quick-link-bar/RedoButton.vue +0 -66
- package/src/brand-page-maker/components/quick-link-bar/UndoButton.vue +0 -66
- package/src/brand-page-maker/components/quick-link-bar/undo-redo-buttons.test.ts +0 -116
- package/src/brand-page-maker/composables/useUndoRedoFlag.ts +0 -26
- package/src/brand-page-maker/stores/history/history-store.test.ts +0 -1088
- package/src/brand-page-maker/stores/history/history-store.ts +0 -621
- package/src/metadata-builder/builders/reorder-pages-builder.ts +0 -80
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
import { defineStore } from 'pinia';
|
|
2
|
-
import { computed, ref, shallowRef, toRaw, watch } from 'vue';
|
|
3
|
-
import { useBrandPageMakerStore } from '../brandpage-maker/brandpage-maker-store';
|
|
4
|
-
import { useBrandPageContainerStore } from '../brandpage-container';
|
|
5
|
-
import { useAiWebsiteBuilderThreadStore } from '../ai-thread/ai-website-builder-thread-store';
|
|
6
|
-
import { useEventTracker } from '../../composables/useEventTracker';
|
|
7
|
-
import { usePageNavigationStore } from '@shared/page-navigation/store/page-navigation-store';
|
|
8
|
-
import { useUiEditableLogoStore } from '@shared/logo-settings/store/editable-logo-store';
|
|
9
|
-
// Monotonic burst — first mutation starts the timer, subsequent mutations
|
|
10
|
-
// don't reset it; max-wait ceiling forces a commit during continuous typing.
|
|
11
|
-
const CAPTURE_DEBOUNCE_MS = 1700;
|
|
12
|
-
const CAPTURE_MAX_WAIT_MS = 10000;
|
|
13
|
-
// FR-12 / FR-13 / FR-13c. 25 MB per-entry accommodates a high-res BYO logo
|
|
14
|
-
// rendered into 4 viewport sections + per-page logo blocks. Oversize
|
|
15
|
-
// entries are skipped with telemetry; 100 MB total evicts oldest
|
|
16
|
-
// non-baseline entries.
|
|
17
|
-
const MAX_ENTRIES = 40;
|
|
18
|
-
const MAX_MEMORY_BYTES = 100 * 1024 * 1024;
|
|
19
|
-
const MAX_ENTRY_BYTES = 25 * 1024 * 1024;
|
|
20
|
-
// Window after baseline during which captured mutations REPLACE the
|
|
21
|
-
// baseline instead of pushing — covers async post-init cascades
|
|
22
|
-
// (template load, router afterEach, layout settle).
|
|
23
|
-
const BASELINE_SETTLE_MS = 2000;
|
|
24
|
-
export const useHistoryStore = defineStore('history', () => {
|
|
25
|
-
const eventTracker = useEventTracker();
|
|
26
|
-
// --- State ---
|
|
27
|
-
// shallowRef keeps the array reactive but prevents Pinia from proxying
|
|
28
|
-
// every entry — combined with stringified snapshots, entries are
|
|
29
|
-
// fully opaque to reactivity traversal.
|
|
30
|
-
const entries = shallowRef([]);
|
|
31
|
-
const currentIndex = ref(-1); // -1 means "no baseline yet"
|
|
32
|
-
const isApplyingSnapshot = ref(false);
|
|
33
|
-
const baselineCaptured = ref(false);
|
|
34
|
-
const watcherInstalled = ref(false);
|
|
35
|
-
let baselineCapturedAt = 0;
|
|
36
|
-
// --- Getters ---
|
|
37
|
-
const canUndo = computed(() => currentIndex.value > 0);
|
|
38
|
-
const canRedo = computed(() => currentIndex.value >= 0 && currentIndex.value < entries.value.length - 1);
|
|
39
|
-
// --- Internals ---
|
|
40
|
-
// Pre-purchase only (matches the save-path strip in
|
|
41
|
-
// `brand-page-container-store.ts:805`). Each rendered base64 is ~2-4 MB
|
|
42
|
-
// across 6-8 placements, so unstripped snapshots bump the 25 MB cap.
|
|
43
|
-
// `_syncLogoSettingsFromSnapshot` repopulates on undo.
|
|
44
|
-
function stripLogoBase64IfPrePurchase(data) {
|
|
45
|
-
try {
|
|
46
|
-
if (!useBrandPageContainerStore().isPrePurchase)
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (data.pageHeaderDesktop) {
|
|
53
|
-
data.pageHeaderDesktop.logoImageBase64Url = undefined;
|
|
54
|
-
}
|
|
55
|
-
if (data.pageHeaderMobile) {
|
|
56
|
-
data.pageHeaderMobile.logoImageBase64Url = undefined;
|
|
57
|
-
}
|
|
58
|
-
if (data.pageFooterDesktop) {
|
|
59
|
-
data.pageFooterDesktop.logoImageBase64Url = undefined;
|
|
60
|
-
}
|
|
61
|
-
if (data.pageFooterMobile) {
|
|
62
|
-
data.pageFooterMobile.logoImageBase64Url = undefined;
|
|
63
|
-
}
|
|
64
|
-
if (Array.isArray(data.pages)) {
|
|
65
|
-
for (const page of data.pages) {
|
|
66
|
-
if (!Array.isArray(page?.contents))
|
|
67
|
-
continue;
|
|
68
|
-
for (const content of page.contents) {
|
|
69
|
-
if (content &&
|
|
70
|
-
typeof content === 'object' &&
|
|
71
|
-
'logoImageBase64Url' in content) {
|
|
72
|
-
content.logoImageBase64Url = undefined;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function serializeCurrentSnapshot() {
|
|
79
|
-
try {
|
|
80
|
-
const cloned = JSON.parse(JSON.stringify(toRaw(useBrandPageMakerStore().brandPageData)));
|
|
81
|
-
stripLogoBase64IfPrePurchase(cloned);
|
|
82
|
-
return JSON.stringify(cloned);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.error('history-store: failed to serialize snapshot', error);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function getCurrentActivePageId() {
|
|
90
|
-
return usePageNavigationStore().activePageId ?? '';
|
|
91
|
-
}
|
|
92
|
-
function serializeCurrentLogoEdits() {
|
|
93
|
-
try {
|
|
94
|
-
const editableLogoStore = useUiEditableLogoStore();
|
|
95
|
-
return JSON.stringify(toRaw(editableLogoStore.logoEdits));
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
console.error('history-store: failed to serialize logoEdits', error);
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// One-shot flag set by hydration write sites (TipTap mount, lazy
|
|
103
|
-
// paymentConfig fill, stale-page-ref clear, ContentSettings RTE
|
|
104
|
-
// rewrap) before their imperative write. The watcher's next flush
|
|
105
|
-
// consumes the flag and merges the snapshot into the current head
|
|
106
|
-
// instead of pushing — preventing phantom undo entries on first
|
|
107
|
-
// sidebar open / section click. Cleared on apply and reset.
|
|
108
|
-
const mergeNextFlush = ref(false);
|
|
109
|
-
// Returns 'noop' when no-change vs current head, 'oversized' when over
|
|
110
|
-
// MAX_ENTRY_BYTES, else 'committed'. Truncates the redo tail, caps at
|
|
111
|
-
// MAX_ENTRIES, evicts oldest non-baseline to stay under MAX_MEMORY_BYTES.
|
|
112
|
-
// Baseline (index 0) is never evicted.
|
|
113
|
-
function commitEntry(args) {
|
|
114
|
-
const current = entries.value[currentIndex.value];
|
|
115
|
-
// Compare both snapshot AND logoEdits sidecar — pre-purchase logo
|
|
116
|
-
// edits don't change brandPageData (base64 is stripped) so the
|
|
117
|
-
// sidecar is the only signal that distinguishes them.
|
|
118
|
-
if (current &&
|
|
119
|
-
current.snapshot === args.snapshot &&
|
|
120
|
-
current.logoEdits === args.logoEdits) {
|
|
121
|
-
return 'noop';
|
|
122
|
-
}
|
|
123
|
-
if (args.snapshot.length > MAX_ENTRY_BYTES) {
|
|
124
|
-
eventTracker.sendHistoryOversizedEvent({
|
|
125
|
-
attempted_bytes: args.snapshot.length,
|
|
126
|
-
source: args.source,
|
|
127
|
-
});
|
|
128
|
-
return 'oversized';
|
|
129
|
-
}
|
|
130
|
-
const entry = {
|
|
131
|
-
snapshot: args.snapshot,
|
|
132
|
-
timestamp: new Date().toISOString(),
|
|
133
|
-
source: args.source,
|
|
134
|
-
actor: args.actor,
|
|
135
|
-
label: args.label,
|
|
136
|
-
sizeBytes: args.snapshot.length,
|
|
137
|
-
activePageId: args.activePageId,
|
|
138
|
-
logoEdits: args.logoEdits,
|
|
139
|
-
};
|
|
140
|
-
// FR-11: truncate redo tail, then append.
|
|
141
|
-
const next = entries.value.slice(0, currentIndex.value + 1);
|
|
142
|
-
next.push(entry);
|
|
143
|
-
// FR-12: 40-entry cap. Drop oldest non-baseline (index 1) if we'd exceed.
|
|
144
|
-
let didEvict = false;
|
|
145
|
-
while (next.length > MAX_ENTRIES && next.length > 1) {
|
|
146
|
-
next.splice(1, 1);
|
|
147
|
-
didEvict = true;
|
|
148
|
-
}
|
|
149
|
-
// FR-13: keep total serialized size under 100 MB. Drop oldest
|
|
150
|
-
// non-baseline repeatedly until under budget. Baseline alone can exceed
|
|
151
|
-
// the budget on a very large page — in that pathological case we stop
|
|
152
|
-
// evicting at length 2 (baseline + newest) rather than dropping the
|
|
153
|
-
// baseline.
|
|
154
|
-
let totalBytes = next.reduce((sum, e) => sum + e.sizeBytes, 0);
|
|
155
|
-
while (totalBytes > MAX_MEMORY_BYTES && next.length > 2) {
|
|
156
|
-
totalBytes -= next[1].sizeBytes;
|
|
157
|
-
next.splice(1, 1);
|
|
158
|
-
didEvict = true;
|
|
159
|
-
}
|
|
160
|
-
entries.value = next;
|
|
161
|
-
currentIndex.value = next.length - 1;
|
|
162
|
-
if (didEvict) {
|
|
163
|
-
eventTracker.sendHistoryCappedEvent({
|
|
164
|
-
entries_count: next.length,
|
|
165
|
-
size_bytes: totalBytes,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return 'committed';
|
|
169
|
-
}
|
|
170
|
-
// Hoisted so force-commit paths (template switch, AI apply) can cancel
|
|
171
|
-
// any pending auto-capture that would otherwise duplicate their entry.
|
|
172
|
-
let idleTimerId = null;
|
|
173
|
-
let maxWaitTimerId = null;
|
|
174
|
-
function cancelPendingAutoCapture() {
|
|
175
|
-
if (idleTimerId !== null) {
|
|
176
|
-
clearTimeout(idleTimerId);
|
|
177
|
-
idleTimerId = null;
|
|
178
|
-
}
|
|
179
|
-
if (maxWaitTimerId !== null) {
|
|
180
|
-
clearTimeout(maxWaitTimerId);
|
|
181
|
-
maxWaitTimerId = null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function installWatcher() {
|
|
185
|
-
if (watcherInstalled.value)
|
|
186
|
-
return;
|
|
187
|
-
const flush = () => {
|
|
188
|
-
cancelPendingAutoCapture();
|
|
189
|
-
if (isApplyingSnapshot.value)
|
|
190
|
-
return;
|
|
191
|
-
if (!baselineCaptured.value)
|
|
192
|
-
return;
|
|
193
|
-
const snapshot = serializeCurrentSnapshot();
|
|
194
|
-
if (!snapshot)
|
|
195
|
-
return;
|
|
196
|
-
const activePageId = getCurrentActivePageId();
|
|
197
|
-
const logoEdits = serializeCurrentLogoEdits();
|
|
198
|
-
// Settle window: fold post-init reactive cascades back into the
|
|
199
|
-
// baseline so undo doesn't light up before the user has done
|
|
200
|
-
// anything.
|
|
201
|
-
const inSettleWindow = currentIndex.value === 0 &&
|
|
202
|
-
Date.now() - baselineCapturedAt < BASELINE_SETTLE_MS;
|
|
203
|
-
// Merge: a hydration write site flagged this flush — replace the
|
|
204
|
-
// current head in place instead of pushing. Consumed once.
|
|
205
|
-
const shouldMerge = mergeNextFlush.value;
|
|
206
|
-
mergeNextFlush.value = false;
|
|
207
|
-
if (inSettleWindow) {
|
|
208
|
-
entries.value = [
|
|
209
|
-
{
|
|
210
|
-
...entries.value[0],
|
|
211
|
-
snapshot,
|
|
212
|
-
timestamp: new Date().toISOString(),
|
|
213
|
-
sizeBytes: snapshot.length,
|
|
214
|
-
activePageId,
|
|
215
|
-
logoEdits,
|
|
216
|
-
},
|
|
217
|
-
];
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (shouldMerge && entries.value[currentIndex.value]) {
|
|
221
|
-
const next = entries.value.slice();
|
|
222
|
-
next[currentIndex.value] = {
|
|
223
|
-
...next[currentIndex.value],
|
|
224
|
-
snapshot,
|
|
225
|
-
timestamp: new Date().toISOString(),
|
|
226
|
-
sizeBytes: snapshot.length,
|
|
227
|
-
activePageId,
|
|
228
|
-
logoEdits,
|
|
229
|
-
};
|
|
230
|
-
entries.value = next;
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
commitEntry({
|
|
234
|
-
snapshot,
|
|
235
|
-
source: 'manual_edit',
|
|
236
|
-
actor: 'user',
|
|
237
|
-
activePageId,
|
|
238
|
-
logoEdits,
|
|
239
|
-
});
|
|
240
|
-
};
|
|
241
|
-
// Monotonic — first mutation arms; subsequent ones inside the window
|
|
242
|
-
// don't reset. Gated on isApplyingSnapshot so undo/redo don't leave
|
|
243
|
-
// a stale timer ticking.
|
|
244
|
-
const scheduleAutoCapture = () => {
|
|
245
|
-
if (isApplyingSnapshot.value)
|
|
246
|
-
return;
|
|
247
|
-
if (idleTimerId !== null)
|
|
248
|
-
return;
|
|
249
|
-
idleTimerId = setTimeout(flush, CAPTURE_DEBOUNCE_MS);
|
|
250
|
-
maxWaitTimerId = setTimeout(flush, CAPTURE_MAX_WAIT_MS);
|
|
251
|
-
};
|
|
252
|
-
watch(() => useBrandPageMakerStore().brandPageData, scheduleAutoCapture, {
|
|
253
|
-
deep: true,
|
|
254
|
-
});
|
|
255
|
-
watcherInstalled.value = true;
|
|
256
|
-
}
|
|
257
|
-
// --- Actions ---
|
|
258
|
-
// Called once per session by BrandPageMaker.mixin.ts after
|
|
259
|
-
// setIsBrandPageLoaded(true). Installs the watcher.
|
|
260
|
-
function captureBaseline() {
|
|
261
|
-
if (baselineCaptured.value)
|
|
262
|
-
return;
|
|
263
|
-
installWatcher();
|
|
264
|
-
const snapshot = serializeCurrentSnapshot();
|
|
265
|
-
if (!snapshot)
|
|
266
|
-
return;
|
|
267
|
-
entries.value = [
|
|
268
|
-
{
|
|
269
|
-
snapshot,
|
|
270
|
-
timestamp: new Date().toISOString(),
|
|
271
|
-
source: 'manual_edit',
|
|
272
|
-
actor: 'user',
|
|
273
|
-
label: 'baseline',
|
|
274
|
-
sizeBytes: snapshot.length,
|
|
275
|
-
activePageId: getCurrentActivePageId(),
|
|
276
|
-
logoEdits: serializeCurrentLogoEdits(),
|
|
277
|
-
},
|
|
278
|
-
];
|
|
279
|
-
currentIndex.value = 0;
|
|
280
|
-
baselineCaptured.value = true;
|
|
281
|
-
baselineCapturedAt = Date.now();
|
|
282
|
-
}
|
|
283
|
-
// Force-commit for non-watcher call sites (template switch, AI apply).
|
|
284
|
-
function capture(args) {
|
|
285
|
-
if (isApplyingSnapshot.value)
|
|
286
|
-
return 'skipped';
|
|
287
|
-
if (!baselineCaptured.value)
|
|
288
|
-
return 'skipped';
|
|
289
|
-
const snapshot = serializeCurrentSnapshot();
|
|
290
|
-
if (!snapshot)
|
|
291
|
-
return 'skipped';
|
|
292
|
-
return commitEntry({
|
|
293
|
-
snapshot,
|
|
294
|
-
activePageId: getCurrentActivePageId(),
|
|
295
|
-
logoEdits: serializeCurrentLogoEdits(),
|
|
296
|
-
...args,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
// Cancel pending auto-capture so the forced entry doesn't duplicate
|
|
300
|
-
// with the watcher's eventual flush.
|
|
301
|
-
function captureTemplateSwitch(label) {
|
|
302
|
-
cancelPendingAutoCapture();
|
|
303
|
-
return capture({ source: 'template_switch', actor: 'user', label });
|
|
304
|
-
}
|
|
305
|
-
function captureAiChangeset(label) {
|
|
306
|
-
cancelPendingAutoCapture();
|
|
307
|
-
return capture({ source: 'ai_changeset', actor: 'ai_builder', label });
|
|
308
|
-
}
|
|
309
|
-
// Active-page resolution priority:
|
|
310
|
-
// 1. Diff `cameFromIndex` against target — pages present in target
|
|
311
|
-
// but absent from source are being restored/added; navigate there
|
|
312
|
-
// (undo-of-delete, redo-of-add).
|
|
313
|
-
// 2. Walk back from targetIndex for the closest entry whose
|
|
314
|
-
// activePageId still exists (undo-of-add and non-page edits).
|
|
315
|
-
async function applySnapshotString(snapshotStr, targetIndex, cameFromIndex) {
|
|
316
|
-
const brandPageMakerStore = useBrandPageMakerStore();
|
|
317
|
-
let snapshot;
|
|
318
|
-
try {
|
|
319
|
-
snapshot = JSON.parse(snapshotStr);
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
console.error('history-store: failed to parse snapshot', error);
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const restoredPageIds = new Set();
|
|
326
|
-
snapshot.pages?.forEach((p) => {
|
|
327
|
-
if (p?.id)
|
|
328
|
-
restoredPageIds.add(p.id);
|
|
329
|
-
});
|
|
330
|
-
let resolvedActivePageId;
|
|
331
|
-
// Step 1: diff cameFromIndex → first page absent from source wins.
|
|
332
|
-
const sourceEntry = entries.value[cameFromIndex];
|
|
333
|
-
if (sourceEntry) {
|
|
334
|
-
const sourcePageIds = new Set();
|
|
335
|
-
try {
|
|
336
|
-
const sourceSnap = JSON.parse(sourceEntry.snapshot);
|
|
337
|
-
sourceSnap.pages?.forEach((p) => {
|
|
338
|
-
if (p?.id)
|
|
339
|
-
sourcePageIds.add(p.id);
|
|
340
|
-
});
|
|
341
|
-
for (const p of snapshot.pages ?? []) {
|
|
342
|
-
if (p?.id && !sourcePageIds.has(p.id)) {
|
|
343
|
-
resolvedActivePageId = p.id;
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
catch {
|
|
349
|
-
// Source snapshot unparseable — fall through to walk-back.
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
// Step 2: walk-back fallback for non-page-structural edits.
|
|
353
|
-
if (!resolvedActivePageId) {
|
|
354
|
-
for (let i = targetIndex; i >= 0; i--) {
|
|
355
|
-
const candidate = entries.value[i]?.activePageId;
|
|
356
|
-
if (candidate && restoredPageIds.has(candidate)) {
|
|
357
|
-
resolvedActivePageId = candidate;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const targetEntry = entries.value[targetIndex];
|
|
363
|
-
isApplyingSnapshot.value = true;
|
|
364
|
-
// Clear any unconsumed merge flag — a stale flag would fold the
|
|
365
|
-
// post-apply tick into the restored entry and erase the redo.
|
|
366
|
-
mergeNextFlush.value = false;
|
|
367
|
-
cancelPendingAutoCapture();
|
|
368
|
-
try {
|
|
369
|
-
await brandPageMakerStore.applyHistorySnapshot(snapshot, resolvedActivePageId, targetEntry?.logoEdits);
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
isApplyingSnapshot.value = false;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
async function undo() {
|
|
376
|
-
if (!canUndo.value)
|
|
377
|
-
return;
|
|
378
|
-
const aiThreadStore = useAiWebsiteBuilderThreadStore();
|
|
379
|
-
if (aiThreadStore.isProcessing)
|
|
380
|
-
return;
|
|
381
|
-
// Actor of the entry being undone (the one we're leaving).
|
|
382
|
-
const undoneActor = entries.value[currentIndex.value]?.actor;
|
|
383
|
-
const cameFromIndex = currentIndex.value;
|
|
384
|
-
currentIndex.value -= 1;
|
|
385
|
-
const target = entries.value[currentIndex.value];
|
|
386
|
-
if (target) {
|
|
387
|
-
await applySnapshotString(target.snapshot, currentIndex.value, cameFromIndex);
|
|
388
|
-
}
|
|
389
|
-
eventTracker.sendUndoEvent({
|
|
390
|
-
entries_count: entries.value.length,
|
|
391
|
-
actor: undoneActor,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
async function redo() {
|
|
395
|
-
if (!canRedo.value)
|
|
396
|
-
return;
|
|
397
|
-
const aiThreadStore = useAiWebsiteBuilderThreadStore();
|
|
398
|
-
if (aiThreadStore.isProcessing)
|
|
399
|
-
return;
|
|
400
|
-
const cameFromIndex = currentIndex.value;
|
|
401
|
-
currentIndex.value += 1;
|
|
402
|
-
const target = entries.value[currentIndex.value];
|
|
403
|
-
if (target) {
|
|
404
|
-
await applySnapshotString(target.snapshot, currentIndex.value, cameFromIndex);
|
|
405
|
-
}
|
|
406
|
-
eventTracker.sendRedoEvent({
|
|
407
|
-
entries_count: entries.value.length,
|
|
408
|
-
actor: target?.actor,
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
// PRD §3: undo history resets on save/publish. Called by
|
|
412
|
-
// brand-page-container-store.saveBrandPage. No-ops when no baseline
|
|
413
|
-
// exists — unflagged sessions never installed the watcher.
|
|
414
|
-
function reset() {
|
|
415
|
-
if (!baselineCaptured.value)
|
|
416
|
-
return;
|
|
417
|
-
cancelPendingAutoCapture();
|
|
418
|
-
mergeNextFlush.value = false;
|
|
419
|
-
entries.value = [];
|
|
420
|
-
currentIndex.value = -1;
|
|
421
|
-
baselineCaptured.value = false;
|
|
422
|
-
baselineCapturedAt = 0;
|
|
423
|
-
captureBaseline();
|
|
424
|
-
}
|
|
425
|
-
// Hydration write sites call this immediately before their imperative
|
|
426
|
-
// mutation; the next watcher flush merges into the current head
|
|
427
|
-
// instead of pushing. Idempotent and one-shot.
|
|
428
|
-
function mergeNextMutation() {
|
|
429
|
-
mergeNextFlush.value = true;
|
|
430
|
-
}
|
|
431
|
-
// --- Instrumentation (NFR-04a) ---
|
|
432
|
-
function getCurrentSizeBytes() {
|
|
433
|
-
return entries.value.reduce((sum, e) => sum + e.sizeBytes, 0);
|
|
434
|
-
}
|
|
435
|
-
function getCurrentEntryCount() {
|
|
436
|
-
return entries.value.length;
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
// State
|
|
440
|
-
entries,
|
|
441
|
-
currentIndex,
|
|
442
|
-
isApplyingSnapshot,
|
|
443
|
-
baselineCaptured,
|
|
444
|
-
// Getters
|
|
445
|
-
canUndo,
|
|
446
|
-
canRedo,
|
|
447
|
-
// Actions
|
|
448
|
-
captureBaseline,
|
|
449
|
-
capture,
|
|
450
|
-
captureTemplateSwitch,
|
|
451
|
-
captureAiChangeset,
|
|
452
|
-
mergeNextMutation,
|
|
453
|
-
undo,
|
|
454
|
-
redo,
|
|
455
|
-
reset,
|
|
456
|
-
getCurrentSizeBytes,
|
|
457
|
-
getCurrentEntryCount,
|
|
458
|
-
};
|
|
459
|
-
});
|