@designcrowd/library.brand-page 5.18.1-undo-29 → 5.18.1-undo-32
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-BxL6mTJW.js → CreatedOnBrandCrowd-de-de-DUgkmx89.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BPbjqfkj.js → CreatedOnBrandCrowd-en-us-C5dQGLuA.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DDsoe143.js → CreatedOnBrandCrowd-es-es-CjPcnNlq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dak7ppy_.js → CreatedOnBrandCrowd-fr-ca-D2wnZOOE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dak7ppy_.js → CreatedOnBrandCrowd-fr-fr-D2wnZOOE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BUAneEVD.js → CreatedOnBrandCrowd-pt-br-OQj-IBh_.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BUAneEVD.js → CreatedOnBrandCrowd-pt-pt-OQj-IBh_.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BJ00bTz_.js → CreatedOnDcom-de-de-DLijuIaL.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-B0V1M9Hs.js → CreatedOnDcom-en-us-D9M_heGE.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-YDMLB7u6.js → CreatedOnDcom-es-es-PgWwbBLA.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DppQ26s1.js → CreatedOnDcom-fr-ca-BXiB-abj.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-0Xi-3slB.js → CreatedOnDcom-fr-fr-BXhTmu7F.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-De-wHqSu.js → CreatedOnDcom-pt-br-3e6y567w.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-CQGAsn5D.js → CreatedOnDcom-pt-pt-GojOtHmO.js} +1 -1
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.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/components/seo-settings/UrlSettings.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +1 -1
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +46 -22
- package/lib/src/brand-page-maker/stores/history/history-store.js +66 -66
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-YRlMax_i.js → lib-DVs3jUit.js} +18674 -18654
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +65 -1
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +12 -0
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +2 -1
- package/lib/src/shared/logo-settings/store/editable-logo-store.d.ts +14 -0
- package/lib/src/shared/logo-settings/store/editable-logo-store.js +26 -10
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +2 -1
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -1
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +1 -1
- package/package.json +1 -1
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +53 -23
- package/src/brand-page-maker/stores/history/history-store.test.ts +29 -135
- package/src/brand-page-maker/stores/history/history-store.ts +85 -76
- package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +12 -0
- package/src/shared/logo-settings/store/editable-logo-store.ts +28 -11
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { defineStore } from 'pinia';
|
|
2
|
-
import { computed,
|
|
2
|
+
import { computed, ref, shallowRef, toRaw, watch } from 'vue';
|
|
3
3
|
import type { BrandPageModel } from '@brand-page';
|
|
4
4
|
import { useBrandPageMakerStore } from '../brandpage-maker/brandpage-maker-store';
|
|
5
|
+
import { useBrandPageContainerStore } from '../brandpage-container';
|
|
5
6
|
import { useAiWebsiteBuilderThreadStore } from '../ai-thread/ai-website-builder-thread-store';
|
|
6
7
|
import { useEventTracker } from '../../composables/useEventTracker';
|
|
7
8
|
import { usePageNavigationStore } from '@shared/page-navigation/store/page-navigation-store';
|
|
8
9
|
import { useUiEditableLogoStore } from '@shared/logo-settings/store/editable-logo-store';
|
|
9
|
-
import { useShopConfigStore } from '@shared/ecommerce-settings/store/shop-settings-store';
|
|
10
|
-
import { useUiPageSettingsStore } from '@shared/page-settings/store';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* A single history entry.
|
|
@@ -121,23 +120,6 @@ const MAX_ENTRY_BYTES = 25 * 1024 * 1024;
|
|
|
121
120
|
// that would otherwise make undo light up before the user has done anything.
|
|
122
121
|
const BASELINE_SETTLE_MS = 2000;
|
|
123
122
|
|
|
124
|
-
// Content-level wrapper keys whose `paymentConfig` child is a denormalised
|
|
125
|
-
// cache of `shopConfigStore.paymentConfig` (the user's Stripe Connect
|
|
126
|
-
// linkage). Stripe Connect linkage state is backend-confirmed (per
|
|
127
|
-
// `project-guardrails.md`) and must NEVER ride along on the undo/redo stack.
|
|
128
|
-
//
|
|
129
|
-
// paymentConfig is kept invariant under undo/redo by re-stamping every live
|
|
130
|
-
// wrapper from `shopConfigStore.paymentConfig` after every restore — see
|
|
131
|
-
// `restorePaymentConfigsOnLivePages`. The shop store itself is never
|
|
132
|
-
// touched by undo/redo (see `applyHistorySnapshot` step 6), so the live
|
|
133
|
-
// wrapper always equals the live shop store regardless of what's in the
|
|
134
|
-
// snapshot bytes.
|
|
135
|
-
const PAYMENT_CONFIG_WRAPPER_KEYS = [
|
|
136
|
-
'supportMeConfig',
|
|
137
|
-
'requestPaymentConfig',
|
|
138
|
-
'shopPaymentConfig',
|
|
139
|
-
] as const;
|
|
140
|
-
|
|
141
123
|
export const useHistoryStore = defineStore('history', () => {
|
|
142
124
|
const eventTracker = useEventTracker();
|
|
143
125
|
|
|
@@ -172,6 +154,65 @@ export const useHistoryStore = defineStore('history', () => {
|
|
|
172
154
|
*/
|
|
173
155
|
let cachedMakerStore: ReturnType<typeof useBrandPageMakerStore> | null = null;
|
|
174
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Walk `brandPageData` and null `logoImageBase64Url` on every logo
|
|
159
|
+
* placement (header/footer × desktop/mobile + per-page logo content).
|
|
160
|
+
* Pre-purchase only — the same gate the save path uses
|
|
161
|
+
* (see `brand-page-container-store.ts:805`).
|
|
162
|
+
*
|
|
163
|
+
* Each rendered base64 is ~2-4 MB and the brand-site templates put the
|
|
164
|
+
* same logo in 6-8 places, so unstripped pre-purchase snapshots run
|
|
165
|
+
* 18-24 MB and bump the 25 MB per-entry cap, which silently drops the
|
|
166
|
+
* entry. The base64 is repopulated on undo by `_syncLogoSettingsFromSnapshot`
|
|
167
|
+
* (designed: re-issue fabric-render; BYO: stamp from per-token cache),
|
|
168
|
+
* so the snapshot doesn't need to carry it.
|
|
169
|
+
*/
|
|
170
|
+
function stripLogoBase64IfPrePurchase(data: {
|
|
171
|
+
pages?: { contents?: { logoImageBase64Url?: string }[] }[];
|
|
172
|
+
pageHeaderDesktop?: { logoImageBase64Url?: string };
|
|
173
|
+
pageHeaderMobile?: { logoImageBase64Url?: string };
|
|
174
|
+
pageFooterDesktop?: { logoImageBase64Url?: string };
|
|
175
|
+
pageFooterMobile?: { logoImageBase64Url?: string };
|
|
176
|
+
}): void {
|
|
177
|
+
try {
|
|
178
|
+
if (!useBrandPageContainerStore().isPrePurchase) return;
|
|
179
|
+
} catch {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (data.pageHeaderDesktop) {
|
|
184
|
+
data.pageHeaderDesktop.logoImageBase64Url = undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (data.pageHeaderMobile) {
|
|
188
|
+
data.pageHeaderMobile.logoImageBase64Url = undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (data.pageFooterDesktop) {
|
|
192
|
+
data.pageFooterDesktop.logoImageBase64Url = undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (data.pageFooterMobile) {
|
|
196
|
+
data.pageFooterMobile.logoImageBase64Url = undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (Array.isArray(data.pages)) {
|
|
200
|
+
for (const page of data.pages) {
|
|
201
|
+
if (!Array.isArray(page?.contents)) continue;
|
|
202
|
+
|
|
203
|
+
for (const content of page.contents) {
|
|
204
|
+
if (
|
|
205
|
+
content &&
|
|
206
|
+
typeof content === 'object' &&
|
|
207
|
+
'logoImageBase64Url' in content
|
|
208
|
+
) {
|
|
209
|
+
content.logoImageBase64Url = undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
175
216
|
/**
|
|
176
217
|
* Serialize the current `brandPageData` into a detached JSON string.
|
|
177
218
|
* Synchronous — safe to call inside a microtask without racing a follow-up
|
|
@@ -181,7 +222,13 @@ export const useHistoryStore = defineStore('history', () => {
|
|
|
181
222
|
if (!cachedMakerStore) return null;
|
|
182
223
|
|
|
183
224
|
try {
|
|
184
|
-
|
|
225
|
+
const cloned = JSON.parse(
|
|
226
|
+
JSON.stringify(toRaw(cachedMakerStore.brandPageData)),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
stripLogoBase64IfPrePurchase(cloned);
|
|
230
|
+
|
|
231
|
+
return JSON.stringify(cloned);
|
|
185
232
|
} catch (error) {
|
|
186
233
|
console.error('history-store: failed to serialize snapshot', error);
|
|
187
234
|
|
|
@@ -276,7 +323,20 @@ export const useHistoryStore = defineStore('history', () => {
|
|
|
276
323
|
}): 'committed' | 'oversized' | 'noop' {
|
|
277
324
|
const current = entries.value[currentIndex.value];
|
|
278
325
|
|
|
279
|
-
|
|
326
|
+
// Compare both the snapshot bytes AND the logoEdits sidecar.
|
|
327
|
+
// Pre-purchase logo-tab edits (text/color/font) live in the
|
|
328
|
+
// `editableLogoStore.logoEdits` sidecar, NOT in `brandPageData`.
|
|
329
|
+
// The accompanying base64 writeback is also stripped from the
|
|
330
|
+
// snapshot (size cap), so a pure logo edit would produce a
|
|
331
|
+
// byte-identical snapshot to the previous entry — without the
|
|
332
|
+
// sidecar comparison every logo edit would dedup as a no-op.
|
|
333
|
+
if (
|
|
334
|
+
current &&
|
|
335
|
+
current.snapshot === args.snapshot &&
|
|
336
|
+
current.logoEdits === args.logoEdits
|
|
337
|
+
) {
|
|
338
|
+
return 'noop';
|
|
339
|
+
}
|
|
280
340
|
|
|
281
341
|
if (args.snapshot.length > MAX_ENTRY_BYTES) {
|
|
282
342
|
eventTracker.sendHistoryOversizedEvent({
|
|
@@ -571,9 +631,9 @@ export const useHistoryStore = defineStore('history', () => {
|
|
|
571
631
|
* dedicated `applyHistorySnapshot` action, which rehydrates every Pinia
|
|
572
632
|
* source store + own-store scalar fields that `brandPageData` reads from.
|
|
573
633
|
*
|
|
574
|
-
* Holds `isApplyingSnapshot`
|
|
575
|
-
*
|
|
576
|
-
*
|
|
634
|
+
* Holds `isApplyingSnapshot` for the duration of the apply so the
|
|
635
|
+
* watcher guard suppresses the cascade of mutations from being
|
|
636
|
+
* re-captured as a spurious duplicate entry.
|
|
577
637
|
*
|
|
578
638
|
* Active-page resolution priority:
|
|
579
639
|
* 1. Diff against `cameFromIndex` — any page present in the target
|
|
@@ -671,62 +731,11 @@ export const useHistoryStore = defineStore('history', () => {
|
|
|
671
731
|
resolvedActivePageId,
|
|
672
732
|
targetEntry?.logoEdits,
|
|
673
733
|
);
|
|
674
|
-
await nextTick();
|
|
675
|
-
|
|
676
|
-
// Re-stamp `wrapper.paymentConfig` on every payment-config wrapper in
|
|
677
|
-
// the freshly-restored content tree from the live shop store. Stripe
|
|
678
|
-
// Connect linkage is backend-confirmed (per `project-guardrails.md`)
|
|
679
|
-
// and must NEVER come from the snapshot — the captured value can be
|
|
680
|
-
// arbitrarily stale (an entry captured before
|
|
681
|
-
// `PaymentConfigField.mixin.ts:created()` lazily filled in the
|
|
682
|
-
// wrapper carries `paymentConfig: undefined`) and using it would
|
|
683
|
-
// re-surface the "Please enable a payment provider" warning on
|
|
684
|
-
// undo. The shop store's value is itself never undoable (see
|
|
685
|
-
// `applyHistorySnapshot` step 6).
|
|
686
|
-
restorePaymentConfigsOnLivePages();
|
|
687
734
|
} finally {
|
|
688
735
|
isApplyingSnapshot.value = false;
|
|
689
736
|
}
|
|
690
737
|
}
|
|
691
738
|
|
|
692
|
-
/**
|
|
693
|
-
* Walk the live `pageSettingsStore.pages` and stamp
|
|
694
|
-
* `wrapper.paymentConfig = shopConfigStore.paymentConfig` on every
|
|
695
|
-
* payment-config wrapper. Called from `applySnapshotString` after
|
|
696
|
-
* `applyHistorySnapshot` rebuilds page contents.
|
|
697
|
-
*/
|
|
698
|
-
function restorePaymentConfigsOnLivePages(): void {
|
|
699
|
-
try {
|
|
700
|
-
const shopStore = useShopConfigStore();
|
|
701
|
-
const pageSettingsStore = useUiPageSettingsStore();
|
|
702
|
-
const livePaymentConfig = shopStore.paymentConfig;
|
|
703
|
-
|
|
704
|
-
for (const page of pageSettingsStore.pages) {
|
|
705
|
-
for (const item of page.contents) {
|
|
706
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
707
|
-
const content: any = item.settings.content;
|
|
708
|
-
|
|
709
|
-
for (const key of PAYMENT_CONFIG_WRAPPER_KEYS) {
|
|
710
|
-
const wrapper = content?.[key];
|
|
711
|
-
|
|
712
|
-
if (
|
|
713
|
-
wrapper &&
|
|
714
|
-
typeof wrapper === 'object' &&
|
|
715
|
-
!Array.isArray(wrapper)
|
|
716
|
-
) {
|
|
717
|
-
wrapper.paymentConfig = livePaymentConfig;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
} catch (error) {
|
|
723
|
-
console.error(
|
|
724
|
-
'history-store: failed to restore paymentConfig on live pages',
|
|
725
|
-
error,
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
739
|
/** Move the pointer back one step and apply the entry at the new position. */
|
|
731
740
|
async function undo(): Promise<void> {
|
|
732
741
|
if (!canUndo.value) return;
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from '@brand-page-maker/utils';
|
|
30
30
|
import { useUiStyleSettingsStore } from '../../../style-settings/store';
|
|
31
31
|
import { usePageNavigationStore } from '../../../../shared/page-navigation/store/page-navigation-store';
|
|
32
|
+
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
32
33
|
|
|
33
34
|
interface CategoryTab {
|
|
34
35
|
category: string;
|
|
@@ -44,6 +45,7 @@ export default defineComponent({
|
|
|
44
45
|
const styleSettingsStore = useUiStyleSettingsStore();
|
|
45
46
|
const userStore = useBrandPageMakerUserStore();
|
|
46
47
|
const eventTracker = useEventTracker();
|
|
48
|
+
const historyStore = useHistoryStore();
|
|
47
49
|
const { externalEditor } = useExternalRichTextEditor();
|
|
48
50
|
|
|
49
51
|
return {
|
|
@@ -65,6 +67,7 @@ export default defineComponent({
|
|
|
65
67
|
pageSettingsStore,
|
|
66
68
|
styleSettingsStore,
|
|
67
69
|
eventTracker,
|
|
70
|
+
historyStore,
|
|
68
71
|
externalEditor,
|
|
69
72
|
};
|
|
70
73
|
},
|
|
@@ -81,6 +84,15 @@ export default defineComponent({
|
|
|
81
84
|
},
|
|
82
85
|
mounted() {
|
|
83
86
|
if (this.canUseRichTextEditor) {
|
|
87
|
+
// `switchAllContentToRTE` walks every content item on every page
|
|
88
|
+
// and rewraps plain-text titles/paragraphs in TipTap HTML so they
|
|
89
|
+
// render through the inline RTE. That's a hydration write — it
|
|
90
|
+
// happens whenever the Content sidebar tab mounts (incl. when the
|
|
91
|
+
// user clicks the header in the canvas) and produces no
|
|
92
|
+
// user-visible change. Arm the merge flag so the watcher folds
|
|
93
|
+
// the resulting flush into the current head entry instead of
|
|
94
|
+
// pushing a phantom undo step.
|
|
95
|
+
this.historyStore.mergeNextMutation();
|
|
84
96
|
switchAllContentToRTE(
|
|
85
97
|
this.pageSettingsStore,
|
|
86
98
|
this.externalEditor,
|
|
@@ -413,6 +413,34 @@ export const useUiEditableLogoStore = defineStore('uiEditableLogo', {
|
|
|
413
413
|
|
|
414
414
|
await Promise.all(tasks);
|
|
415
415
|
|
|
416
|
+
this.stampLogoBase64IntoBrandPageData(overrideLogoVariation);
|
|
417
|
+
|
|
418
|
+
// Stash the freshly rendered variation set under the active draft
|
|
419
|
+
// token so undo can restore it later without re-rendering. This is
|
|
420
|
+
// the only place in production that meaningfully populates
|
|
421
|
+
// `editableLogoVariations` (BYO upload + designed-logo setStateAsync
|
|
422
|
+
// both funnel here), so caching here keeps every distinct draft's
|
|
423
|
+
// grid available for the lifetime of the session.
|
|
424
|
+
if (this.logoDraftToken) {
|
|
425
|
+
this.variationsByDraftToken[this.logoDraftToken] =
|
|
426
|
+
this.editableLogoVariations.map((v) => ({ ...v }));
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Stamp `editableLogoVariations[*].base64` across every consumer in
|
|
432
|
+
* `brandPageData`: header/footer logo sections (desktop + mobile)
|
|
433
|
+
* and every `LogoContent` block on every page. Used by the live edit
|
|
434
|
+
* path after a fabric-render POST AND by the undo/redo BYO branch
|
|
435
|
+
* after `restoreVariationsForDraftToken` has rehydrated the cache.
|
|
436
|
+
*
|
|
437
|
+
* The history store strips `logoImageBase64Url` from snapshots to
|
|
438
|
+
* keep them under the 25 MB per-entry cap, so something has to
|
|
439
|
+
* write the base64 back into `brandPageData` after restore — for
|
|
440
|
+
* designed logos it's the awaited fabric-render call; for BYO it's
|
|
441
|
+
* this method.
|
|
442
|
+
*/
|
|
443
|
+
stampLogoBase64IntoBrandPageData(overrideLogoVariation?: number): void {
|
|
416
444
|
const pageSettingStore = useUiPageSettingsStore();
|
|
417
445
|
|
|
418
446
|
[PAGE_SECTIONS.header, PAGE_SECTIONS.footer].forEach((section) => {
|
|
@@ -449,17 +477,6 @@ export const useUiEditableLogoStore = defineStore('uiEditableLogo', {
|
|
|
449
477
|
}
|
|
450
478
|
});
|
|
451
479
|
});
|
|
452
|
-
|
|
453
|
-
// Stash the freshly rendered variation set under the active draft
|
|
454
|
-
// token so undo can restore it later without re-rendering. This is
|
|
455
|
-
// the only place in production that meaningfully populates
|
|
456
|
-
// `editableLogoVariations` (BYO upload + designed-logo setStateAsync
|
|
457
|
-
// both funnel here), so caching here keeps every distinct draft's
|
|
458
|
-
// grid available for the lifetime of the session.
|
|
459
|
-
if (this.logoDraftToken) {
|
|
460
|
-
this.variationsByDraftToken[this.logoDraftToken] =
|
|
461
|
-
this.editableLogoVariations.map((v) => ({ ...v }));
|
|
462
|
-
}
|
|
463
480
|
},
|
|
464
481
|
|
|
465
482
|
/**
|