@designcrowd/library.brand-page 6.0.6-undo-2 → 6.0.6-undo-4
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-BSFTp6yl.js → CreatedOnBrandCrowd-de-de-BqD7lX6T.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-rPQrOY1L.js → CreatedOnBrandCrowd-en-us-BXFZ-Vv_.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-awd8-QFa.js → CreatedOnBrandCrowd-es-es-qTAVOIsd.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CwJ4J7Ow.js → CreatedOnBrandCrowd-fr-ca-LVZ7sAKH.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CwJ4J7Ow.js → CreatedOnBrandCrowd-fr-fr-LVZ7sAKH.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BXYfTLlD.js → CreatedOnBrandCrowd-pt-br-TSeJN2wb.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BXYfTLlD.js → CreatedOnBrandCrowd-pt-pt-TSeJN2wb.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-kX6u1lNK.js → CreatedOnDcom-de-de-BLLcDw_4.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Bkq5sgFs.js → CreatedOnDcom-en-us-BVdWxcIs.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-zCAC9ujj.js → CreatedOnDcom-es-es-BZRzR4GZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BYz6Wzg5.js → CreatedOnDcom-fr-ca-B97f7ekB.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-CN-wAaSH.js → CreatedOnDcom-fr-fr-CNWS12Rb.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BrK4Ngg-.js → CreatedOnDcom-pt-br-CsASsHeV.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-AfI_g7xJ.js → CreatedOnDcom-pt-pt-CsYqjPls.js} +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +5 -5
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +6 -6
- package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +23 -13
- package/lib/src/brand-page-maker/stores/history/history-store.d.ts +1 -1
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-BYFWAvIq.js → lib-BNqCRgBN.js} +15895 -15862
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +29 -0
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +6 -6
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +5 -5
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +5 -5
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +5 -5
- package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +5 -5
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +0 -67
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.js +4 -6
- package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -0
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +19 -0
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +5 -0
- package/lib/src/shared/content-settings/models.d.ts +11 -0
- package/lib/src/shared/content-settings/store/content-settings-store.d.ts +5 -5
- package/lib/src/shared/content-settings/store/content-settings-store.js +39 -5
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +5 -5
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +5 -5
- package/package.json +1 -1
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +24 -15
- package/src/shared/content-settings/components/GenericTextInput.mixin.test.ts +119 -0
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +34 -0
- package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +10 -5
- package/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.ts +4 -7
- package/src/shared/content-settings/components/text-field/TextField.vue +2 -1
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.test.ts +149 -0
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +26 -0
- package/src/shared/content-settings/components/textarea-field/TextareaField.vue +2 -1
- package/src/shared/content-settings/models.ts +11 -0
- package/src/shared/content-settings/store/content-settings-store.test.ts +137 -0
- package/src/shared/content-settings/store/content-settings-store.ts +45 -0
|
@@ -4,6 +4,7 @@ import { RESERVED_FIELD_NAMES } from '../constants';
|
|
|
4
4
|
import { UI_EVENT_SOURCES } from '../../models';
|
|
5
5
|
import { CONTENT_SETTINGS_DISPLAY_MODES, ITEM_CATEGORIES, ITEM_CATEGORY_NAMES, } from '@brand-page-maker/models';
|
|
6
6
|
import { useUiStore, useBrandPageMakerUserStore, useBrandPageMakerStore, useVideoStore, } from '@brand-page-maker/stores';
|
|
7
|
+
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
7
8
|
import { useUiSideBarStore } from '../../side-bar/store';
|
|
8
9
|
import { createDefaultContentItemGroupState, createDefaultContentItemState, } from '../../navigation-style-settings/store';
|
|
9
10
|
import { adminValidatorsDict, dummyValidator, validatorDict, } from '@brand-page-maker/validators';
|
|
@@ -210,6 +211,15 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
210
211
|
if (!item) {
|
|
211
212
|
return;
|
|
212
213
|
}
|
|
214
|
+
// Hydration write-backs (RTE canonical HTML on mount, lazy config
|
|
215
|
+
// hydration, stale-id clears) fold into the current undo head instead
|
|
216
|
+
// of pushing an entry. Arming here — same tick as the write — is what
|
|
217
|
+
// makes the merge reliable: the flag travelled with the payload
|
|
218
|
+
// through the debounced field chain, so emit-time arming would lapse
|
|
219
|
+
// before the write lands (BP-5810).
|
|
220
|
+
if (payload.isHydrationWrite) {
|
|
221
|
+
useHistoryStore().mergeNextMutation();
|
|
222
|
+
}
|
|
213
223
|
item.settings.content[payload.name] = payload.value;
|
|
214
224
|
this.validateItem({
|
|
215
225
|
id: payload.id,
|
|
@@ -217,29 +227,45 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
217
227
|
});
|
|
218
228
|
this.onContentChanged();
|
|
219
229
|
},
|
|
220
|
-
updateMultipleItemValues({ id, name, value, }) {
|
|
230
|
+
updateMultipleItemValues({ id, name, value, isHydrationWrite, }) {
|
|
221
231
|
const item = this.items.find((i) => i.id === id);
|
|
222
232
|
if (!item) {
|
|
223
233
|
return;
|
|
224
234
|
}
|
|
235
|
+
// Same hydration-write contract as updateItemValue.
|
|
236
|
+
if (isHydrationWrite) {
|
|
237
|
+
useHistoryStore().mergeNextMutation();
|
|
238
|
+
}
|
|
225
239
|
item.settings.content = vueDeepMerge(item.settings.content, value);
|
|
226
240
|
this.validateItem({ id, name });
|
|
227
241
|
this.onContentChanged();
|
|
228
242
|
},
|
|
229
|
-
updateTextStyleType({ id, name, value, }) {
|
|
243
|
+
updateTextStyleType({ id, name, value, isHydrationWrite, }) {
|
|
230
244
|
const item = this.items.find((i) => i.id === id);
|
|
231
245
|
if (!item) {
|
|
232
246
|
return;
|
|
233
247
|
}
|
|
248
|
+
// Same hydration-write contract as updateItemValue.
|
|
249
|
+
if (isHydrationWrite) {
|
|
250
|
+
useHistoryStore().mergeNextMutation();
|
|
251
|
+
}
|
|
234
252
|
item.settings.content[`${name}Style`].textStyleType = vueDeepMerge(item.settings.content, value);
|
|
235
253
|
this.validateItem({ id, name });
|
|
236
254
|
this.onContentChanged();
|
|
237
255
|
},
|
|
238
|
-
updateTextStyles({ id, name, value, }) {
|
|
256
|
+
updateTextStyles({ id, name, value, isHydrationWrite, }) {
|
|
239
257
|
const item = this.items.find((i) => i.id === id);
|
|
240
258
|
if (!item) {
|
|
241
259
|
return;
|
|
242
260
|
}
|
|
261
|
+
// Same hydration-write contract as updateItemValue. The RTE's
|
|
262
|
+
// mount-time style sync (setInitialStyleSettings) lands here ~100ms
|
|
263
|
+
// BEFORE its canonical text write-back — if this write opens a plain
|
|
264
|
+
// capture burst, the flagged text write joins it and both commit as a
|
|
265
|
+
// phantom entry (BP-5810's second act).
|
|
266
|
+
if (isHydrationWrite) {
|
|
267
|
+
useHistoryStore().mergeNextMutation();
|
|
268
|
+
}
|
|
243
269
|
item.settings.content[`${name}Style`] = vueDeepMerge(item.settings.content[`${name}Style`], value);
|
|
244
270
|
this.validateItem({ id, name });
|
|
245
271
|
this.onContentChanged();
|
|
@@ -251,21 +277,29 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
251
277
|
}
|
|
252
278
|
return item.settings.content[`${name}Style`];
|
|
253
279
|
},
|
|
254
|
-
updateTextItemTextStyleType({ id, name, value, index, }) {
|
|
280
|
+
updateTextItemTextStyleType({ id, name, value, index, isHydrationWrite, }) {
|
|
255
281
|
const item = this.items.find((i) => i.id === id);
|
|
256
282
|
if (!item) {
|
|
257
283
|
return;
|
|
258
284
|
}
|
|
285
|
+
// Same hydration-write contract as updateItemValue.
|
|
286
|
+
if (isHydrationWrite) {
|
|
287
|
+
useHistoryStore().mergeNextMutation();
|
|
288
|
+
}
|
|
259
289
|
item.settings.content.textItems[index].textStyle.textStyleType =
|
|
260
290
|
vueDeepMerge(item.settings.content.textItems[index].textStyle.textStyleType, value);
|
|
261
291
|
this.validateItem({ id, name });
|
|
262
292
|
this.onContentChanged();
|
|
263
293
|
},
|
|
264
|
-
updateTextItemTextStyleTypeWithStyles({ id, name, value, index, }) {
|
|
294
|
+
updateTextItemTextStyleTypeWithStyles({ id, name, value, index, isHydrationWrite, }) {
|
|
265
295
|
const item = this.items.find((i) => i.id === id);
|
|
266
296
|
if (!item) {
|
|
267
297
|
return;
|
|
268
298
|
}
|
|
299
|
+
// Same hydration-write contract as updateItemValue.
|
|
300
|
+
if (isHydrationWrite) {
|
|
301
|
+
useHistoryStore().mergeNextMutation();
|
|
302
|
+
}
|
|
269
303
|
item.settings.content.textItems[index].textStyle = vueDeepMerge(item.settings.content.textItems[index].textStyle, value);
|
|
270
304
|
this.validateItem({ id, name });
|
|
271
305
|
this.onContentChanged();
|
|
@@ -4411,15 +4411,15 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
4411
4411
|
pushMode(item: import("../../../content-settings/store").ContentSettingsModeStackItem): void;
|
|
4412
4412
|
resetMode(): void;
|
|
4413
4413
|
updateItemValue(payload: import("../../../content-settings/models").UpdateItemValuePayload<unknown>): void;
|
|
4414
|
-
updateMultipleItemValues({ id, name, value, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4415
|
-
updateTextStyleType({ id, name, value, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4416
|
-
updateTextStyles({ id, name, value, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4414
|
+
updateMultipleItemValues({ id, name, value, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4415
|
+
updateTextStyleType({ id, name, value, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4416
|
+
updateTextStyles({ id, name, value, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4417
4417
|
getTextStyles({ id, name }: {
|
|
4418
4418
|
id: string;
|
|
4419
4419
|
name: string;
|
|
4420
4420
|
}): unknown;
|
|
4421
|
-
updateTextItemTextStyleType({ id, name, value, index, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4422
|
-
updateTextItemTextStyleTypeWithStyles({ id, name, value, index, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4421
|
+
updateTextItemTextStyleType({ id, name, value, index, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4422
|
+
updateTextItemTextStyleTypeWithStyles({ id, name, value, index, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
4423
4423
|
_setActiveCategoryTab({ activeCategoryTab, }: {
|
|
4424
4424
|
activeCategoryTab: import("../../../../brand-page-maker/models").ItemCategory;
|
|
4425
4425
|
}): void;
|
|
@@ -3787,15 +3787,15 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
3787
3787
|
pushMode(item: import("../../../content-settings/store").ContentSettingsModeStackItem): void;
|
|
3788
3788
|
resetMode(): void;
|
|
3789
3789
|
updateItemValue(payload: UpdateItemValuePayload<unknown>): void;
|
|
3790
|
-
updateMultipleItemValues({ id, name, value, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3791
|
-
updateTextStyleType({ id, name, value, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3792
|
-
updateTextStyles({ id, name, value, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3790
|
+
updateMultipleItemValues({ id, name, value, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3791
|
+
updateTextStyleType({ id, name, value, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3792
|
+
updateTextStyles({ id, name, value, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3793
3793
|
getTextStyles({ id, name }: {
|
|
3794
3794
|
id: string;
|
|
3795
3795
|
name: string;
|
|
3796
3796
|
}): unknown;
|
|
3797
|
-
updateTextItemTextStyleType({ id, name, value, index, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3798
|
-
updateTextItemTextStyleTypeWithStyles({ id, name, value, index, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3797
|
+
updateTextItemTextStyleType({ id, name, value, index, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3798
|
+
updateTextItemTextStyleTypeWithStyles({ id, name, value, index, isHydrationWrite, }: import("../../../content-settings/models").UpdateMultipleItemValuesPayload<any>): void;
|
|
3799
3799
|
_setActiveCategoryTab({ activeCategoryTab, }: {
|
|
3800
3800
|
activeCategoryTab: import("../../../../brand-page-maker/models").ItemCategory;
|
|
3801
3801
|
}): void;
|
package/package.json
CHANGED
|
@@ -211,10 +211,6 @@ export const useRichTextEditor = (
|
|
|
211
211
|
): UseRichTextEditorReturn => {
|
|
212
212
|
const historyStore = useHistoryStore();
|
|
213
213
|
|
|
214
|
-
// TipTap rehydrates and writes back canonical HTML on mount — fold
|
|
215
|
-
// that into the current head instead of pushing a phantom entry.
|
|
216
|
-
historyStore.mergeNextMutation();
|
|
217
|
-
|
|
218
214
|
const editor = new Editor({
|
|
219
215
|
content: model.value ?? '',
|
|
220
216
|
editable: !props.readOnly,
|
|
@@ -492,6 +488,14 @@ export const useRichTextEditor = (
|
|
|
492
488
|
);
|
|
493
489
|
|
|
494
490
|
editor.on('transaction', ({ transaction }) => {
|
|
491
|
+
// Style detection only makes sense for user styling — an unfocused
|
|
492
|
+
// editor's transactions are mount rehydration / programmatic setContent
|
|
493
|
+
// churn, and clearing textStyleType from those would push a phantom
|
|
494
|
+
// style write into the model (BP-5810 family).
|
|
495
|
+
if (!editor.isFocused) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
495
499
|
if (isStyleChanged(transaction) && props.styles.textStyleType) {
|
|
496
500
|
onSelectTextStyleType({
|
|
497
501
|
label: 'None',
|
|
@@ -505,22 +509,22 @@ export const useRichTextEditor = (
|
|
|
505
509
|
|
|
506
510
|
// An update fired while the editor is unfocused is TipTap rewriting the
|
|
507
511
|
// model back into its canonical HTML during mount rehydration or a
|
|
508
|
-
// programmatic setContent — not a user edit.
|
|
509
|
-
//
|
|
510
|
-
// (BP-5810). The
|
|
511
|
-
//
|
|
512
|
-
//
|
|
512
|
+
// programmatic setContent — not a user edit. Tag the emit so the write
|
|
513
|
+
// folds into the current undo head instead of pushing a phantom entry
|
|
514
|
+
// (BP-5810). The flag travels WITH the value through the debounced
|
|
515
|
+
// field → store chain and arms the history merge at the moment of the
|
|
516
|
+
// store write — arming here at emit time would lapse before the
|
|
517
|
+
// debounce fires. Genuine edits happen while focused and commit
|
|
518
|
+
// normally.
|
|
513
519
|
//
|
|
514
520
|
// Known limitation: a programmatic toolbar command run on an editor the
|
|
515
521
|
// user hasn't focused yet would also merge (losing that undo step). Today
|
|
516
522
|
// the RTE only mounts inside an already-open settings drawer and the user
|
|
517
523
|
// focuses it before formatting, so this doesn't occur — revisit if a
|
|
518
524
|
// consumer ever drives toolbar actions on an unfocused editor.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
emit('update:modelValue', sanitizedData.sanitizedHtml);
|
|
525
|
+
emit('update:modelValue', sanitizedData.sanitizedHtml, {
|
|
526
|
+
isHydration: !editor.isFocused,
|
|
527
|
+
});
|
|
524
528
|
});
|
|
525
529
|
|
|
526
530
|
editor.on('selectionUpdate', () => {
|
|
@@ -621,7 +625,11 @@ export const useRichTextEditor = (
|
|
|
621
625
|
);
|
|
622
626
|
|
|
623
627
|
if (globalStyles) {
|
|
624
|
-
// sync content settings style
|
|
628
|
+
// sync content settings style — a mount-time re-derivation, not a user
|
|
629
|
+
// edit. Flag it so the store folds it into the current undo head: this
|
|
630
|
+
// write lands ~100ms BEFORE the editor's canonical text write-back, and
|
|
631
|
+
// an unflagged write here would open a plain capture burst that
|
|
632
|
+
// swallows the flagged text write into a phantom entry (BP-5810).
|
|
625
633
|
contentSettingStore.updateTextStyles({
|
|
626
634
|
id: props.rawId,
|
|
627
635
|
name: props.name,
|
|
@@ -635,6 +643,7 @@ export const useRichTextEditor = (
|
|
|
635
643
|
lineHeight: globalStyles.lineHeight,
|
|
636
644
|
letterSpacing: globalStyles.letterSpacing,
|
|
637
645
|
},
|
|
646
|
+
isHydrationWrite: !editor.isFocused,
|
|
638
647
|
});
|
|
639
648
|
|
|
640
649
|
// sync toolbar editor styles
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { defineComponent } from 'vue';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import GenericTextInputMixin from './GenericTextInput.mixin';
|
|
4
|
+
|
|
5
|
+
// BP-5810: the RTE attaches `{ isHydration: true }` meta to its canonical
|
|
6
|
+
// write-back emits. GenericTextInput must carry that through its 100ms
|
|
7
|
+
// debounce onto the outgoing payload as `isHydrationWrite` — and downgrade
|
|
8
|
+
// the burst as soon as a single non-hydration write lands in it.
|
|
9
|
+
|
|
10
|
+
const DEBOUNCE_WAIT_MS = 100;
|
|
11
|
+
|
|
12
|
+
interface EmittedPayload {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
value: string | false;
|
|
16
|
+
isHydrationWrite?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const TestHost = defineComponent({
|
|
20
|
+
mixins: [GenericTextInputMixin],
|
|
21
|
+
template: '<div />',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const mountHost = () =>
|
|
25
|
+
mount(TestHost, {
|
|
26
|
+
props: {
|
|
27
|
+
id: 'item-1',
|
|
28
|
+
name: 'title',
|
|
29
|
+
value: '<p>seed</p>',
|
|
30
|
+
showRichTextEditor: true,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const emittedPayload = (
|
|
35
|
+
wrapper: ReturnType<typeof mountHost>,
|
|
36
|
+
call: number,
|
|
37
|
+
): EmittedPayload =>
|
|
38
|
+
wrapper.emitted('update:modelValue')![call][0] as EmittedPayload;
|
|
39
|
+
|
|
40
|
+
describe('GenericTextInput hydration burst flag', () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.useFakeTimers();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('a hydration-only burst emits a payload flagged isHydrationWrite', async () => {
|
|
50
|
+
const wrapper = mountHost();
|
|
51
|
+
|
|
52
|
+
wrapper.vm.onRteModelValue('<p style="text-align: center;"></p>', {
|
|
53
|
+
isHydration: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await vi.advanceTimersByTimeAsync(DEBOUNCE_WAIT_MS + 10);
|
|
57
|
+
|
|
58
|
+
const emitted = wrapper.emitted('update:modelValue');
|
|
59
|
+
|
|
60
|
+
expect(emitted).toHaveLength(1);
|
|
61
|
+
expect(emitted![0][0]).toEqual({
|
|
62
|
+
id: 'item-1',
|
|
63
|
+
name: 'title',
|
|
64
|
+
value: '<p style="text-align: center;"></p>',
|
|
65
|
+
isHydrationWrite: true,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('coalesced hydration writes in one burst stay flagged', async () => {
|
|
70
|
+
const wrapper = mountHost();
|
|
71
|
+
|
|
72
|
+
wrapper.vm.onRteModelValue('<p>a</p>', { isHydration: true });
|
|
73
|
+
wrapper.vm.onRteModelValue('<p>b</p>', { isHydration: true });
|
|
74
|
+
|
|
75
|
+
await vi.advanceTimersByTimeAsync(DEBOUNCE_WAIT_MS + 10);
|
|
76
|
+
|
|
77
|
+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1);
|
|
78
|
+
expect(emittedPayload(wrapper, 0).isHydrationWrite).toBe(true);
|
|
79
|
+
expect(emittedPayload(wrapper, 0).value).toBe('<p>b</p>');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('a user write in the same burst downgrades it to a normal edit', async () => {
|
|
83
|
+
const wrapper = mountHost();
|
|
84
|
+
|
|
85
|
+
wrapper.vm.onRteModelValue('<p>canonical</p>', { isHydration: true });
|
|
86
|
+
// Focused typing emits without meta.
|
|
87
|
+
wrapper.vm.onRteModelValue('<p>typed</p>');
|
|
88
|
+
|
|
89
|
+
await vi.advanceTimersByTimeAsync(DEBOUNCE_WAIT_MS + 10);
|
|
90
|
+
|
|
91
|
+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1);
|
|
92
|
+
expect(emittedPayload(wrapper, 0).isHydrationWrite).toBeUndefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('a plain write (no meta) is never flagged', async () => {
|
|
96
|
+
const wrapper = mountHost();
|
|
97
|
+
|
|
98
|
+
wrapper.vm.text = '<p>typed</p>';
|
|
99
|
+
|
|
100
|
+
await vi.advanceTimersByTimeAsync(DEBOUNCE_WAIT_MS + 10);
|
|
101
|
+
|
|
102
|
+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1);
|
|
103
|
+
expect(emittedPayload(wrapper, 0).isHydrationWrite).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('the flag resets between bursts', async () => {
|
|
107
|
+
const wrapper = mountHost();
|
|
108
|
+
|
|
109
|
+
wrapper.vm.onRteModelValue('<p>canonical</p>', { isHydration: true });
|
|
110
|
+
await vi.advanceTimersByTimeAsync(DEBOUNCE_WAIT_MS + 10);
|
|
111
|
+
|
|
112
|
+
wrapper.vm.text = '<p>typed later</p>';
|
|
113
|
+
await vi.advanceTimersByTimeAsync(DEBOUNCE_WAIT_MS + 10);
|
|
114
|
+
|
|
115
|
+
expect(wrapper.emitted('update:modelValue')).toHaveLength(2);
|
|
116
|
+
expect(emittedPayload(wrapper, 0).isHydrationWrite).toBe(true);
|
|
117
|
+
expect(emittedPayload(wrapper, 1).isHydrationWrite).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -108,6 +108,13 @@ export default defineComponent({
|
|
|
108
108
|
isDirty: false,
|
|
109
109
|
emitChangeDebounce: undefined as DebounceHandle | undefined,
|
|
110
110
|
lengthError: false,
|
|
111
|
+
// Whether every write in the current debounce burst was an RTE
|
|
112
|
+
// hydration write-back (see UpdateItemValuePayload.isHydrationWrite).
|
|
113
|
+
// undefined = no burst pending; any user write downgrades it to false.
|
|
114
|
+
burstIsHydration: undefined as boolean | undefined,
|
|
115
|
+
// One-shot channel from onRteModelValue into the `text` setter for
|
|
116
|
+
// the write currently being applied.
|
|
117
|
+
pendingWriteIsHydration: false,
|
|
111
118
|
};
|
|
112
119
|
},
|
|
113
120
|
computed: {
|
|
@@ -144,6 +151,15 @@ export default defineComponent({
|
|
|
144
151
|
return unescapeContentText(this.value);
|
|
145
152
|
},
|
|
146
153
|
set(newVal: string | false): void {
|
|
154
|
+
// A burst is hydration only if EVERY write in it is hydration —
|
|
155
|
+
// one real keystroke downgrades the whole burst to a user edit
|
|
156
|
+
// (the canonicalisation then rides inside that legitimate entry).
|
|
157
|
+
const writeIsHydration = this.pendingWriteIsHydration;
|
|
158
|
+
|
|
159
|
+
this.pendingWriteIsHydration = false;
|
|
160
|
+
this.burstIsHydration =
|
|
161
|
+
this.burstIsHydration !== false && writeIsHydration;
|
|
162
|
+
|
|
147
163
|
if (newVal === false) {
|
|
148
164
|
this._value = false;
|
|
149
165
|
} else {
|
|
@@ -179,12 +195,16 @@ export default defineComponent({
|
|
|
179
195
|
this._value = false;
|
|
180
196
|
}
|
|
181
197
|
|
|
198
|
+
// Toggling is a user action — downgrade any pending hydration burst
|
|
199
|
+
// so this emit commits as a normal entry.
|
|
200
|
+
this.burstIsHydration = false;
|
|
182
201
|
this.emitChange();
|
|
183
202
|
},
|
|
184
203
|
|
|
185
204
|
showRichTextEditor(newVal: boolean) {
|
|
186
205
|
if (!newVal && this._value && typeof this._value === 'string') {
|
|
187
206
|
this._value = encodeContentText(this._value);
|
|
207
|
+
this.burstIsHydration = false;
|
|
188
208
|
this.emitChange();
|
|
189
209
|
}
|
|
190
210
|
},
|
|
@@ -208,11 +228,25 @@ export default defineComponent({
|
|
|
208
228
|
this.isDirty = false;
|
|
209
229
|
},
|
|
210
230
|
|
|
231
|
+
// RTE branch handler: receives the hydration meta the RichTextEditor
|
|
232
|
+
// attaches to its update:modelValue emits (a plain v-model binding
|
|
233
|
+
// would drop the second argument), records it for the `text` setter,
|
|
234
|
+
// then applies the write.
|
|
235
|
+
onRteModelValue(newVal: string, meta?: { isHydration?: boolean }): void {
|
|
236
|
+
this.pendingWriteIsHydration = meta?.isHydration === true;
|
|
237
|
+
this.text = newVal;
|
|
238
|
+
},
|
|
239
|
+
|
|
211
240
|
emitChange() {
|
|
241
|
+
const isHydrationWrite = this.burstIsHydration === true;
|
|
242
|
+
|
|
243
|
+
this.burstIsHydration = undefined;
|
|
244
|
+
|
|
212
245
|
this.$emit('update:modelValue', {
|
|
213
246
|
id: this.id,
|
|
214
247
|
name: this.name,
|
|
215
248
|
value: this._value,
|
|
249
|
+
...(isHydrationWrite ? { isHydrationWrite: true } : {}),
|
|
216
250
|
});
|
|
217
251
|
},
|
|
218
252
|
},
|
|
@@ -24,7 +24,6 @@ import { Button } from '@designcrowd/fe-shared-lib';
|
|
|
24
24
|
import { ref, computed, onMounted } from 'vue';
|
|
25
25
|
import { useUiPageSettingsStore } from '../../../page-settings/store';
|
|
26
26
|
import { useBrandPageMakerUserStore } from '../../../../brand-page-maker/stores';
|
|
27
|
-
import { useHistoryStore } from '../../../../brand-page-maker/stores/history/history-store';
|
|
28
27
|
import { pageSettingTr } from '../../../../composables/useTranslate';
|
|
29
28
|
import PageSelectorDropdown from '../../../page-navigation/PageSelectorDropdown.vue';
|
|
30
29
|
import { usePageNavigationStore } from '../../../page-navigation/store/page-navigation-store';
|
|
@@ -44,14 +43,18 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
44
43
|
const emit = defineEmits<{
|
|
45
44
|
(
|
|
46
45
|
e: 'update:modelValue',
|
|
47
|
-
payload: {
|
|
46
|
+
payload: {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
value: string;
|
|
50
|
+
isHydrationWrite?: boolean;
|
|
51
|
+
},
|
|
48
52
|
): void;
|
|
49
53
|
}>();
|
|
50
54
|
|
|
51
55
|
const pageNavigationStore = usePageNavigationStore();
|
|
52
56
|
const pageSettingsStore = useUiPageSettingsStore();
|
|
53
57
|
const userStore = useBrandPageMakerUserStore();
|
|
54
|
-
const historyStore = useHistoryStore();
|
|
55
58
|
|
|
56
59
|
const selectedPage = ref('');
|
|
57
60
|
|
|
@@ -111,12 +114,14 @@ onMounted(() => {
|
|
|
111
114
|
if (routeByPageId) {
|
|
112
115
|
selectedPage.value = routeByPageId.displayName;
|
|
113
116
|
} else {
|
|
114
|
-
// Stale page-id clear at mount —
|
|
115
|
-
|
|
117
|
+
// Stale page-id clear at mount — a hydration write, not a user edit.
|
|
118
|
+
// The flag makes the content-settings store fold it into the current
|
|
119
|
+
// undo head at write time (see UpdateItemValuePayload.isHydrationWrite).
|
|
116
120
|
emit('update:modelValue', {
|
|
117
121
|
id: props.id,
|
|
118
122
|
name: props.name,
|
|
119
123
|
value: '',
|
|
124
|
+
isHydrationWrite: true,
|
|
120
125
|
});
|
|
121
126
|
}
|
|
122
127
|
}
|
package/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { useShopConfigStore } from '@shared/ecommerce-settings/store/shop-settin
|
|
|
15
15
|
import { paymentConfigTr } from '@src/composables/useTranslate';
|
|
16
16
|
import brandPageClient from '../../../../clients/brand-page-client';
|
|
17
17
|
import { useUiPageSettingsStore } from '@shared/page-settings/store';
|
|
18
|
-
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
19
18
|
|
|
20
19
|
export default defineComponent({
|
|
21
20
|
props: {
|
|
@@ -42,7 +41,6 @@ export default defineComponent({
|
|
|
42
41
|
const uiPageSettingsStore = useUiPageSettingsStore();
|
|
43
42
|
const eventManagerStore = useEventManagerStore();
|
|
44
43
|
const shopConfigStore = useShopConfigStore();
|
|
45
|
-
const historyStore = useHistoryStore();
|
|
46
44
|
|
|
47
45
|
return {
|
|
48
46
|
brandPageToken: computed(() => brandPageContainerStore.brandPageToken),
|
|
@@ -53,7 +51,6 @@ export default defineComponent({
|
|
|
53
51
|
eventManagerStore,
|
|
54
52
|
shopConfigStore,
|
|
55
53
|
uiPageSettingsStore,
|
|
56
|
-
historyStore,
|
|
57
54
|
};
|
|
58
55
|
},
|
|
59
56
|
emits: ['update:modelValue'],
|
|
@@ -113,16 +110,16 @@ export default defineComponent({
|
|
|
113
110
|
paymentConfig: this.shopConfigStore.paymentConfig,
|
|
114
111
|
};
|
|
115
112
|
|
|
113
|
+
// Lazy paymentConfig hydration — a hydration write, not a user edit.
|
|
114
|
+
// The flag makes the content-settings store fold it into the current
|
|
115
|
+
// undo head at write time (see UpdateItemValuePayload.isHydrationWrite).
|
|
116
116
|
const payload: UpdateItemValuePayload<PaymentConfig> = {
|
|
117
117
|
id: this.id,
|
|
118
118
|
name: this.name,
|
|
119
119
|
value,
|
|
120
|
+
isHydrationWrite: true,
|
|
120
121
|
};
|
|
121
122
|
|
|
122
|
-
// Lazy paymentConfig hydration — fold into current head instead
|
|
123
|
-
// of pushing a phantom entry.
|
|
124
|
-
this.historyStore.mergeNextMutation();
|
|
125
|
-
|
|
126
123
|
this.$emit('update:modelValue', payload);
|
|
127
124
|
}
|
|
128
125
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
v-if="showRichTextEditor"
|
|
5
5
|
:id="`${id}-${name}`"
|
|
6
6
|
ref="richTextEditorRef"
|
|
7
|
-
|
|
7
|
+
:model-value="text"
|
|
8
8
|
:attrs="attrs"
|
|
9
9
|
:label="label"
|
|
10
10
|
:placeholder="placeholder"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
:content-type="contentType"
|
|
18
18
|
@focus="onFocus"
|
|
19
19
|
@blur="onBlur"
|
|
20
|
+
@update:model-value="onRteModelValue"
|
|
20
21
|
/>
|
|
21
22
|
<TextInput
|
|
22
23
|
v-else
|