@designcrowd/library.brand-page 5.17.25-undo-8 → 5.17.25-undo-10

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 (31) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-AfnY8zoN.js → CreatedOnBrandCrowd-de-de-C2CJCAQ1.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-D8X7A_M5.js → CreatedOnBrandCrowd-en-us-Bg5k7y_D.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DjKTziiy.js → CreatedOnBrandCrowd-es-es-DAaAXvzy.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-dSR3lZfa.js → CreatedOnBrandCrowd-fr-ca-B7C0ERbi.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-dSR3lZfa.js → CreatedOnBrandCrowd-fr-fr-B7C0ERbi.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-BTD44ERQ.js → CreatedOnBrandCrowd-pt-br-1pvCqQK1.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-BTD44ERQ.js → CreatedOnBrandCrowd-pt-pt-1pvCqQK1.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-QP4_OShH.js → CreatedOnDcom-de-de-hbcr-qW0.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-CWyO9t7x.js → CreatedOnDcom-en-us-BToix_74.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-B3sZqeKP.js → CreatedOnDcom-es-es-DOCxN0tr.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BR3-inLX.js → CreatedOnDcom-fr-ca-Q3ZYgCxp.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DdrT0HCa.js → CreatedOnDcom-fr-fr-C1-dUWKB.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-3Oz8RFHK.js → CreatedOnDcom-pt-br-KWKgld1I.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DwrZkI_f.js → CreatedOnDcom-pt-pt-CRR3Hfej.js} +1 -1
  15. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +1 -0
  16. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.js +6 -0
  17. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +36 -0
  18. package/lib/src/brand-page-maker/stores/history/history-store.js +213 -20
  19. package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
  20. package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
  21. package/lib/src/css/library.brand-page-designCom.min.css +1 -1
  22. package/lib/src/css/library.brand-page-preview.min.css +1 -1
  23. package/lib/src/index.mjs +1 -1
  24. package/lib/src/{lib-Y0xSZnR9.js → lib-Bhf5ZHsd.js} +5967 -5886
  25. package/package.json +1 -1
  26. package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.ts +7 -0
  27. package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.vue +1 -1
  28. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +195 -0
  29. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +42 -0
  30. package/src/brand-page-maker/stores/history/history-store.test.ts +234 -3
  31. package/src/brand-page-maker/stores/history/history-store.ts +239 -23
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "275",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "251",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "250",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-Y0xSZnR9.js";
2
+ import { _ as t } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-Y0xSZnR9.js";
2
+ import { _ as e } from "./lib-Bhf5ZHsd.js";
3
3
  const M = {}, Z = {
4
4
  width: "242",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-Y0xSZnR9.js";
2
+ import { _ as e } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, M = {
4
4
  width: "231",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-Y0xSZnR9.js";
2
+ import { _ as e } from "./lib-Bhf5ZHsd.js";
3
3
  const M = {}, Z = {
4
4
  width: "230",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-Y0xSZnR9.js";
2
+ import { _ as e } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-Y0xSZnR9.js";
2
+ import { _ as e } from "./lib-Bhf5ZHsd.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-Y0xSZnR9.js";
2
+ import { _ as M } from "./lib-Bhf5ZHsd.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-Y0xSZnR9.js";
2
+ import { _ as M } from "./lib-Bhf5ZHsd.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -324,6 +324,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
324
324
  copied: boolean;
325
325
  }, {
326
326
  isPublished(): boolean;
327
+ shouldShowBar(): boolean;
327
328
  brandPageUrl(): string;
328
329
  copyButtonLabel(): string;
329
330
  }, {
@@ -30,6 +30,12 @@ export default defineComponent({
30
30
  isPublished() {
31
31
  return this.brandPageContainerStore.isPublished;
32
32
  },
33
+ // Only render the bar when at least one inner cluster has something to
34
+ // show. Otherwise we'd render an empty white strip — e.g. on an
35
+ // unpublished page with the undo flag off.
36
+ shouldShowBar() {
37
+ return Boolean(this.undoRedoEnabled) || this.isPublished;
38
+ },
33
39
  brandPageUrl() {
34
40
  if (this.brandPageContainerStore.isCustomDomainEnabled &&
35
41
  this.brandPageContainerStore.customDomain &&
@@ -594,6 +594,28 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
594
594
  const draft = logoStore.getLogoDraftAssetByToken(logoImage.logoDraftToken);
595
595
  if (draft)
596
596
  editableLogoStore.setEnableEditMode(!draft.isByo);
597
+ // Repaint the canvas logo when the restored draft token differs
598
+ // from the editable logo store's current token. History snapshots
599
+ // intentionally drop the rendered `logoImageBase64Url` (a high-res
600
+ // BYO upload renders ~10 MB across the four viewports, which would
601
+ // blow the 5 MB per-entry cap and freeze history). On undo back to
602
+ // a different draft, the page-settings logo fields now have an
603
+ // empty base64, so the canvas would show no logo until something
604
+ // re-renders. Pointing the editable logo store at the restored
605
+ // draft and asking it to re-render fans out fabric-render requests
606
+ // for the four variations and writes fresh base64 onto every page
607
+ // section + logo content block — same path as the original BYO
608
+ // upload. Fire-and-forget: the restore returns immediately and the
609
+ // canvas updates as the renders complete, just like the upload UX.
610
+ const tokenChanged = editableLogoStore.logoDraftToken !== logoImage.logoDraftToken;
611
+ if (tokenChanged && logoImage.logoDraftToken) {
612
+ editableLogoStore.setLogoDraftToken(logoImage.logoDraftToken);
613
+ editableLogoStore
614
+ .updateEditableLogoImageUrlAsync()
615
+ .catch((err) => {
616
+ console.error('history-store: failed to repaint logo on restore', err);
617
+ });
618
+ }
597
619
  },
598
620
  _fallbackSidebarIfSelectedContentMissing(snapshot) {
599
621
  const contentSettingsStore = useUiContentSettingsStore();
@@ -610,9 +632,23 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
610
632
  });
611
633
  if (existingContentIds.has(selectedItemId))
612
634
  return;
635
+ // The previously-selected content was undone away. Restore the
636
+ // Content tab's natural landing state — the same shape produced
637
+ // when the user clicks the Content tab fresh — instead of leaving
638
+ // the sidebar stuck rendering `<FocusedContentItemSettings>` for
639
+ // a missing item, which paints a blank panel.
640
+ //
641
+ // `resetMode()` pops the modeStack back to the base `edit` entry
642
+ // (the default landing mode); clearing `selectedItemId` releases
643
+ // the pointer to the now-deleted item.
613
644
  sideBarStore.setSelectedTabName({
614
645
  tabName: SIDE_BAR_TAB_NAMES.CONTENT_SETTINGS,
615
646
  });
647
+ contentSettingsStore.resetMode();
648
+ contentSettingsStore.selectedItemId = null;
649
+ contentSettingsStore.selectedItemFieldName = null;
650
+ contentSettingsStore.selectedItemIndex = null;
651
+ contentSettingsStore.selectedItemValue = null;
616
652
  },
617
653
  export() {
618
654
  const data = this.brandPageData;
@@ -73,7 +73,26 @@ export const useHistoryStore = defineStore('history', () => {
73
73
  return null;
74
74
  try {
75
75
  const raw = toRaw(cachedMakerStore.brandPageData);
76
- return JSON.stringify(raw);
76
+ // Drop `logoImageBase64Url` from the serialized form. A high-res
77
+ // BYO upload renders a ~2.5 MB base64 PNG into each of the four
78
+ // viewport sections (~10 MB raw), which is well past the 5 MB
79
+ // per-entry cap and would otherwise stick every BYO capture — and
80
+ // every follow-up edit while the upload is on the page — into the
81
+ // `oversized` path, freezing history. Stripping the field at
82
+ // serialize time keeps entries small and is safe for restore:
83
+ // - BYO upload IS undoable. The undo-relevant signal is
84
+ // `logoImage.logoDraftToken`, which is preserved.
85
+ // - On undo `LogoRender` watches `viewModel` and falls back to
86
+ // fetching the SVG by `logoDraftToken` when `logoImageBase64Url`
87
+ // is undefined (LogoRender.vue:97-102, 139-161), so the canvas
88
+ // repaints the previous logo without the cached PNG.
89
+ // - Per-property logo edits (text, color, font) author via
90
+ // `editableLogoStore.logoEdits` — not `brandPageData` — so
91
+ // they're already out of V1 undo scope. Same key is also
92
+ // stripped in the dedup normalizer; that strip is now
93
+ // redundant for the watcher path but kept as belt-and-braces
94
+ // in case a snapshot string lands there from another origin.
95
+ return JSON.stringify(raw, (key, value) => key === 'logoImageBase64Url' ? undefined : value);
77
96
  }
78
97
  catch (error) {
79
98
  console.error('history-store: failed to serialize brandPageData', error);
@@ -91,6 +110,10 @@ export const useHistoryStore = defineStore('history', () => {
91
110
  * (e.g. `""` → `<p style="text-align: center;"></p>`).
92
111
  * - TipTap RTE writing back its default-style hydration on first mount
93
112
  * (e.g. plain `"Hello"` → `<p style="..."><span style="..."><mark ...>Hello</mark></span></p>`).
113
+ * - TipTap re-emitting whole-content `<strong>`/`<em>`/`<u>`/`<s>` tags
114
+ * to mirror the resolved `textStyle.fontWeight / fontStyle /
115
+ * textDecoration` (e.g. plain `"text"` →
116
+ * `<p>...<span>...<strong>text</strong></span></p>`).
94
117
  * - Lazy hydration of `*Style` objects when a content settings panel —
95
118
  * or section selection on the canvas — resolves `textStyleType` and
96
119
  * writes the full resolved style (fontFamily, fontSize, color,
@@ -98,38 +121,197 @@ export const useHistoryStore = defineStore('history', () => {
98
121
  * onto the model. The `textStyleType` itself is the authoring
99
122
  * intent; the resolved keys are just cached values.
100
123
  * - `isModifiedAfterTemplateApplied` flipping on first edit gesture.
124
+ * - `logoImageBase64Url` — the rendered logo PNG. Per-property logo
125
+ * edits author through `editableLogoStore.logoEdits` (not in
126
+ * `brandPageData`), so an undo would revert the canvas image but
127
+ * leave the sidebar fields showing the user's typed value. Logo
128
+ * property edits are excluded from V1 undo scope; BYO-upload undo
129
+ * still works because that diff lives in `logoImage.logoDraftToken`.
130
+ *
131
+ * RTE strings are canonicalized by parsing the HTML with DOMParser and
132
+ * walking the tree; only attributeless format wrappers (and a tightly
133
+ * scoped set of hydration styles on `<p>` / `<span>` / `<mark>`) are
134
+ * collapsed. Real authoring intent (links, custom colors, partial-text
135
+ * formatting like `Hello <strong>world</strong>`) survives the walk
136
+ * because at least one node falls outside the hydration whitelist,
137
+ * and the walker bails to "preserve original" the moment that happens.
101
138
  *
102
139
  * Used ONLY for dedup comparison. Stored snapshots remain unchanged so
103
140
  * `applyHistorySnapshot` always sees the real state.
104
141
  */
142
+ // Tags TipTap re-emits purely to mirror the resolved textStyle on first
143
+ // hydration. None carry user authoring intent on their own when they are
144
+ // the sole element-child wrapping the entire content:
145
+ // - <p>: only its `text-align` style is meaningful, and that's already
146
+ // captured in `textStyle.textAlign`.
147
+ // - <span>: TipTap wraps content in a span with `font-family`, `font-size`,
148
+ // `color`, `letter-spacing`, `line-height` resolved from the
149
+ // textStyle. The textStyle object on the same content is the
150
+ // authoritative source.
151
+ // - <mark>: TipTap's highlight extension default — collapses regardless
152
+ // of `data-color` / `style` attrs, mirroring the original
153
+ // regex's behavior on a hydrated highlight.
154
+ // - <strong>/<em>/<u>/<s>: attributeless tags that mirror
155
+ // `fontWeight: bold` / `fontStyle: italic` / `textDecoration:
156
+ // underline / line-through`. ONLY recognized as hydration when
157
+ // they are the sole child of their parent — partial-content
158
+ // usage like `Hello <strong>world</strong>` is a real edit.
159
+ // Anything else (`<a>`, `<br>`, `<span style="color: red">`, attributes on
160
+ // <strong>/<em>, etc.) is preserved as a real diff.
161
+ const STRUCTURAL_HYDRATION_TAGS = new Set(['P', 'SPAN', 'MARK']);
162
+ const FORMAT_HYDRATION_TAGS = new Set(['STRONG', 'EM', 'U', 'S']);
163
+ // Default style properties TipTap emits on a hydration <span> /
164
+ // <mark>. The textStyle resolver writes all of these onto the live
165
+ // model, so when TipTap re-emits them as inline style on its
166
+ // hydration span/mark there is no user-visible delta.
167
+ // Anything outside this whitelist (e.g. `text-decoration` mid-content,
168
+ // `text-shadow`, `text-transform`) is treated as a real edit.
169
+ const HYDRATION_INLINE_STYLE_PROPS = new Set([
170
+ 'font-family',
171
+ 'font-size',
172
+ 'letter-spacing',
173
+ 'line-height',
174
+ 'color',
175
+ 'background-color',
176
+ ]);
105
177
  // Empty paragraph forms TipTap emits when an editor mounts on an empty
106
178
  // string. Treated as equivalent to `""` for dedup.
107
- const EMPTY_RTE_HTML = /^<p(\s[^>]*)?>(<br\s*\/?>)?<\/p>$/;
108
- // TipTap's default-hydration shape: a single <p> (with optional align
109
- // attr) containing the original plain text, optionally wrapped in
110
- // default-styled <span> (font-family / font-size / color) and/or
111
- // <mark> (highlight) tags. The inner capture intentionally rejects any
112
- // string that itself contains `<` or `>` so that real formatting tags
113
- // — `<strong>`, `<em>`, `<a>`, nested spans with non-default styles —
114
- // are preserved as a real diff rather than collapsed.
115
- const RTE_DEFAULT_HYDRATION = /^<p(\s[^>]*)?>(<span(\s[^>]*)?>)?(<mark(\s[^>]*)?>)?([^<>]*)(<\/mark>)?(<\/span>)?<\/p>$/;
116
179
  function isEmptyRteHtml(value) {
117
- return typeof value === 'string' && EMPTY_RTE_HTML.test(value);
180
+ if (typeof value !== 'string')
181
+ return false;
182
+ return /^<p(\s[^>]*)?>(<br\s*\/?>)?<\/p>$/.test(value);
183
+ }
184
+ function isHydrationOnlyAttributes(el) {
185
+ // Format tags (<strong>/<em>/<u>/<s>) must be attributeless. Anything
186
+ // with class/style/data-*/etc. is a real edit.
187
+ if (FORMAT_HYDRATION_TAGS.has(el.tagName))
188
+ return el.attributes.length === 0;
189
+ for (const attr of Array.from(el.attributes)) {
190
+ // <mark> may carry a `data-color` attr (TipTap highlight extension's
191
+ // default-and-resolved value). The accompanying `style` is checked
192
+ // against the same hydration-prop whitelist as <span>.
193
+ if (el.tagName === 'MARK' &&
194
+ (attr.name === 'data-color' || attr.name === 'style')) {
195
+ if (attr.name === 'data-color')
196
+ continue;
197
+ }
198
+ else if (attr.name !== 'style') {
199
+ return false;
200
+ }
201
+ const decls = attr.value
202
+ .split(';')
203
+ .map((d) => d.trim())
204
+ .filter(Boolean);
205
+ if (el.tagName === 'P') {
206
+ for (const decl of decls) {
207
+ const [prop] = decl.split(':').map((s) => s.trim());
208
+ if (prop !== 'text-align')
209
+ return false;
210
+ }
211
+ }
212
+ else if (el.tagName === 'SPAN' || el.tagName === 'MARK') {
213
+ for (const decl of decls) {
214
+ const [prop, value] = decl.split(':').map((s) => s.trim());
215
+ // Allow `color: inherit` on <mark> (TipTap stamps it on the
216
+ // default highlight to inherit the span's color). Otherwise
217
+ // require the prop to be in the hydration whitelist.
218
+ if (el.tagName === 'MARK' &&
219
+ prop === 'color' &&
220
+ value === 'inherit') {
221
+ continue;
222
+ }
223
+ if (!HYDRATION_INLINE_STYLE_PROPS.has(prop))
224
+ return false;
225
+ }
226
+ }
227
+ else {
228
+ return false;
229
+ }
230
+ }
231
+ return true;
118
232
  }
119
233
  /**
120
- * If `value` is TipTap's default-hydration wrapper around a plain text
121
- * (no inline formatting tags), return the bare inner text. Otherwise
122
- * return the original string. This collapses the BP-5506 phantom where
123
- * opening a content sidebar mounts every RTE editor at once and writes
124
- * back wrapped HTML with no user-visible delta.
234
+ * Walk a parsed HTML node tree. Return the joined text content if every
235
+ * descendant is hydration noise; return null if any node carries real
236
+ * authoring intent (links, partial formatting, custom styles, etc.).
237
+ *
238
+ * `isFormatTag` differs in how it's allowed to appear: a structural tag
239
+ * (P/SPAN/MARK) can have siblings; a format tag (STRONG/EM/U/S) is only
240
+ * allowed when it's the SOLE child of its parent. The caller enforces
241
+ * that at the iteration site.
242
+ */
243
+ function extractCanonicalText(node) {
244
+ if (node.nodeType === 3 /* TEXT_NODE */)
245
+ return node.textContent ?? '';
246
+ if (node.nodeType !== 1 /* ELEMENT_NODE */) {
247
+ // Comments, CDATA, etc. — treat as opaque (preserve original).
248
+ return null;
249
+ }
250
+ const el = node;
251
+ const isStructural = STRUCTURAL_HYDRATION_TAGS.has(el.tagName);
252
+ const isFormat = FORMAT_HYDRATION_TAGS.has(el.tagName);
253
+ if (!isStructural && !isFormat)
254
+ return null;
255
+ if (!isHydrationOnlyAttributes(el))
256
+ return null;
257
+ const children = Array.from(el.childNodes);
258
+ let out = '';
259
+ for (const child of children) {
260
+ // Format tags only count as hydration when they wrap the ENTIRE
261
+ // content of their parent. If a <strong>/<em>/<u>/<s> appears
262
+ // alongside any sibling (text or element), it's partial-content
263
+ // formatting — a real edit that can't be expressed via textStyle —
264
+ // so bail.
265
+ if (child.nodeType === 1 &&
266
+ FORMAT_HYDRATION_TAGS.has(child.tagName) &&
267
+ children.length > 1) {
268
+ return null;
269
+ }
270
+ const part = extractCanonicalText(child);
271
+ if (part === null)
272
+ return null;
273
+ out += part;
274
+ }
275
+ return out;
276
+ }
277
+ /**
278
+ * If `value` is TipTap's default-hydration wrapper around plain text
279
+ * (only hydration-noise elements wrapping bare text), return that bare
280
+ * text. Otherwise return the original string unchanged.
281
+ *
282
+ * This collapses the BP-5506 phantom where opening a content sidebar —
283
+ * or selecting a section on the canvas — mounts every RTE editor at
284
+ * once and writes back wrapped HTML with no user-visible delta.
125
285
  */
126
286
  function canonicalizeRteString(value) {
127
- if (EMPTY_RTE_HTML.test(value))
287
+ // Cheap exit: no tags at all.
288
+ if (!value.includes('<'))
289
+ return value;
290
+ if (isEmptyRteHtml(value))
128
291
  return '';
129
- const match = value.match(RTE_DEFAULT_HYDRATION);
130
- if (!match)
292
+ // DOMParser is unavailable in some non-browser test environments;
293
+ // fall back to leaving the value as-is so the caller produces a
294
+ // real diff (i.e. fail open — never falsely collapse).
295
+ if (typeof DOMParser === 'undefined')
296
+ return value;
297
+ let doc;
298
+ try {
299
+ doc = new DOMParser().parseFromString(`<div id="__rte_root__">${value}</div>`, 'text/html');
300
+ }
301
+ catch {
302
+ return value;
303
+ }
304
+ const root = doc.getElementById('__rte_root__');
305
+ if (!root)
131
306
  return value;
132
- return match[6] ?? '';
307
+ let out = '';
308
+ for (const child of Array.from(root.childNodes)) {
309
+ const part = extractCanonicalText(child);
310
+ if (part === null)
311
+ return value;
312
+ out += part;
313
+ }
314
+ return out;
133
315
  }
134
316
  function normalizeNode(node) {
135
317
  if (Array.isArray(node))
@@ -144,6 +326,17 @@ export const useHistoryStore = defineStore('history', () => {
144
326
  // represent a user-visible change.
145
327
  if (key === 'isModifiedAfterTemplateApplied')
146
328
  continue;
329
+ // Strip the rendered logo PNG. Logo property edits (text, color,
330
+ // font, outline, shadow) authored via the Logo sidebar live in
331
+ // `editableLogoStore.logoEdits` — which is NOT in `brandPageData`,
332
+ // so an undo would revert this base64 image on the canvas without
333
+ // touching the sidebar fields. Per V1 scope, logo property edits
334
+ // are non-undoable (parallels the PRD's admin-logo exclusion);
335
+ // BYO-upload undo still works because that change shows up in
336
+ // `logoImage.logoDraftToken`, not here. Stripping the field keeps
337
+ // those isolated render-output diffs out of the stack entirely.
338
+ if (key === 'logoImageBase64Url')
339
+ continue;
147
340
  // Collapse RTE default-hydration wrappers down to the plain text
148
341
  // they wrap. Plain "X" and `<p style="..."><span ...><mark ...>X
149
342
  // </mark></span></p>` share the same canonical "X" so a sidebar
@@ -9171,7 +9171,7 @@ input.dynamic-width-input[data-v-97be2e87]:focus {
9171
9171
  padding: 4px 8px;
9172
9172
  }
9173
9173
 
9174
- .c-quick-links[data-v-56c1c89f] {
9174
+ .c-quick-links[data-v-5d5516b2] {
9175
9175
  left: 0px;
9176
9176
  z-index: 30;
9177
9177
  width: 100%;
@@ -9488,7 +9488,7 @@ input.dynamic-width-input[data-v-97be2e87]:focus {
9488
9488
  padding: 4px 8px;
9489
9489
  }
9490
9490
 
9491
- .c-quick-links[data-v-56c1c89f] {
9491
+ .c-quick-links[data-v-5d5516b2] {
9492
9492
  left: 0px;
9493
9493
  z-index: 30;
9494
9494
  width: 100%;
@@ -9201,7 +9201,7 @@ input.dynamic-width-input[data-v-97be2e87]:focus {
9201
9201
  padding: 4px 8px;
9202
9202
  }
9203
9203
 
9204
- .c-quick-links[data-v-56c1c89f] {
9204
+ .c-quick-links[data-v-5d5516b2] {
9205
9205
  left: 0px;
9206
9206
  z-index: 30;
9207
9207
  width: 100%;
@@ -8899,7 +8899,7 @@ input.dynamic-width-input[data-v-97be2e87]:focus {
8899
8899
  padding: 4px 8px;
8900
8900
  }
8901
8901
 
8902
- .bp .c-quick-links[data-v-56c1c89f] {
8902
+ .bp .c-quick-links[data-v-5d5516b2] {
8903
8903
  left: 0px;
8904
8904
  z-index: 30;
8905
8905
  width: 100%;
package/lib/src/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as E, a as T, b as s, c as I, d as S, e as N, f as e, B as A, g as O, h as C, i as G, j as D, k as P, l as t, m as R, n as L, o as M, p as o, q as r, r as b, s as n, t as F, u as i, v as l, w as g, x as B, y as c, z as Y, C as U, D as d, E as u, F as H, G as p, H as y, I as K, J as k, K as m, L as V, M as X, N as h, O as v, P as W, Q as f, R as x, S as z, T as w, U as Z, V as j, W as Q, X as q, Y as J, Z as $, $ as aa, a0 as _a, a1 as Ea, a2 as Ta, a3 as sa, a4 as Ia, a5 as Sa, a6 as Na, a7 as ea, a8 as Aa, a9 as Oa, aa as Ca, ab as Ga, ac as Da, ad as Pa, ae as ta, af as Ra, ag as La, ah as Ma, ai as oa, aj as ra, ak as ba, al as na, am as Fa, an as ia, ao as la, ap as ga, aq as Ba, ar as ca, as as Ya, at as Ua, au as da, av as ua, aw as Ha, ax as pa, ay as ya, az as Ka, aA as ka, aB as ma, aC as Va, aD as Xa, aE as ha, aF as va, aG as Wa, aH as fa, aI as xa, aJ as za, aK as wa, aL as Za, aM as ja, aN as Qa, aO as qa, aP as Ja, aQ as $a, aR as a_, aS as __, aT as E_, aU as T_, aV as s_, aW as I_, aX as S_, aY as N_, aZ as e_, a_ as A_, a$ as O_, b0 as C_, b1 as G_, b2 as D_, b3 as P_, b4 as t_, b5 as R_, b6 as L_, b7 as M_, b8 as o_, b9 as r_, ba as b_, bb as n_, bc as F_, bd as i_, be as l_, bf as g_, bg as B_, bh as c_, bi as Y_, bj as U_, bk as d_, bl as u_, bm as H_, bn as p_, bo as y_, bp as K_, bq as k_, br as m_, bs as V_, bt as X_, bu as h_, bv as v_, bw as W_, bx as f_, by as x_, bz as z_, bA as w_, bB as Z_, bC as j_, bD as Q_, bE as q_, bF as J_, bG as $_, bH as aE, bI as _E, bJ as EE, bK as TE, bL as sE, bM as IE, bN as SE, bO as NE, bP as eE, bQ as AE, bR as OE, bS as CE, bT as GE, bU as DE, bV as PE, bW as tE, bX as RE, bY as LE, bZ as ME, b_ as oE, b$ as rE, c0 as bE, c1 as nE, c2 as FE, c3 as iE, c4 as lE, c5 as gE, c6 as BE, c7 as cE, c8 as YE, c9 as UE, ca as dE, cb as uE, cc as HE, cd as pE, ce as yE, cf as KE, cg as kE, ch as mE, ci as VE, cj as XE, ck as hE, cl as vE, cm as WE, cn as fE, co as xE } from "./lib-Y0xSZnR9.js";
1
+ import { A as E, a as T, b as s, c as I, d as S, e as N, f as e, B as A, g as O, h as C, i as G, j as D, k as P, l as t, m as R, n as L, o as M, p as o, q as r, r as b, s as n, t as F, u as i, v as l, w as g, x as B, y as c, z as Y, C as U, D as d, E as u, F as H, G as p, H as y, I as K, J as k, K as m, L as V, M as X, N as h, O as v, P as W, Q as f, R as x, S as z, T as w, U as Z, V as j, W as Q, X as q, Y as J, Z as $, $ as aa, a0 as _a, a1 as Ea, a2 as Ta, a3 as sa, a4 as Ia, a5 as Sa, a6 as Na, a7 as ea, a8 as Aa, a9 as Oa, aa as Ca, ab as Ga, ac as Da, ad as Pa, ae as ta, af as Ra, ag as La, ah as Ma, ai as oa, aj as ra, ak as ba, al as na, am as Fa, an as ia, ao as la, ap as ga, aq as Ba, ar as ca, as as Ya, at as Ua, au as da, av as ua, aw as Ha, ax as pa, ay as ya, az as Ka, aA as ka, aB as ma, aC as Va, aD as Xa, aE as ha, aF as va, aG as Wa, aH as fa, aI as xa, aJ as za, aK as wa, aL as Za, aM as ja, aN as Qa, aO as qa, aP as Ja, aQ as $a, aR as a_, aS as __, aT as E_, aU as T_, aV as s_, aW as I_, aX as S_, aY as N_, aZ as e_, a_ as A_, a$ as O_, b0 as C_, b1 as G_, b2 as D_, b3 as P_, b4 as t_, b5 as R_, b6 as L_, b7 as M_, b8 as o_, b9 as r_, ba as b_, bb as n_, bc as F_, bd as i_, be as l_, bf as g_, bg as B_, bh as c_, bi as Y_, bj as U_, bk as d_, bl as u_, bm as H_, bn as p_, bo as y_, bp as K_, bq as k_, br as m_, bs as V_, bt as X_, bu as h_, bv as v_, bw as W_, bx as f_, by as x_, bz as z_, bA as w_, bB as Z_, bC as j_, bD as Q_, bE as q_, bF as J_, bG as $_, bH as aE, bI as _E, bJ as EE, bK as TE, bL as sE, bM as IE, bN as SE, bO as NE, bP as eE, bQ as AE, bR as OE, bS as CE, bT as GE, bU as DE, bV as PE, bW as tE, bX as RE, bY as LE, bZ as ME, b_ as oE, b$ as rE, c0 as bE, c1 as nE, c2 as FE, c3 as iE, c4 as lE, c5 as gE, c6 as BE, c7 as cE, c8 as YE, c9 as UE, ca as dE, cb as uE, cc as HE, cd as pE, ce as yE, cf as KE, cg as kE, ch as mE, ci as VE, cj as XE, ck as hE, cl as vE, cm as WE, cn as fE, co as xE } from "./lib-Bhf5ZHsd.js";
2
2
  import { createPinia as wE, setActivePinia as ZE } from "pinia";
3
3
  export {
4
4
  E as ABOUT_SECTION_SETTINGS_CONFIG,