@designcrowd/library.brand-page 6.0.25 → 6.0.26-json-prereq-2
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-CS3ct0wa.js → CreatedOnBrandCrowd-de-de-CKBqjDH3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CmBcnDu6.js → CreatedOnBrandCrowd-en-us-CRXiimvT.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BdTuJtqV.js → CreatedOnBrandCrowd-es-es-DCYdY6sj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cx_H9X6l.js → CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BZZ9hjUi.js → CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-SbONDQN1.js → CreatedOnDcom-de-de-ZSLkWMwM.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-Beno1_gw.js → CreatedOnDcom-en-us-BgdIGfT3.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BBE_zdRu.js → CreatedOnDcom-es-es-D0KemkCZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-C6pgDB5x.js → CreatedOnDcom-fr-ca-BuwNjNbC.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-FD1eqtg3.js → CreatedOnDcom-fr-fr-DCQcsFPf.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BiSx3OwM.js → CreatedOnDcom-pt-br-DD-oLAS1.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-Dn4DmKGA.js → CreatedOnDcom-pt-pt-D8qLq2wW.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +5 -2
- package/lib/src/brand-page/models/index.d.ts +34 -26
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +2 -0
- package/lib/src/brand-page/utils/migration/migration-20.js +66 -0
- package/lib/src/brand-page/utils/migration.util.js +11 -0
- package/lib/src/brand-page/utils/text.util.d.ts +1 -1
- package/lib/src/brand-page/utils/text.util.js +32 -0
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
- package/lib/src/brand-page/utils/view-model.util.js +68 -20
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +22 -0
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +22 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.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/ai-chat/useAiMetadataBuilder.d.ts +0 -1
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +21 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +2 -9
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +6 -10
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +14 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +39 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +28 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +90 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +24 -103
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +8 -2
- package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +20 -0
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +7 -0
- package/lib/src/brand-page-maker/utils/index.d.ts +2 -0
- package/lib/src/brand-page-maker/utils/index.js +2 -0
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +28 -5
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +48 -5
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +63 -0
- package/lib/src/brand-page-maker/utils/rte-render.util.js +95 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +6 -0
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +158 -0
- package/lib/src/brand-page-maker/validators/field.validator.js +10 -2
- package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
- package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
- package/lib/src/css/library.brand-page-designCom.min.css +39 -39
- package/lib/src/css/library.brand-page-preview.min.css +39 -39
- package/lib/src/index.d.ts +1 -0
- package/lib/src/index.mjs +143 -138
- package/lib/src/{lib-BVHFYSLL.js → lib-Db6j9fQZ.js} +56262 -52768
- package/lib/src/lib.js +2 -0
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +0 -1
- package/lib/src/metadata-builder/ai-metadata-executor.js +0 -19
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +3 -4
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +3 -4
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +2 -3
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +1 -2
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +15 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +11 -13
- package/lib/src/metadata-builder/helpers/rte.helpers.js +14 -73
- package/lib/src/metadata-builder/metadata-executor.js +0 -4
- package/lib/src/metadata-builder/models.d.ts +0 -2
- package/lib/src/metadata-builder/models.js +0 -2
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +0 -19
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +0 -4
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +7 -8
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +21 -52
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +14 -1
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
- 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.js +12 -3
- package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/radio-field/RadioField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +3 -2
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +10 -3
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +4 -1
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +2 -2
- package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +2 -2
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +1 -1
- package/lib/src/utils/minify-brand-page-metadata.js +4 -1
- package/package.json +17 -15
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +5 -2
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +17 -6
- package/src/brand-page/models/index.ts +35 -26
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/migration/migration-20.server.test.ts +125 -0
- package/src/brand-page/utils/migration/migration-20.test.ts +72 -0
- package/src/brand-page/utils/migration/migration-20.ts +87 -0
- package/src/brand-page/utils/migration.util.ts +12 -0
- package/src/brand-page/utils/style.util.test.ts +51 -0
- package/src/brand-page/utils/text.util.test.ts +47 -0
- package/src/brand-page/utils/text.util.ts +43 -4
- package/src/brand-page/utils/vcard.util.ts +6 -4
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +104 -0
- package/src/brand-page/utils/view-model.util.ts +94 -26
- package/src/brand-page/view-models.ts +3 -2
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +23 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +4 -3
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +0 -1
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +28 -9
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +6 -10
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +52 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +42 -0
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +137 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +27 -111
- package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +23 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +8 -0
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +202 -0
- package/src/brand-page-maker/utils/index.ts +2 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +37 -3
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +66 -11
- package/src/brand-page-maker/utils/rte-features.test.ts +351 -0
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +157 -0
- package/src/brand-page-maker/utils/rte-render.util.ts +165 -0
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +158 -0
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +195 -0
- package/src/brand-page-maker/validators/field.validator.test.ts +55 -0
- package/src/brand-page-maker/validators/field.validator.ts +10 -2
- package/src/lib.ts +9 -0
- package/src/metadata-builder/ai-metadata-executor.ts +0 -26
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +2 -5
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +3 -4
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +3 -6
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +2 -3
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +1 -2
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +19 -13
- package/src/metadata-builder/helpers/rte.helpers.ts +15 -101
- package/src/metadata-builder/metadata-executor.ts +0 -5
- package/src/metadata-builder/models.ts +0 -3
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +0 -27
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +24 -53
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +17 -1
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +16 -3
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +13 -4
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +6 -4
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +6 -3
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +6 -1
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/utils/minify-brand-page-metadata.ts +4 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +0 -20
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +0 -44
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +0 -27
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +0 -44
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +0 -17
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +0 -103
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +0 -16
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +0 -216
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +0 -73
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +0 -85
- package/src/metadata-builder/builders/update-content-text-builder.ts +0 -148
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +0 -352
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BP-5787 — golden-master fixture for the RTE HTML→JSON migration.
|
|
3
|
+
*
|
|
4
|
+
* A single rich-text value that exercises every mark/node the editor can emit,
|
|
5
|
+
* so one snapshot guards the whole serialisation surface. Captured as a baseline
|
|
6
|
+
* on the current HTML pipeline; after the migration the JSON pipeline must
|
|
7
|
+
* reproduce byte-identical rendered output (see rte-render-parity.test.ts).
|
|
8
|
+
*
|
|
9
|
+
* Coverage:
|
|
10
|
+
* - block nodes: heading (h2), paragraph, text-align (center / right)
|
|
11
|
+
* - inline marks: bold, italic, underline, strike
|
|
12
|
+
* - textStyle attrs: fontFamily, fontSize (em), letterSpacing (em), lineHeight (unitless)
|
|
13
|
+
* - colour: theme TOKEN ref (var(--logo-text-color)) AND a literal hex (#FF0000)
|
|
14
|
+
* - highlight (background colour via <mark>)
|
|
15
|
+
* - links: internal (target=_self) and external (target=_blank rel=noopener)
|
|
16
|
+
*/
|
|
17
|
+
export const COMPLEX_RTE_HTML = `<h2 style="text-align: center"><span style="color: var(--logo-text-color)">Themed heading</span></h2><p><strong>bold</strong> <em>italic</em> <u>underline</u> <s>strike</s> <span style="font-family: Montserrat; font-size: 1.5em; letter-spacing: 0.05em; line-height: 1.4">styled span</span> <mark data-color="#ffe08a" style="background-color: #ffe08a">highlight</mark> <span style="color: #FF0000">literal hex</span> <a target="_self" href="/about">internal link</a> <a target="_blank" rel="noopener noreferrer nofollow" href="https://example.com">external link</a></p><p style="text-align: right">second paragraph, right-aligned</p>`;
|
|
18
|
+
/**
|
|
19
|
+
* The theme-token CSS variable that MUST survive the HTML→JSON round-trip
|
|
20
|
+
* (BP-5787 stores token refs instead of flattening them to rgb()).
|
|
21
|
+
*/
|
|
22
|
+
export const THEME_TOKEN_REF = 'var(--logo-text-color)';
|
|
@@ -59,7 +59,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
59
59
|
selectedTabName: import("vue").ComputedRef<string>;
|
|
60
60
|
isAdmin: import("vue").ComputedRef<boolean>;
|
|
61
61
|
contentErrorCount: import("vue").ComputedRef<number>;
|
|
62
|
-
mode: import("vue").ComputedRef<"images" | "
|
|
62
|
+
mode: import("vue").ComputedRef<"images" | "add" | "pages" | "edit" | "focusedContentItem">;
|
|
63
63
|
brandPageMakerStore: import("pinia").Store<"brandPageMaker", import("@brand-page-maker/stores").BrandPageMakerState, {
|
|
64
64
|
contentErrorCount(): number;
|
|
65
65
|
brandPageBaseUrl(): string;
|
|
@@ -480,9 +480,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
480
480
|
}>> & Readonly<{
|
|
481
481
|
"onUpdate-settings"?: ((...args: any[]) => any) | undefined;
|
|
482
482
|
}>, {
|
|
483
|
+
brandPageType: string;
|
|
483
484
|
pageTitle: string;
|
|
484
485
|
designDraftToken: string;
|
|
485
|
-
brandPageType: string;
|
|
486
486
|
pageDescription: string;
|
|
487
487
|
isMaker: boolean;
|
|
488
488
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -21,7 +21,6 @@ export interface AiMetadataBuilderResult {
|
|
|
21
21
|
}
|
|
22
22
|
export declare function useAiMetadataBuilder(logger: Logger): {
|
|
23
23
|
applyAiToolCallsAsync: (model: BrandPageModel, aiAssistantToolCall: AiAssistantToolCall[], options?: {
|
|
24
|
-
maxCharacters?: number;
|
|
25
24
|
/**
|
|
26
25
|
* Invoked by the UndoBuilder to resolve the pre-prompt snapshot.
|
|
27
26
|
* Required when the response includes an `undo` tool call.
|
|
@@ -36,18 +36,17 @@ export function useAiWebsiteBuilderChat() {
|
|
|
36
36
|
const aiMetadataBuilder = useAiMetadataBuilder(aiLogger);
|
|
37
37
|
const checkpointStore = useAiCheckpointStore();
|
|
38
38
|
const eventTracker = useEventTracker();
|
|
39
|
+
const createWelcomeMessage = () => ({
|
|
40
|
+
id: '1',
|
|
41
|
+
type: 'text',
|
|
42
|
+
role: 'agent',
|
|
43
|
+
text: 'What changes would you like to make to your website?',
|
|
44
|
+
timestamp: new Date(),
|
|
45
|
+
});
|
|
39
46
|
const hasStoredMessages = aiThreadStore.messages.length > 0;
|
|
40
47
|
const threadItems = ref(aiThreadStore.messages.length > 0
|
|
41
48
|
? aiThreadStore.messages
|
|
42
|
-
: [
|
|
43
|
-
{
|
|
44
|
-
id: '1',
|
|
45
|
-
type: 'text',
|
|
46
|
-
role: 'agent',
|
|
47
|
-
text: 'What changes would you like to make to your website?',
|
|
48
|
-
timestamp: new Date(),
|
|
49
|
-
},
|
|
50
|
-
]);
|
|
49
|
+
: [createWelcomeMessage()]);
|
|
51
50
|
// ---------------------------------------------------------------------------
|
|
52
51
|
// Undo snapshot lifecycle (WL9) — reuses the existing `checkpointStore`,
|
|
53
52
|
// which stores pre-prompt BrandPageModel snapshots keyed by user message id.
|
|
@@ -100,6 +99,19 @@ export function useAiWebsiteBuilderChat() {
|
|
|
100
99
|
const isStopped = ref(false);
|
|
101
100
|
const generatedText = ref(null);
|
|
102
101
|
const generatedStyle = ref(null);
|
|
102
|
+
// External reset (e.g. template switch, BP-5896): tear down the build WS
|
|
103
|
+
// client so a late push can't repopulate the fresh thread, then re-seed
|
|
104
|
+
// the welcome bubble. Keyed off `resetToken` — NOT `messages` — to avoid
|
|
105
|
+
// looping with the deep `threadItems` → `messages` watch above.
|
|
106
|
+
watch(() => aiThreadStore.resetToken, () => {
|
|
107
|
+
if (activeBuildWsClient) {
|
|
108
|
+
activeBuildWsClient.disconnect();
|
|
109
|
+
activeBuildWsClient = null;
|
|
110
|
+
}
|
|
111
|
+
currentTypingMessageId.value = null;
|
|
112
|
+
isStopped.value = false;
|
|
113
|
+
threadItems.value = [createWelcomeMessage()];
|
|
114
|
+
});
|
|
103
115
|
const SERVICE_UNAVAILABLE_MESSAGE = 'This service is not available right now. Please try again later.';
|
|
104
116
|
const addServiceUnavailableBubble = () => {
|
|
105
117
|
threadItems.value.push({
|
|
@@ -5,15 +5,8 @@ export declare const useBubbleMenu: (editor: Editor, isHyperlinkPopupOpen?: Ref<
|
|
|
5
5
|
editor: any;
|
|
6
6
|
state: any;
|
|
7
7
|
}) => any;
|
|
8
|
-
|
|
9
|
-
duration: number;
|
|
8
|
+
bubbleMenuOptions: {
|
|
10
9
|
placement: "bottom";
|
|
11
|
-
|
|
12
|
-
appendTo: "parent";
|
|
13
|
-
hideOnClick: boolean;
|
|
14
|
-
arrow: boolean;
|
|
15
|
-
offset: [number, number];
|
|
16
|
-
trigger: "manual";
|
|
17
|
-
zIndex: number;
|
|
10
|
+
offset: number;
|
|
18
11
|
};
|
|
19
12
|
};
|
|
@@ -13,19 +13,15 @@ export const useBubbleMenu = (editor, isHyperlinkPopupOpen) => {
|
|
|
13
13
|
}
|
|
14
14
|
return false;
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// v3 BubbleMenu uses Floating UI (was tippy.js in v2). Only placement + offset
|
|
17
|
+
// carry over; the rest of the old tippy config (trigger/interactive/hideOnClick/
|
|
18
|
+
// zIndex) is either the v3 default or handled in CSS.
|
|
19
|
+
const bubbleMenuOptions = {
|
|
18
20
|
placement: 'bottom',
|
|
19
|
-
|
|
20
|
-
appendTo: 'parent',
|
|
21
|
-
hideOnClick: false,
|
|
22
|
-
arrow: false,
|
|
23
|
-
offset: [0, 10],
|
|
24
|
-
trigger: 'manual',
|
|
25
|
-
zIndex: 10,
|
|
21
|
+
offset: 10,
|
|
26
22
|
};
|
|
27
23
|
return {
|
|
28
24
|
shouldShow,
|
|
29
|
-
|
|
25
|
+
bubbleMenuOptions,
|
|
30
26
|
};
|
|
31
27
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BP-5787 — font-family with quote-stripping parse.
|
|
3
|
+
*
|
|
4
|
+
* The stock extension stores whatever `element.style.fontFamily` returns. For
|
|
5
|
+
* multi-word families (e.g. "Open Sans", "Playwrite AU") the browser quotes the
|
|
6
|
+
* value, so parsing HTML — pasting RTE-styled text between editors, or the
|
|
7
|
+
* HTML→JSON migration — yields a value that still contains the quote chars
|
|
8
|
+
* (`"Open Sans"`). That value then (a) no longer matches the font list and,
|
|
9
|
+
* worse, (b) is dropped by the native RTE sanitiser (`sanitizeRteJson`), whose
|
|
10
|
+
* `isSafeCssValue` rejects any value containing `"` `'` `;` — so the font is
|
|
11
|
+
* silently lost. Mirror the FontSize/LetterSpacing extensions and strip the
|
|
12
|
+
* quotes on parse so the stored value is always the bare family name.
|
|
13
|
+
*/
|
|
14
|
+
export declare const FontFamily: import("@tiptap/core").Extension<import("@tiptap/extension-text-style").FontFamilyOptions, any>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import TiptapFontFamily from '@tiptap/extension-font-family';
|
|
2
|
+
/**
|
|
3
|
+
* BP-5787 — font-family with quote-stripping parse.
|
|
4
|
+
*
|
|
5
|
+
* The stock extension stores whatever `element.style.fontFamily` returns. For
|
|
6
|
+
* multi-word families (e.g. "Open Sans", "Playwrite AU") the browser quotes the
|
|
7
|
+
* value, so parsing HTML — pasting RTE-styled text between editors, or the
|
|
8
|
+
* HTML→JSON migration — yields a value that still contains the quote chars
|
|
9
|
+
* (`"Open Sans"`). That value then (a) no longer matches the font list and,
|
|
10
|
+
* worse, (b) is dropped by the native RTE sanitiser (`sanitizeRteJson`), whose
|
|
11
|
+
* `isSafeCssValue` rejects any value containing `"` `'` `;` — so the font is
|
|
12
|
+
* silently lost. Mirror the FontSize/LetterSpacing extensions and strip the
|
|
13
|
+
* quotes on parse so the stored value is always the bare family name.
|
|
14
|
+
*/
|
|
15
|
+
export const FontFamily = TiptapFontFamily.extend({
|
|
16
|
+
addGlobalAttributes() {
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
types: this.options.types,
|
|
20
|
+
attributes: {
|
|
21
|
+
fontFamily: {
|
|
22
|
+
default: null,
|
|
23
|
+
// Strip quotes only — keep '' for an absent value so the stored
|
|
24
|
+
// JSON shape matches the other textStyle attrs (FontSize etc.).
|
|
25
|
+
parseHTML: (element) => element.style.fontFamily?.replace(/['"]+/g, ''),
|
|
26
|
+
renderHTML: (attributes) => {
|
|
27
|
+
if (!attributes.fontFamily) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
style: `font-family: ${attributes.fontFamily}`,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Extensions } from '@tiptap/vue-3';
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for the Tiptap extension set (BP-5787).
|
|
4
|
+
*
|
|
5
|
+
* The live editor, the headless serialiser (useExternalRichTextEditor), and
|
|
6
|
+
* the upcoming render-from-JSON path must all use the SAME extensions, or
|
|
7
|
+
* rendered output drifts from what the user authored. This factory removes the
|
|
8
|
+
* previous duplication while preserving each consumer's exact configuration.
|
|
9
|
+
*
|
|
10
|
+
* Known, intentional differences are parameterised:
|
|
11
|
+
* - `restrictStarterKit` — the live editor disables list/hr/code nodes.
|
|
12
|
+
* - `includeLinkRel` — the headless/render path emits rel="noopener noreferrer";
|
|
13
|
+
* the live editing surface does not.
|
|
14
|
+
* - `editing` — only the live editor carries the Placeholder + MaxLength plugins
|
|
15
|
+
* (neither affects serialised output; they are editing-time only).
|
|
16
|
+
*/
|
|
17
|
+
export interface RteExtensionOptions {
|
|
18
|
+
/** Disable StarterKit nodes the editor doesn't use (lists, hr, code). */
|
|
19
|
+
restrictStarterKit?: boolean;
|
|
20
|
+
/** Emit rel="noopener noreferrer" on link HTMLAttributes (render path). */
|
|
21
|
+
includeLinkRel?: boolean;
|
|
22
|
+
/** When present, add the live-editing-only plugins (Placeholder, MaxLength). */
|
|
23
|
+
editing?: {
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare const getRteExtensions: (options?: RteExtensionOptions) => Extensions;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
2
|
+
import Underline from '@tiptap/extension-underline';
|
|
3
|
+
import { TextStyle, FontSize, LineHeight } from '@tiptap/extension-text-style';
|
|
4
|
+
import TextAlign from '@tiptap/extension-text-align';
|
|
5
|
+
import ColorExtension from '@tiptap/extension-color';
|
|
6
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
7
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
8
|
+
import Link from '@tiptap/extension-link';
|
|
9
|
+
import { FontFamily } from './extensions/FontFamily';
|
|
10
|
+
import { LetterSpacing } from './extensions/LetterSpacing';
|
|
11
|
+
import { MaxLength } from './extensions/MaxLength';
|
|
12
|
+
import { AutoLink } from './extensions/AutoLink';
|
|
13
|
+
import { isHrefPageId } from './hyperlink';
|
|
14
|
+
export const getRteExtensions = (options = {}) => {
|
|
15
|
+
const { restrictStarterKit = false, includeLinkRel = false, editing, } = options;
|
|
16
|
+
const extensions = [
|
|
17
|
+
// v3 StarterKit bundles Link + Underline; we register both separately (with
|
|
18
|
+
// custom Link config), so disable the bundled copies to avoid duplicate
|
|
19
|
+
// extension errors.
|
|
20
|
+
// trailingNode: false — v3 StarterKit adds a TrailingNode plugin (not in v2)
|
|
21
|
+
// that appends an empty paragraph after any non-paragraph trailing block.
|
|
22
|
+
// Disabled to keep editor/stored-content behaviour identical to v2 (BP-5787
|
|
23
|
+
// is backward-compat-first); revisit as a standalone UX change if wanted.
|
|
24
|
+
restrictStarterKit
|
|
25
|
+
? StarterKit.configure({
|
|
26
|
+
link: false,
|
|
27
|
+
underline: false,
|
|
28
|
+
trailingNode: false,
|
|
29
|
+
bulletList: false,
|
|
30
|
+
orderedList: false,
|
|
31
|
+
listItem: false,
|
|
32
|
+
horizontalRule: false,
|
|
33
|
+
codeBlock: false,
|
|
34
|
+
code: false,
|
|
35
|
+
})
|
|
36
|
+
: StarterKit.configure({
|
|
37
|
+
link: false,
|
|
38
|
+
underline: false,
|
|
39
|
+
trailingNode: false,
|
|
40
|
+
}),
|
|
41
|
+
Underline,
|
|
42
|
+
// priority: 99 — render the textStyle <span> (which carries font-size) as the
|
|
43
|
+
// OUTERMOST inline mark, so decoration marks (<u>/<s>) nest INSIDE it and
|
|
44
|
+
// inherit the applied font-size. v3's extension-text-style defaults to
|
|
45
|
+
// priority 101, which flips this: <u>/<s> end up outside the font-size span
|
|
46
|
+
// and inherit only the base paragraph size. Since text-decoration thickness
|
|
47
|
+
// AND vertical position are computed from the decorating element's own font,
|
|
48
|
+
// that produced thin, mis-centred underline/strike on large text — a
|
|
49
|
+
// regression vs v2's serializer, which emitted the font-size span outermost
|
|
50
|
+
// (BP-5787, backward-compat-first).
|
|
51
|
+
TextStyle.extend({ priority: 99 }).configure({
|
|
52
|
+
mergeNestedSpanStyles: true,
|
|
53
|
+
}),
|
|
54
|
+
TextAlign.configure({
|
|
55
|
+
types: ['heading', 'paragraph'],
|
|
56
|
+
}),
|
|
57
|
+
FontFamily.configure({
|
|
58
|
+
types: ['textStyle'],
|
|
59
|
+
}),
|
|
60
|
+
// FontSize + LineHeight are now shipped natively by @tiptap/extension-text-style.
|
|
61
|
+
FontSize.configure({ types: ['textStyle'] }),
|
|
62
|
+
LetterSpacing,
|
|
63
|
+
LineHeight.configure({ types: ['textStyle'] }),
|
|
64
|
+
ColorExtension.configure({
|
|
65
|
+
types: ['textStyle'],
|
|
66
|
+
}),
|
|
67
|
+
Highlight.configure({
|
|
68
|
+
multicolor: true,
|
|
69
|
+
}),
|
|
70
|
+
];
|
|
71
|
+
// Live-editing-only plugins sit between Highlight and Link (original order).
|
|
72
|
+
if (editing) {
|
|
73
|
+
extensions.push(Placeholder.configure({ placeholder: editing.placeholder }), MaxLength.configure({ maxLength: editing.maxLength }));
|
|
74
|
+
}
|
|
75
|
+
extensions.push(Link.configure({
|
|
76
|
+
openOnClick: false,
|
|
77
|
+
defaultProtocol: 'https',
|
|
78
|
+
autolink: true,
|
|
79
|
+
HTMLAttributes: (attrs) => {
|
|
80
|
+
const href = attrs.href;
|
|
81
|
+
const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
82
|
+
return {
|
|
83
|
+
...(includeLinkRel && { rel: 'noopener noreferrer' }),
|
|
84
|
+
target: isExternal ? '_blank' : null,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
|
|
88
|
+
}), AutoLink);
|
|
89
|
+
return extensions;
|
|
90
|
+
};
|
|
@@ -1,72 +1,25 @@
|
|
|
1
1
|
import { onBeforeUnmount, computed, watch, } from 'vue';
|
|
2
2
|
import { Editor } from '@tiptap/vue-3';
|
|
3
|
-
import
|
|
4
|
-
import Underline from '@tiptap/extension-underline';
|
|
5
|
-
import TextStyle from '@tiptap/extension-text-style';
|
|
6
|
-
import TextAlign from '@tiptap/extension-text-align';
|
|
7
|
-
import FontFamily from '@tiptap/extension-font-family';
|
|
8
|
-
import ColorExtension from '@tiptap/extension-color';
|
|
9
|
-
import { FontSize } from './extensions/FontSize';
|
|
10
|
-
import { LetterSpacing } from './extensions/LetterSpacing';
|
|
11
|
-
import { LineHeight } from './extensions/LineHeight';
|
|
12
|
-
import Highlight from '@tiptap/extension-highlight';
|
|
13
|
-
import Placeholder from '@tiptap/extension-placeholder';
|
|
14
|
-
import Link from '@tiptap/extension-link';
|
|
3
|
+
import { getRteExtensions } from './getRteExtensions';
|
|
15
4
|
import { createFormattingActions } from './formatting';
|
|
16
5
|
import { useTextAlign } from './textAlign';
|
|
17
6
|
import { useFont } from './font';
|
|
18
7
|
import { useSpacing } from './spacing';
|
|
19
8
|
import Color from 'color';
|
|
20
|
-
import { buildInitialMarks, emToPx, isStyleChanged, pxToEm,
|
|
9
|
+
import { buildInitialMarks, emToPx, isStyleChanged, pxToEm, sanitizeRteJson, skipStyleDetection, } from '@brand-page-maker/utils';
|
|
21
10
|
import { HYPERLINK_TYPE, } from '@brand-page';
|
|
22
11
|
import { letterSpacingValueConvertor, colorConvertor, backgroundColorConvertor, } from '@brand-page-maker/convertors';
|
|
23
12
|
import { useUiContentSettingsStore } from '@shared/content-settings/store';
|
|
24
13
|
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
25
14
|
import { AllSelection } from 'prosemirror-state';
|
|
26
|
-
import {
|
|
27
|
-
import { AutoLink } from './extensions/AutoLink';
|
|
28
|
-
import { isHrefPageId, useHyperlink } from './hyperlink';
|
|
15
|
+
import { useHyperlink } from './hyperlink';
|
|
29
16
|
import { useBubbleMenu } from './bubbleMenu';
|
|
30
17
|
import { hasLogoColorVar, isLogoColor, getRTEColor, } from '@brand-page-maker/utils/rich-text-editor.util';
|
|
31
18
|
import { useEventTracker, EVENTS } from '../useEventTracker';
|
|
32
19
|
export const useExternalRichTextEditor = () => {
|
|
33
20
|
const externalEditor = new Editor({
|
|
34
21
|
editable: false,
|
|
35
|
-
extensions:
|
|
36
|
-
StarterKit,
|
|
37
|
-
Underline,
|
|
38
|
-
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
39
|
-
TextAlign.configure({
|
|
40
|
-
types: ['heading', 'paragraph'],
|
|
41
|
-
}),
|
|
42
|
-
FontFamily.configure({
|
|
43
|
-
types: ['textStyle'],
|
|
44
|
-
}),
|
|
45
|
-
FontSize,
|
|
46
|
-
LetterSpacing,
|
|
47
|
-
LineHeight,
|
|
48
|
-
ColorExtension.configure({
|
|
49
|
-
types: ['textStyle'],
|
|
50
|
-
}),
|
|
51
|
-
Highlight.configure({
|
|
52
|
-
multicolor: true,
|
|
53
|
-
}),
|
|
54
|
-
Link.configure({
|
|
55
|
-
openOnClick: false,
|
|
56
|
-
defaultProtocol: 'https',
|
|
57
|
-
autolink: true,
|
|
58
|
-
HTMLAttributes: (attrs) => {
|
|
59
|
-
const href = attrs.href;
|
|
60
|
-
const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
61
|
-
return {
|
|
62
|
-
rel: 'noopener noreferrer',
|
|
63
|
-
target: isExternal ? '_blank' : null,
|
|
64
|
-
};
|
|
65
|
-
},
|
|
66
|
-
isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
|
|
67
|
-
}),
|
|
68
|
-
AutoLink,
|
|
69
|
-
],
|
|
22
|
+
extensions: getRteExtensions({ includeLinkRel: true }),
|
|
70
23
|
});
|
|
71
24
|
return {
|
|
72
25
|
externalEditor,
|
|
@@ -77,51 +30,13 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
77
30
|
const editor = new Editor({
|
|
78
31
|
content: model.value ?? '',
|
|
79
32
|
editable: !props.readOnly,
|
|
80
|
-
extensions:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
orderedList: false,
|
|
84
|
-
listItem: false,
|
|
85
|
-
horizontalRule: false,
|
|
86
|
-
codeBlock: false,
|
|
87
|
-
code: false,
|
|
88
|
-
}),
|
|
89
|
-
Underline,
|
|
90
|
-
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
91
|
-
TextAlign.configure({
|
|
92
|
-
types: ['heading', 'paragraph'],
|
|
93
|
-
}),
|
|
94
|
-
FontFamily.configure({
|
|
95
|
-
types: ['textStyle'],
|
|
96
|
-
}),
|
|
97
|
-
FontSize,
|
|
98
|
-
LetterSpacing,
|
|
99
|
-
LineHeight,
|
|
100
|
-
ColorExtension.configure({
|
|
101
|
-
types: ['textStyle'],
|
|
102
|
-
}),
|
|
103
|
-
Highlight.configure({
|
|
104
|
-
multicolor: true,
|
|
105
|
-
}),
|
|
106
|
-
Placeholder.configure({
|
|
33
|
+
extensions: getRteExtensions({
|
|
34
|
+
restrictStarterKit: true,
|
|
35
|
+
editing: {
|
|
107
36
|
placeholder: props.placeholder,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
openOnClick: false,
|
|
112
|
-
defaultProtocol: 'https',
|
|
113
|
-
autolink: true,
|
|
114
|
-
HTMLAttributes: (attrs) => {
|
|
115
|
-
const href = attrs.href;
|
|
116
|
-
const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
117
|
-
return {
|
|
118
|
-
target: isExternal ? '_blank' : null,
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
|
|
122
|
-
}),
|
|
123
|
-
AutoLink,
|
|
124
|
-
],
|
|
37
|
+
maxLength: props.maxLength,
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
125
40
|
editorProps: {
|
|
126
41
|
handleDOMEvents: {
|
|
127
42
|
drop(view, event) {
|
|
@@ -224,7 +139,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
224
139
|
const { selectedFont, toggleFont, toggleFontSize, toggleFontAll, toggleFontSizeAll, } = useFont(editor, fontSettings, fontStyleDropdownRef);
|
|
225
140
|
const { letterSpacingValue, lineHeightValue, letterSpacing, lineHeight, setLetterSpaceAll, setLineHeightAll, } = useSpacing(editor);
|
|
226
141
|
const { hyperlinkValue, setHyperlinkUrl, setHyperlinkUrlValue, removeHyperlink: removeHyperlinkFromComposable, applyHyperlinkToEditor, } = useHyperlink(editor);
|
|
227
|
-
const { shouldShow,
|
|
142
|
+
const { shouldShow, bubbleMenuOptions } = useBubbleMenu(editor, isHyperlinkPopupOpen);
|
|
228
143
|
watch(() => props.styles.textAlign, (newAlignment) => {
|
|
229
144
|
if (newAlignment && newAlignment !== currentAlignment.value.alignment) {
|
|
230
145
|
skipStyleDetection(editor.chain().selectAll().setTextAlign(newAlignment)).run();
|
|
@@ -239,8 +154,12 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
239
154
|
const appliedSinceLast = historyStore.snapshotAppliedToken !== lastAppliedToken;
|
|
240
155
|
lastAppliedToken = historyStore.snapshotAppliedToken;
|
|
241
156
|
const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
|
|
242
|
-
|
|
243
|
-
|
|
157
|
+
// Compare in the stored (JSON) format so a JSON model value isn't always
|
|
158
|
+
// seen as "changed" against the editor's HTML (which would re-run
|
|
159
|
+
// setContent and reset the user's just-applied styles).
|
|
160
|
+
const hasChanged = JSON.stringify(editor.getJSON()) !== JSON.stringify(newValue);
|
|
161
|
+
if (isExternalMutation && hasChanged) {
|
|
162
|
+
editor.commands.setContent(newValue, { emitUpdate: false });
|
|
244
163
|
updateRichTextEditor.value = false;
|
|
245
164
|
//text style type is not switching when reordering
|
|
246
165
|
textStyleTypeOptions.forEach((item) => {
|
|
@@ -267,11 +186,13 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
267
186
|
});
|
|
268
187
|
}
|
|
269
188
|
});
|
|
270
|
-
// Update model when editor content changes
|
|
189
|
+
// Update model when editor content changes. RTE content is stored as Tiptap
|
|
190
|
+
// JSON (BP-5787). getJSON() is already in hand (the editor holds the doc) and
|
|
191
|
+
// is sanitised natively — no getHTML(), no DOMPurify, no extra editor — so
|
|
192
|
+
// the per-keystroke path allocates nothing heavy.
|
|
271
193
|
editor.on('update', () => {
|
|
272
|
-
const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
|
|
273
194
|
// An update fired while the editor is unfocused is TipTap rewriting the
|
|
274
|
-
// model back into its canonical
|
|
195
|
+
// model back into its canonical form during mount rehydration or a
|
|
275
196
|
// programmatic setContent — not a user edit. Tag the emit so the write
|
|
276
197
|
// folds into the current undo head instead of pushing a phantom entry
|
|
277
198
|
// (BP-5810). The flag travels WITH the value through the debounced
|
|
@@ -285,7 +206,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
285
206
|
// the RTE only mounts inside an already-open settings drawer and the user
|
|
286
207
|
// focuses it before formatting, so this doesn't occur — revisit if a
|
|
287
208
|
// consumer ever drives toolbar actions on an unfocused editor.
|
|
288
|
-
emit('update:modelValue',
|
|
209
|
+
emit('update:modelValue', sanitizeRteJson(editor.getJSON()), {
|
|
289
210
|
isHydrationWrite: !editor.isFocused,
|
|
290
211
|
});
|
|
291
212
|
});
|
|
@@ -725,7 +646,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
725
646
|
hyperlinkValue,
|
|
726
647
|
setHyperlinkUrlValue,
|
|
727
648
|
shouldShow,
|
|
728
|
-
|
|
649
|
+
bubbleMenuOptions,
|
|
729
650
|
handleAdminColorChange,
|
|
730
651
|
};
|
|
731
652
|
};
|
|
@@ -8,7 +8,9 @@ export declare const useAiWebsiteBuilderThreadStore: import("pinia").StoreDefini
|
|
|
8
8
|
buildSectionsCompleted: import("vue").Ref<number, number>;
|
|
9
9
|
promptText: import("vue").Ref<string, string>;
|
|
10
10
|
stopGeneration: import("vue").Ref<(() => void) | null, (() => void) | null>;
|
|
11
|
-
|
|
11
|
+
resetToken: import("vue").Ref<number, number>;
|
|
12
|
+
reset: () => void;
|
|
13
|
+
}, "threadId" | "runId" | "messages" | "isProcessing" | "buildId" | "isBuildInProgress" | "buildSectionsCompleted" | "promptText" | "stopGeneration" | "resetToken">, Pick<{
|
|
12
14
|
threadId: import("vue").Ref<string | null, string | null>;
|
|
13
15
|
runId: import("vue").Ref<string | null, string | null>;
|
|
14
16
|
messages: import("vue").Ref<any[], any[]>;
|
|
@@ -18,6 +20,8 @@ export declare const useAiWebsiteBuilderThreadStore: import("pinia").StoreDefini
|
|
|
18
20
|
buildSectionsCompleted: import("vue").Ref<number, number>;
|
|
19
21
|
promptText: import("vue").Ref<string, string>;
|
|
20
22
|
stopGeneration: import("vue").Ref<(() => void) | null, (() => void) | null>;
|
|
23
|
+
resetToken: import("vue").Ref<number, number>;
|
|
24
|
+
reset: () => void;
|
|
21
25
|
}, never>, Pick<{
|
|
22
26
|
threadId: import("vue").Ref<string | null, string | null>;
|
|
23
27
|
runId: import("vue").Ref<string | null, string | null>;
|
|
@@ -28,4 +32,6 @@ export declare const useAiWebsiteBuilderThreadStore: import("pinia").StoreDefini
|
|
|
28
32
|
buildSectionsCompleted: import("vue").Ref<number, number>;
|
|
29
33
|
promptText: import("vue").Ref<string, string>;
|
|
30
34
|
stopGeneration: import("vue").Ref<(() => void) | null, (() => void) | null>;
|
|
31
|
-
|
|
35
|
+
resetToken: import("vue").Ref<number, number>;
|
|
36
|
+
reset: () => void;
|
|
37
|
+
}, "reset">>;
|
|
@@ -10,6 +10,24 @@ export const useAiWebsiteBuilderThreadStore = defineStore('aiWebsiteBuilderThrea
|
|
|
10
10
|
const buildSectionsCompleted = ref(0);
|
|
11
11
|
const promptText = ref('');
|
|
12
12
|
const stopGeneration = ref(null);
|
|
13
|
+
// Bumped on every reset so consumers (useAiWebsiteBuilderChat) can react
|
|
14
|
+
// without watching `messages`, which would loop with its own deep watch.
|
|
15
|
+
const resetToken = ref(0);
|
|
16
|
+
const reset = () => {
|
|
17
|
+
if (isProcessing.value || isBuildInProgress.value) {
|
|
18
|
+
stopGeneration.value?.();
|
|
19
|
+
}
|
|
20
|
+
threadId.value = null;
|
|
21
|
+
runId.value = null;
|
|
22
|
+
messages.value = [];
|
|
23
|
+
isProcessing.value = false;
|
|
24
|
+
buildId.value = null;
|
|
25
|
+
isBuildInProgress.value = false;
|
|
26
|
+
buildSectionsCompleted.value = 0;
|
|
27
|
+
promptText.value = '';
|
|
28
|
+
stopGeneration.value = null;
|
|
29
|
+
resetToken.value += 1;
|
|
30
|
+
};
|
|
13
31
|
return {
|
|
14
32
|
threadId,
|
|
15
33
|
runId,
|
|
@@ -20,5 +38,7 @@ export const useAiWebsiteBuilderThreadStore = defineStore('aiWebsiteBuilderThrea
|
|
|
20
38
|
buildSectionsCompleted,
|
|
21
39
|
promptText,
|
|
22
40
|
stopGeneration,
|
|
41
|
+
resetToken,
|
|
42
|
+
reset,
|
|
23
43
|
};
|
|
24
44
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { EMPTY_LOGO_IMAGE_MODEL, SENSITIVE_CONTENT_TYPES, TEXT_STYLE_TYPES, VIEWPORTS, } from '@brand-page';
|
|
2
2
|
import { BackgroundStyles, BRAND_PAGE_TYPES, BrandPageModel, CONTENT_TYPES, NavigationDesktop, NavigationMobile, } from '@brand-page';
|
|
3
3
|
import { useBrandPageMakerUserStore } from '../user';
|
|
4
|
+
import { useAiWebsiteBuilderThreadStore } from '../ai-thread/ai-website-builder-thread-store';
|
|
5
|
+
import { useAiCheckpointStore } from '../ai-checkpoint/ai-checkpoint-store';
|
|
4
6
|
import { defineStore } from 'pinia';
|
|
5
7
|
import { toRaw } from 'vue';
|
|
6
8
|
import { useUiStore } from '../ui';
|
|
@@ -353,6 +355,11 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
|
|
|
353
355
|
backgroundStyles: brandPageModel.styles.backgroundStyles,
|
|
354
356
|
});
|
|
355
357
|
await this.recalculateLogoSizes(true);
|
|
358
|
+
// A template switch invalidates the AI conversation — the server-side
|
|
359
|
+
// thread's history still describes the previous template (BP-5896).
|
|
360
|
+
// reset() also stops any in-flight generation before clearing.
|
|
361
|
+
useAiWebsiteBuilderThreadStore().reset();
|
|
362
|
+
useAiCheckpointStore().clearAll();
|
|
356
363
|
this.setIsModifiedAfterTemplateApplied({
|
|
357
364
|
isModified: false,
|
|
358
365
|
});
|
|
@@ -5,4 +5,6 @@ export * from './url.util';
|
|
|
5
5
|
export * from './slug.util';
|
|
6
6
|
export * from './text.util';
|
|
7
7
|
export * from './rich-text-editor.util';
|
|
8
|
+
export * from './rte-render.util';
|
|
9
|
+
export * from './rte-sanitize.util';
|
|
8
10
|
export declare const isColorMatched: (color: string | null | undefined) => boolean;
|
|
@@ -6,6 +6,8 @@ export * from './url.util';
|
|
|
6
6
|
export * from './slug.util';
|
|
7
7
|
export * from './text.util';
|
|
8
8
|
export * from './rich-text-editor.util';
|
|
9
|
+
export * from './rte-render.util';
|
|
10
|
+
export * from './rte-sanitize.util';
|
|
9
11
|
export const isColorMatched = (color) => {
|
|
10
12
|
if (!color)
|
|
11
13
|
return false;
|