@designcrowd/library.brand-page 6.2.2 → 6.2.3-ast-faq
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-7P2gNr-R.js → CreatedOnBrandCrowd-de-de-BRisMzFY.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-Dl7BymW6.js → CreatedOnBrandCrowd-en-us-CDDsL9GX.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-CO4FvWVw.js → CreatedOnBrandCrowd-es-es-BkZrEo4j.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DHNqakQu.js → CreatedOnBrandCrowd-fr-ca-sx_ury7R.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DHNqakQu.js → CreatedOnBrandCrowd-fr-fr-sx_ury7R.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CGUC-ZbP.js → CreatedOnBrandCrowd-pt-br-DCWiAfkU.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CGUC-ZbP.js → CreatedOnBrandCrowd-pt-pt-DCWiAfkU.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-Dvds-Vmy.js → CreatedOnDcom-de-de-C1S7f22L.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-fd3C-8Tp.js → CreatedOnDcom-en-us-DgTlWaXE.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DTXZW35-.js → CreatedOnDcom-es-es-CxSQJe_V.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DRCYOKPs.js → CreatedOnDcom-fr-ca-Bbam2tt6.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DpIB8x2x.js → CreatedOnDcom-fr-fr-Cb0XDRPL.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-Esnxy8wX.js → CreatedOnDcom-pt-br-BjdX3-E3.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-CaiVHFXR.js → CreatedOnDcom-pt-pt-BXpbfopB.js} +1 -1
- package/lib/src/brand-page/constants/misc.constants.d.ts +8 -0
- package/lib/src/brand-page/constants/misc.constants.js +7 -0
- package/lib/src/brand-page/constants/styles.constants.d.ts +2 -0
- package/lib/src/brand-page/constants/styles.constants.js +2 -0
- package/lib/src/brand-page/models/brand-page-context.model.d.ts +0 -2
- package/lib/src/brand-page/models/brand-page-context.model.js +0 -12
- package/lib/src/brand-page/models/content.model.d.ts +1 -0
- package/lib/src/brand-page/models/content.model.js +1 -0
- package/lib/src/brand-page/models/index.d.ts +19 -0
- package/lib/src/brand-page/utils/content-callback-util.d.ts +2 -1
- package/lib/src/brand-page/utils/content-callback-util.js +3 -0
- package/lib/src/brand-page/utils/model.util.d.ts +2 -1
- package/lib/src/brand-page/utils/model.util.js +3 -0
- package/lib/src/brand-page/utils/view-model.util.d.ts +2 -0
- package/lib/src/brand-page/utils/view-model.util.js +24 -0
- package/lib/src/brand-page/view-models.d.ts +9 -2
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +0 -1
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -9
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +10 -42
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +10 -42
- package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +0 -14
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +13 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +64 -30
- package/lib/src/brand-page-maker/constants/config-settings-constant.d.ts +2 -1
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +101 -2
- package/lib/src/brand-page-maker/convertors/faq-display-style.convertor.d.ts +3 -0
- package/lib/src/brand-page-maker/convertors/faq-display-style.convertor.js +47 -0
- package/lib/src/brand-page-maker/models/content-settings-configs.d.ts +22 -1
- package/lib/src/brand-page-maker/models/content-settings-configs.js +1 -0
- package/lib/src/brand-page-maker/models/content-settings.d.ts +4 -1
- package/lib/src/brand-page-maker/models/content-settings.js +45 -1
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +0 -42
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +1 -0
- package/lib/src/brand-page-maker/utils/apply-color-strategy.js +47 -17
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +6 -0
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +24 -1
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -0
- package/lib/src/bundled-translations-DY9YvuL-.js +179 -0
- package/lib/src/bundles/bundled-translations.json +20 -2
- package/lib/src/composables/useTranslate.d.ts +1 -1
- package/lib/src/composables/useTranslate.js +1 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +143 -36
- package/lib/src/css/library.brand-page-brandPage.min.css +143 -36
- package/lib/src/css/library.brand-page-designCom.min.css +143 -36
- package/lib/src/css/library.brand-page-preview.min.css +143 -36
- package/lib/src/index.mjs +159 -158
- package/lib/src/{lib-V7aqfuiB.js → lib-DwpSAbLZ.js} +26889 -26178
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +1 -1
- package/lib/src/shared/color-strategies/color-strategies.js +4 -0
- package/lib/src/shared/color-strategies/rte-field-pairs.d.ts +12 -0
- package/lib/src/shared/color-strategies/rte-field-pairs.js +10 -0
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +11 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.d.ts +14 -1
- package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.js +11 -1
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +0 -42
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.js +5 -0
- package/lib/src/shared/content-settings/models.d.ts +6 -0
- package/lib/src/shared/content-settings/store/content-settings-store.d.ts +14 -1
- package/lib/src/shared/content-settings/store/content-settings-store.js +24 -2
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +0 -42
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +10 -42
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +10 -42
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +0 -56
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +0 -42
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +0 -42
- package/package.json +2 -2
- package/src/brand-page/components/faq-section/FaqSection.test.ts +292 -0
- package/src/brand-page/components/faq-section/FaqSection.vue +299 -0
- package/src/brand-page/components/page/PageComponent.vue +2 -0
- package/src/brand-page/components/section-heading/SectionHeading.vue +52 -0
- package/src/brand-page/components/text-wrapper/TextWrapper.test.ts +33 -0
- package/src/brand-page/constants/misc.constants.ts +12 -0
- package/src/brand-page/constants/styles.constants.ts +2 -0
- package/src/brand-page/models/brand-page-context.model.ts +0 -23
- package/src/brand-page/models/content.model.ts +1 -0
- package/src/brand-page/models/index.ts +21 -0
- package/src/brand-page/utils/content-callback-util.ts +5 -0
- package/src/brand-page/utils/model.util.ts +7 -0
- package/src/brand-page/utils/view-model.util.test.ts +96 -0
- package/src/brand-page/utils/view-model.util.ts +49 -0
- package/src/brand-page/view-models.ts +12 -1
- package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -10
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.test.ts +8 -49
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.vue +1 -1
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.test.ts +165 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +37 -49
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +87 -30
- package/src/brand-page-maker/constants/config-settings-constant.ts +108 -0
- package/src/brand-page-maker/constants/faq-section-config.test.ts +151 -0
- package/src/brand-page-maker/convertors/faq-display-style.convertor.ts +59 -0
- package/src/brand-page-maker/convertors/i18n/converter.json +4 -1
- package/src/brand-page-maker/models/content-settings-configs.ts +24 -0
- package/src/brand-page-maker/models/content-settings.ts +57 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +2 -0
- package/src/brand-page-maker/utils/__snapshots__/apply-color-strategy.golden.test.ts.snap +4063 -895
- package/src/brand-page-maker/utils/apply-color-strategy.test.ts +104 -2
- package/src/brand-page-maker/utils/apply-color-strategy.ts +77 -19
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +110 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +66 -0
- package/src/brand-page-maker/validators/field.validator.ts +2 -0
- package/src/bundles/bundled-translations.json +20 -2
- package/src/composables/useTranslate.ts +2 -1
- package/src/metadata-builder/builders/item-id-utils.test.ts +24 -0
- package/src/metadata-builder/parsers/section-tool-arguments.ts +2 -1
- package/src/metadata-builder/parsers/section-tool-map.test.ts +12 -0
- package/src/shared/color-strategies/color-strategies.ts +4 -0
- package/src/shared/color-strategies/rte-field-pairs.ts +23 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +17 -1
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.vue +3 -0
- package/src/shared/content-settings/components/items-field/ItemsField.mixin.ts +11 -1
- package/src/shared/content-settings/components/items-field/ItemsField.vue +8 -2
- package/src/shared/content-settings/components/list-items-field/ListItemsField.test.ts +429 -0
- package/src/shared/content-settings/components/list-items-field/ListItemsField.vue +262 -0
- package/src/shared/content-settings/components/textarea-field/TextareaField.mixin.ts +6 -0
- package/src/shared/content-settings/components/textarea-field/TextareaField.vue +1 -0
- package/src/shared/content-settings/models.ts +7 -0
- package/src/shared/content-settings/store/content-settings-store.test.ts +70 -0
- package/src/shared/content-settings/store/content-settings-store.ts +37 -3
- package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.test.ts +19 -0
- package/src/shared/page-settings/store/page-settings-store.test.ts +0 -71
- package/src/utils/minify-brand-page-metadata.test.ts +35 -0
- package/lib/src/bundled-translations-BYUfWBT3.js +0 -179
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
SECTION_RAISED_LAYER,
|
|
14
14
|
} from '@shared/color-strategies/color-strategies';
|
|
15
15
|
import { pageStrategyAnchors } from '@shared/color-strategies/page-anchors';
|
|
16
|
+
import { RTE_ITEM_FIELD_PAIRS } from '@shared/color-strategies/rte-field-pairs';
|
|
16
17
|
import type { ColorStrategy } from '@shared/color-strategies/models';
|
|
17
18
|
|
|
18
19
|
const backgroundConvertor = {
|
|
@@ -1652,12 +1653,113 @@ describe('every colour field is bound or deliberately unbound (BP-5886)', () =>
|
|
|
1652
1653
|
// `successMessageStyle` is a content-model field (contact-form's success
|
|
1653
1654
|
// message) with no settings-config entry, so the config-driven applier can
|
|
1654
1655
|
// never write it. Kept bound as documentation of intent; this pins that the
|
|
1655
|
-
// phantom set never grows silently.
|
|
1656
|
+
// phantom set never grows silently. Item-level styles (each FAQ item's
|
|
1657
|
+
// `questionStyle`) are declared by RTE_ITEM_FIELD_PAIRS, not by a config field.
|
|
1658
|
+
const itemStyleFields = new Set(
|
|
1659
|
+
Object.values(RTE_ITEM_FIELD_PAIRS).flatMap((pairs) =>
|
|
1660
|
+
pairs.map((pair) => pair.styleField),
|
|
1661
|
+
),
|
|
1662
|
+
);
|
|
1656
1663
|
const phantoms = [
|
|
1657
1664
|
...Object.keys(FIELD_SLOT_MAP),
|
|
1658
1665
|
...Object.values(SECTION_RAISED_LAYER).flatMap((layer) => layer.content),
|
|
1659
|
-
].filter(
|
|
1666
|
+
].filter(
|
|
1667
|
+
(name) => !configColourFields.has(name) && !itemStyleFields.has(name),
|
|
1668
|
+
);
|
|
1660
1669
|
|
|
1661
1670
|
expect(new Set(phantoms)).toEqual(new Set(['successMessageStyle']));
|
|
1662
1671
|
});
|
|
1663
1672
|
});
|
|
1673
|
+
|
|
1674
|
+
describe('buildColorStrategyValues — FAQ section', () => {
|
|
1675
|
+
const doc = (text: string) => ({
|
|
1676
|
+
type: 'doc',
|
|
1677
|
+
content: [
|
|
1678
|
+
{
|
|
1679
|
+
type: 'paragraph',
|
|
1680
|
+
content: [
|
|
1681
|
+
{
|
|
1682
|
+
type: 'text',
|
|
1683
|
+
text,
|
|
1684
|
+
marks: [{ type: 'textStyle', attrs: { color: '#123456' } }],
|
|
1685
|
+
},
|
|
1686
|
+
],
|
|
1687
|
+
},
|
|
1688
|
+
],
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
const faq = () => ({
|
|
1692
|
+
type: 'faq-section',
|
|
1693
|
+
displayStyle: 'faq-centered',
|
|
1694
|
+
backgroundStyle: {},
|
|
1695
|
+
titleStyle: {},
|
|
1696
|
+
descriptionStyle: {},
|
|
1697
|
+
items: [
|
|
1698
|
+
{
|
|
1699
|
+
id: 'q1',
|
|
1700
|
+
question: doc('Q1'),
|
|
1701
|
+
questionStyle: { textStyleType: 'headings', backgroundColor: '#fff' },
|
|
1702
|
+
answer: 'Plain answer',
|
|
1703
|
+
answerStyle: { textStyleType: 'paragraphs' },
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
id: 'q2',
|
|
1707
|
+
question: 'Plain question',
|
|
1708
|
+
questionStyle: { textStyleType: 'headings' },
|
|
1709
|
+
answer: doc('A2'),
|
|
1710
|
+
answerStyle: { textStyleType: 'paragraphs' },
|
|
1711
|
+
},
|
|
1712
|
+
],
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
it('repaints every item: its styles and its rich-text documents', () => {
|
|
1716
|
+
const config = CONTENT_SETTINGS_CONFIGS()['faq-section'];
|
|
1717
|
+
const content = faq();
|
|
1718
|
+
const values: Record<string, any> = buildColorStrategyValues(
|
|
1719
|
+
config,
|
|
1720
|
+
content,
|
|
1721
|
+
'duotone',
|
|
1722
|
+
);
|
|
1723
|
+
const [q1, q2] = values.items;
|
|
1724
|
+
const questionRef = q1.questionStyle.color;
|
|
1725
|
+
|
|
1726
|
+
expect(values.backgroundStyle).toBeDefined();
|
|
1727
|
+
expect(questionRef).toMatch(/^var\(--palette-\d\)$/);
|
|
1728
|
+
expect(q1.answerStyle.color).toMatch(/^var\(--palette-\d\)$/);
|
|
1729
|
+
expect(q2.questionStyle.color).toBe(questionRef);
|
|
1730
|
+
// Text on the band never takes the band's own role.
|
|
1731
|
+
expect(questionRef).not.toBe(values.backgroundStyle?.backgroundColor);
|
|
1732
|
+
// The strategy owns the text treatment: background cleared, typography kept.
|
|
1733
|
+
expect('backgroundColor' in q1.questionStyle).toBe(false);
|
|
1734
|
+
expect(q1.questionStyle.textStyleType).toBe('headings');
|
|
1735
|
+
// RTE documents take the colour in their marks; plain text is left as text.
|
|
1736
|
+
expect(JSON.stringify(q1.question)).toContain(questionRef);
|
|
1737
|
+
expect(JSON.stringify(q1.question)).not.toContain('#123456');
|
|
1738
|
+
expect(q1.answer).toBe('Plain answer');
|
|
1739
|
+
expect(JSON.stringify(q2.answer)).toContain(q2.answerStyle.color);
|
|
1740
|
+
// A new list — never the stored one mutated.
|
|
1741
|
+
expect(values.items).not.toBe(content.items);
|
|
1742
|
+
expect(content.items[0].questionStyle.backgroundColor).toBe('#fff');
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
it('asks the live editors to re-seed when an item document was replaced', () => {
|
|
1746
|
+
const updateItemValue = vi.fn();
|
|
1747
|
+
const markRteResync = vi.fn();
|
|
1748
|
+
|
|
1749
|
+
applyColorStrategyToItem(
|
|
1750
|
+
{ updateItemValue },
|
|
1751
|
+
{
|
|
1752
|
+
id: 'faq-1',
|
|
1753
|
+
config: CONTENT_SETTINGS_CONFIGS()['faq-section'],
|
|
1754
|
+
content: faq(),
|
|
1755
|
+
strategyId: 'duotone',
|
|
1756
|
+
markRteResync,
|
|
1757
|
+
},
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
expect(updateItemValue).toHaveBeenCalledWith(
|
|
1761
|
+
expect.objectContaining({ name: 'items' }),
|
|
1762
|
+
);
|
|
1763
|
+
expect(markRteResync).toHaveBeenCalledTimes(1);
|
|
1764
|
+
});
|
|
1765
|
+
});
|
|
@@ -15,7 +15,10 @@ import type {
|
|
|
15
15
|
SectionLayers,
|
|
16
16
|
StrategyAnchors,
|
|
17
17
|
} from '@shared/color-strategies/models';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
pairedRteTextField,
|
|
20
|
+
RTE_ITEM_FIELD_PAIRS,
|
|
21
|
+
} from '@shared/color-strategies/rte-field-pairs';
|
|
19
22
|
|
|
20
23
|
// A section colour field, narrowed to what the macro reads. Config is declared `<any>`,
|
|
21
24
|
// so this keeps the applier honest without fighting the union.
|
|
@@ -116,6 +119,23 @@ const sectionLayers = (
|
|
|
116
119
|
};
|
|
117
120
|
};
|
|
118
121
|
|
|
122
|
+
/**
|
|
123
|
+
* The strategy's text treatment for one text-style field: `color` set, text background
|
|
124
|
+
* cleared (matching the highlight strip in `setRteTextColor` — the toolbar writes both).
|
|
125
|
+
* The key is deleted rather than set undefined so `'backgroundColor' in style` checks
|
|
126
|
+
* downstream stay honest.
|
|
127
|
+
*/
|
|
128
|
+
const withStrategyTextStyle = (
|
|
129
|
+
current: Record<string, unknown> | undefined,
|
|
130
|
+
ref: string,
|
|
131
|
+
): Record<string, unknown> => {
|
|
132
|
+
const nextStyle: Record<string, unknown> = { ...(current ?? {}), color: ref };
|
|
133
|
+
|
|
134
|
+
delete nextStyle.backgroundColor;
|
|
135
|
+
|
|
136
|
+
return nextStyle;
|
|
137
|
+
};
|
|
138
|
+
|
|
119
139
|
/**
|
|
120
140
|
* Build the per-field colour writes a strategy stamps onto a section. Pure — reads the
|
|
121
141
|
* section config + current content and returns a `{ fieldName: value }` partial. Each
|
|
@@ -170,18 +190,7 @@ export const buildColorStrategyValues = (
|
|
|
170
190
|
| Record<string, unknown>
|
|
171
191
|
| undefined;
|
|
172
192
|
|
|
173
|
-
|
|
174
|
-
...(current ?? {}),
|
|
175
|
-
color: ref,
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// A strategy owns the text treatment, so it clears the text background colour
|
|
179
|
-
// too — matching the highlight strip in `setRteTextColor` (the toolbar writes
|
|
180
|
-
// both). Delete the key rather than set it undefined so `'backgroundColor' in
|
|
181
|
-
// style` checks downstream stay honest.
|
|
182
|
-
delete nextStyle.backgroundColor;
|
|
183
|
-
|
|
184
|
-
values[name] = nextStyle;
|
|
193
|
+
values[name] = withStrategyTextStyle(current, ref);
|
|
185
194
|
|
|
186
195
|
// Also stamp the colour into the paired RTE document, if this field drives one.
|
|
187
196
|
// `setRteTextColor` returns undefined for a plain string or legacy HTML RTE, so
|
|
@@ -219,9 +228,52 @@ export const buildColorStrategyValues = (
|
|
|
219
228
|
values[name] = field.valueConvertor.convertBack(ref);
|
|
220
229
|
});
|
|
221
230
|
|
|
231
|
+
// Item-level RTE fields (each FAQ question) carry their own style and document, so
|
|
232
|
+
// the section's field loop above never sees them. Same two halves, per item, written
|
|
233
|
+
// back as one new list.
|
|
234
|
+
const itemPairs =
|
|
235
|
+
typeof contentRecord.type === 'string'
|
|
236
|
+
? (RTE_ITEM_FIELD_PAIRS[contentRecord.type] ?? [])
|
|
237
|
+
: [];
|
|
238
|
+
|
|
239
|
+
itemPairs.forEach((pair) => {
|
|
240
|
+
const ref = strategyRoleRefForField(
|
|
241
|
+
strategyId,
|
|
242
|
+
pair.styleField,
|
|
243
|
+
content,
|
|
244
|
+
context,
|
|
245
|
+
layers,
|
|
246
|
+
anchors,
|
|
247
|
+
);
|
|
248
|
+
const items = (values[pair.itemsField] ??
|
|
249
|
+
contentRecord[pair.itemsField]) as Record<string, unknown>[] | undefined;
|
|
250
|
+
|
|
251
|
+
if (!ref || !Array.isArray(items)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
values[pair.itemsField] = items.map((item) => {
|
|
256
|
+
const nextDoc = setRteTextColor(item[pair.textField], ref);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
...item,
|
|
260
|
+
[pair.styleField]: withStrategyTextStyle(
|
|
261
|
+
item[pair.styleField] as Record<string, unknown> | undefined,
|
|
262
|
+
ref,
|
|
263
|
+
),
|
|
264
|
+
...(nextDoc ? { [pair.textField]: nextDoc } : {}),
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
222
269
|
return values;
|
|
223
270
|
};
|
|
224
271
|
|
|
272
|
+
const isRteDoc = (value: unknown): boolean =>
|
|
273
|
+
typeof value === 'object' &&
|
|
274
|
+
value !== null &&
|
|
275
|
+
(value as { type?: unknown }).type === 'doc';
|
|
276
|
+
|
|
225
277
|
// Just the slice of the content-settings store the macro writes through. Keeps
|
|
226
278
|
// this util free of a store import (and of the module cycle that would create).
|
|
227
279
|
interface ItemValueWriter {
|
|
@@ -274,14 +326,20 @@ export const applyColorStrategyToItem = (
|
|
|
274
326
|
store.updateItemValue({ id, name, value });
|
|
275
327
|
});
|
|
276
328
|
|
|
277
|
-
// A recoloured RTE field is emitted as a whole Tiptap document
|
|
278
|
-
//
|
|
279
|
-
// to re-seed, so signal it just
|
|
329
|
+
// A recoloured RTE field is emitted as a whole Tiptap document (at the top level,
|
|
330
|
+
// or inside a rewritten item list); the paired style-object writes are plain
|
|
331
|
+
// objects. Only the former needs the live editor to re-seed, so signal it just
|
|
332
|
+
// when a doc was among the writes.
|
|
280
333
|
const replacedRteDoc = Object.values(values).some(
|
|
281
334
|
(value) =>
|
|
282
|
-
|
|
283
|
-
value
|
|
284
|
-
|
|
335
|
+
isRteDoc(value) ||
|
|
336
|
+
(Array.isArray(value) &&
|
|
337
|
+
value.some(
|
|
338
|
+
(item) =>
|
|
339
|
+
typeof item === 'object' &&
|
|
340
|
+
item !== null &&
|
|
341
|
+
Object.values(item).some(isRteDoc),
|
|
342
|
+
)),
|
|
285
343
|
);
|
|
286
344
|
|
|
287
345
|
if (replacedRteDoc) {
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
resolveToolbarTextColor,
|
|
16
16
|
resolveInitialToolbarTextColor,
|
|
17
17
|
transformToRTE,
|
|
18
|
+
switchAllContentToRTE,
|
|
18
19
|
} from './rich-text-editor.util';
|
|
20
|
+
import { useExternalRichTextEditor } from '../composables/rich-text-editor/useRichTextEditor';
|
|
19
21
|
import type { TextStyleDeclaration } from '@brand-page';
|
|
20
22
|
|
|
21
23
|
describe('isStyleChanged', () => {
|
|
@@ -585,6 +587,114 @@ describe('transformToRTE — respects existing RTE marks on selection (BP-5886)'
|
|
|
585
587
|
});
|
|
586
588
|
});
|
|
587
589
|
|
|
590
|
+
describe('item-level RTE fields (FAQ questions and answers)', () => {
|
|
591
|
+
const styleStore = () =>
|
|
592
|
+
({
|
|
593
|
+
textStyles: {
|
|
594
|
+
textStyleOptions: {
|
|
595
|
+
headings: { style: { fontFamily: 'Oswald', fontSize: '1.5em' } },
|
|
596
|
+
paragraphs: { style: { fontFamily: 'Lato', fontSize: '1em' } },
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
}) as any;
|
|
600
|
+
|
|
601
|
+
const faqPageStore = (items: any[]) =>
|
|
602
|
+
({
|
|
603
|
+
pages: [
|
|
604
|
+
{
|
|
605
|
+
contents: [
|
|
606
|
+
{
|
|
607
|
+
id: 'faq-1',
|
|
608
|
+
type: 'faq-section',
|
|
609
|
+
settings: {
|
|
610
|
+
content: {
|
|
611
|
+
id: 'faq-1',
|
|
612
|
+
type: 'faq-section',
|
|
613
|
+
title: '',
|
|
614
|
+
titleStyle: {},
|
|
615
|
+
description: '',
|
|
616
|
+
descriptionStyle: {},
|
|
617
|
+
items,
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
}) as any;
|
|
625
|
+
|
|
626
|
+
const item = (id: string, questionStyle: any) => ({
|
|
627
|
+
id,
|
|
628
|
+
question: `Question ${id}`,
|
|
629
|
+
questionStyle,
|
|
630
|
+
answer: `Answer ${id}`,
|
|
631
|
+
answerStyle: { textStyleType: 'paragraphs' },
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const itemsOf = (store: any) =>
|
|
635
|
+
store.pages[0].contents[0].settings.content.items;
|
|
636
|
+
|
|
637
|
+
const fontOf = (value: unknown): string | undefined =>
|
|
638
|
+
JSON.stringify(value).match(/"fontFamily":"([^"]+)"/)?.[1];
|
|
639
|
+
|
|
640
|
+
it("converts every item's plain text to RTE, each from its own style", () => {
|
|
641
|
+
const { externalEditor } = useExternalRichTextEditor();
|
|
642
|
+
const store = faqPageStore([
|
|
643
|
+
item('1', { textStyleType: 'headings' }),
|
|
644
|
+
item('2', { fontFamily: 'Georgia', fontSize: '2em' }), // unlinked
|
|
645
|
+
]);
|
|
646
|
+
|
|
647
|
+
switchAllContentToRTE(store, externalEditor, styleStore());
|
|
648
|
+
|
|
649
|
+
const [first, second] = itemsOf(store);
|
|
650
|
+
|
|
651
|
+
expect(first.question.type).toBe('doc');
|
|
652
|
+
expect(fontOf(first.question)).toBe('Oswald');
|
|
653
|
+
expect(fontOf(first.answer)).toBe('Lato');
|
|
654
|
+
expect(fontOf(second.question)).toBe('Georgia');
|
|
655
|
+
externalEditor.destroy();
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('a global text-style change re-stamps only the items linked to it', () => {
|
|
659
|
+
const { externalEditor } = useExternalRichTextEditor();
|
|
660
|
+
const store = faqPageStore([
|
|
661
|
+
item('1', { textStyleType: 'headings' }),
|
|
662
|
+
item('2', { textStyleType: 'headings' }),
|
|
663
|
+
]);
|
|
664
|
+
|
|
665
|
+
switchAllContentToRTE(store, externalEditor, styleStore());
|
|
666
|
+
|
|
667
|
+
const answerBefore = itemsOf(store)[0].answer;
|
|
668
|
+
|
|
669
|
+
transformToRTE(store, externalEditor, undefined, undefined, 'headings', {
|
|
670
|
+
fontFamily: 'Playfair Display',
|
|
671
|
+
} as TextStyleDeclaration);
|
|
672
|
+
|
|
673
|
+
const [first, second] = itemsOf(store);
|
|
674
|
+
|
|
675
|
+
expect(fontOf(first.question)).toBe('Playfair Display');
|
|
676
|
+
expect(fontOf(second.question)).toBe('Playfair Display');
|
|
677
|
+
expect(first.answer).toEqual(answerBefore);
|
|
678
|
+
externalEditor.destroy();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('selecting the section leaves already-authored item documents alone', () => {
|
|
682
|
+
const { externalEditor } = useExternalRichTextEditor();
|
|
683
|
+
const store = faqPageStore([item('1', { textStyleType: 'headings' })]);
|
|
684
|
+
|
|
685
|
+
switchAllContentToRTE(store, externalEditor, styleStore());
|
|
686
|
+
|
|
687
|
+
const authored = itemsOf(store)[0].question;
|
|
688
|
+
|
|
689
|
+
transformToRTE(store, externalEditor, styleStore(), {
|
|
690
|
+
id: 'faq-1',
|
|
691
|
+
} as any);
|
|
692
|
+
|
|
693
|
+
expect(itemsOf(store)[0].question).toEqual(authored);
|
|
694
|
+
externalEditor.destroy();
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
|
|
588
698
|
describe('sanitiseClientSideRTE', () => {
|
|
589
699
|
it('removes forbidden tags entirely', () => {
|
|
590
700
|
const input =
|
|
@@ -31,6 +31,7 @@ import createDomPurify, { type DOMPurify } from 'dompurify';
|
|
|
31
31
|
import Color from 'color';
|
|
32
32
|
import { isHrefPageId } from '../composables/rich-text-editor/hyperlink';
|
|
33
33
|
import {
|
|
34
|
+
RTE_ITEM_FIELD_PAIRS,
|
|
34
35
|
RTE_SECTION_FIELD_PAIRS,
|
|
35
36
|
type RTEFieldPair,
|
|
36
37
|
} from '@shared/color-strategies/rte-field-pairs';
|
|
@@ -547,6 +548,46 @@ const switchRTEField = (
|
|
|
547
548
|
}
|
|
548
549
|
};
|
|
549
550
|
|
|
551
|
+
// Items are visited as their own "content": each item carries the style that drives its
|
|
552
|
+
// text, so the section-field helpers apply unchanged.
|
|
553
|
+
const forEachRteItem = (
|
|
554
|
+
sectionType: string,
|
|
555
|
+
content: unknown,
|
|
556
|
+
visit: (item: RTESectionContent, pair: RTEFieldPair) => void,
|
|
557
|
+
): void => {
|
|
558
|
+
for (const pair of RTE_ITEM_FIELD_PAIRS[sectionType] ?? []) {
|
|
559
|
+
const items = (content as Record<string, unknown>)[pair.itemsField];
|
|
560
|
+
|
|
561
|
+
if (Array.isArray(items)) {
|
|
562
|
+
items.forEach((item) => visit(item as RTESectionContent, pair));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Convert a new list item's plain text to RTE, as adding its section would. Goes through
|
|
569
|
+
* `transformRTEField` (element-first colour), so an item that copied a sibling's
|
|
570
|
+
* strategy-coloured style bakes that colour rather than the linked global one.
|
|
571
|
+
*/
|
|
572
|
+
export const switchRteItemFieldsToRTE = (
|
|
573
|
+
sectionType: string,
|
|
574
|
+
item: Record<string, unknown>,
|
|
575
|
+
externalEditor: Editor,
|
|
576
|
+
uiStyleSettingsStore?: StyleSettingsState,
|
|
577
|
+
): void => {
|
|
578
|
+
RTE_ITEM_FIELD_PAIRS[sectionType]?.forEach((pair) =>
|
|
579
|
+
transformRTEField(
|
|
580
|
+
item as unknown as RTESectionContent,
|
|
581
|
+
pair,
|
|
582
|
+
externalEditor,
|
|
583
|
+
uiStyleSettingsStore,
|
|
584
|
+
item as unknown as Content,
|
|
585
|
+
undefined,
|
|
586
|
+
undefined,
|
|
587
|
+
),
|
|
588
|
+
);
|
|
589
|
+
};
|
|
590
|
+
|
|
550
591
|
export const transformToRTE = (
|
|
551
592
|
uiPageSettingStore: PageSettingsState,
|
|
552
593
|
externalEditor: Editor,
|
|
@@ -575,6 +616,24 @@ export const transformToRTE = (
|
|
|
575
616
|
}
|
|
576
617
|
}
|
|
577
618
|
|
|
619
|
+
const isNewContent = !!newContent && newContent.id === contentItem.id;
|
|
620
|
+
|
|
621
|
+
forEachRteItem(
|
|
622
|
+
contentItem.type,
|
|
623
|
+
contentItem.settings.content,
|
|
624
|
+
(item, pair) =>
|
|
625
|
+
transformRTEField(
|
|
626
|
+
item,
|
|
627
|
+
pair,
|
|
628
|
+
externalEditor,
|
|
629
|
+
uiStyleSettingsStore,
|
|
630
|
+
// An unlinked item of the section being added bakes its own style.
|
|
631
|
+
isNewContent ? (item as unknown as Content) : undefined,
|
|
632
|
+
type,
|
|
633
|
+
newStyle,
|
|
634
|
+
),
|
|
635
|
+
);
|
|
636
|
+
|
|
578
637
|
if (contentItem.type === 'text') {
|
|
579
638
|
const textContent = contentItem.settings.content as TextContent;
|
|
580
639
|
|
|
@@ -699,6 +758,13 @@ export const switchAllContentToRTE = (
|
|
|
699
758
|
}
|
|
700
759
|
}
|
|
701
760
|
|
|
761
|
+
forEachRteItem(
|
|
762
|
+
contentItem.type,
|
|
763
|
+
contentItem.settings.content,
|
|
764
|
+
(item, pair) =>
|
|
765
|
+
switchRTEField(item, pair, externalEditor, uiStyleSettingsStore),
|
|
766
|
+
);
|
|
767
|
+
|
|
702
768
|
if (contentItem.type === 'text') {
|
|
703
769
|
const textContent = contentItem.settings.content as TextContent;
|
|
704
770
|
|
|
@@ -161,6 +161,7 @@ export const validatorDict: Record<ItemFieldType, Validator> = {
|
|
|
161
161
|
'color-replacements': dummyValidator,
|
|
162
162
|
'testimonial-items': dummyValidator,
|
|
163
163
|
'text-items': dummyValidator,
|
|
164
|
+
'list-items': dummyValidator,
|
|
164
165
|
'page-dropdown': dummyValidator,
|
|
165
166
|
shape: dummyValidator,
|
|
166
167
|
'logo-selection': dummyValidator,
|
|
@@ -204,6 +205,7 @@ export const adminValidatorsDict: Record<ItemFieldType, Validator> = {
|
|
|
204
205
|
'color-replacements': dummyValidator,
|
|
205
206
|
'testimonial-items': dummyValidator,
|
|
206
207
|
'text-items': dummyValidator,
|
|
208
|
+
'list-items': dummyValidator,
|
|
207
209
|
'page-dropdown': dummyValidator,
|
|
208
210
|
shape: dummyValidator,
|
|
209
211
|
'logo-selection': dummyValidator,
|
|
@@ -1262,7 +1262,10 @@
|
|
|
1262
1262
|
"medium": "Medium",
|
|
1263
1263
|
"large": "Large",
|
|
1264
1264
|
"bigImage": "Big Image",
|
|
1265
|
-
"columns": "Columns"
|
|
1265
|
+
"columns": "Columns",
|
|
1266
|
+
"faqCenteredAccordion": "Centered accordion",
|
|
1267
|
+
"faqSplitAccordion": "Split accordion",
|
|
1268
|
+
"faqGrid": "Grid"
|
|
1266
1269
|
},
|
|
1267
1270
|
"model": {
|
|
1268
1271
|
"defaultBackgroundColor": "Default background color",
|
|
@@ -1640,7 +1643,22 @@
|
|
|
1640
1643
|
"enterTextHerePlaceholder": "Enter text here...",
|
|
1641
1644
|
"bookNowLabel": "Book Now",
|
|
1642
1645
|
"shippingLabel": "Enable Shipping Address",
|
|
1643
|
-
"shippingPlaceholder": "Require customers to enter a shipping address during checkout."
|
|
1646
|
+
"shippingPlaceholder": "Require customers to enter a shipping address during checkout.",
|
|
1647
|
+
"faqSectionDisplayName": "FAQ",
|
|
1648
|
+
"faqItemsDisplayName": "Items",
|
|
1649
|
+
"defaultFaqTitle": "Frequently Asked Questions",
|
|
1650
|
+
"defaultFaqDescription": "Find answers to common questions about our products, services, or business",
|
|
1651
|
+
"defaultFaqQuestion": "What services do you offer?",
|
|
1652
|
+
"defaultFaqAnswer": "We provide a range of products and services tailored to meet your needs. Contact us to learn more about how we can help.",
|
|
1653
|
+
"faqNewQuestion": "New question?",
|
|
1654
|
+
"faqNewAnswer": "Add the answer here.",
|
|
1655
|
+
"faqQuestionTitle": "Question",
|
|
1656
|
+
"faqAnswerTitle": "Answer",
|
|
1657
|
+
"faqQuestionPlaceholder": "Enter a question",
|
|
1658
|
+
"faqAnswerPlaceholder": "Enter the answer",
|
|
1659
|
+
"listItemsAddItem": "Add item",
|
|
1660
|
+
"listItemsMaxReached": "Max {{max}} items reached",
|
|
1661
|
+
"listItemsDeleteLabel": "Delete item"
|
|
1644
1662
|
},
|
|
1645
1663
|
"formFields": {
|
|
1646
1664
|
"field": "Field",
|
|
@@ -121,7 +121,8 @@ const converterTr = (key) => tr(`converter.${key}`);
|
|
|
121
121
|
const backgroundSettingsTr = (key) => tr(`backgroundSettings.${key}`);
|
|
122
122
|
const colorPickerTr = (key) => tr(`colorPicker.${key}`);
|
|
123
123
|
const colorReplacementFieldTr = (key) => tr(`colorReplacementField.${key}`);
|
|
124
|
-
const contentSettingsTr = (key
|
|
124
|
+
const contentSettingsTr = (key, valuesToInterpolate = {}) =>
|
|
125
|
+
tr(`contentSettings.${key}`, valuesToInterpolate);
|
|
125
126
|
const formFieldsTr = (key) => tr(`formFields.${key}`);
|
|
126
127
|
const genericTextInputTr = (key) => tr(`genericTextInput.${key}`);
|
|
127
128
|
const imageFieldTr = (key) => tr(`imageField.${key}`);
|
|
@@ -182,3 +182,27 @@ describe('ensureBrandPageItemIds', () => {
|
|
|
182
182
|
).not.toThrow();
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
|
+
|
|
186
|
+
describe('ensureBrandPageItemIds — FAQ items', () => {
|
|
187
|
+
it('fills a missing FAQ item id and keeps an existing one', () => {
|
|
188
|
+
const model = {
|
|
189
|
+
pages: [
|
|
190
|
+
{
|
|
191
|
+
contents: [
|
|
192
|
+
{
|
|
193
|
+
type: 'faq-section',
|
|
194
|
+
items: [{ id: 'keep', question: 'Q1' }, { question: 'Q2' }],
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
} as unknown as BrandPageModel;
|
|
200
|
+
|
|
201
|
+
ensureBrandPageItemIds(model);
|
|
202
|
+
|
|
203
|
+
const faqItems = (model.pages[0].contents[0] as any).items;
|
|
204
|
+
|
|
205
|
+
expect(faqItems[0].id).toBe('keep');
|
|
206
|
+
expect(faqItems[1].id).toMatch(UUID_RE);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { SECTION_TOOL_MAP } from './ai-toolcall-parser';
|
|
3
|
+
|
|
4
|
+
describe('SECTION_TOOL_MAP', () => {
|
|
5
|
+
test('no AI tool builds an FAQ section — FAQ is not AI-supported', () => {
|
|
6
|
+
const sectionTypes = Object.values(SECTION_TOOL_MAP).map(
|
|
7
|
+
(tool) => tool.sectionType as string,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
expect(sectionTypes).not.toContain('faq-section');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -272,6 +272,10 @@ export const FIELD_SLOT_MAP: Record<string, ColorSlot> = {
|
|
|
272
272
|
nameStyle: 'onSurface',
|
|
273
273
|
positionStyle: 'onSurface',
|
|
274
274
|
testimonialStyle: 'onSurface',
|
|
275
|
+
// Per-item styles of each FAQ item (RTE_ITEM_FIELD_PAIRS). The rows sit on the
|
|
276
|
+
// section band — there is no raised card.
|
|
277
|
+
questionStyle: 'onSurface',
|
|
278
|
+
answerStyle: 'onSurface',
|
|
275
279
|
};
|
|
276
280
|
|
|
277
281
|
/**
|
|
@@ -21,6 +21,10 @@ export const RTE_SECTION_FIELD_PAIRS: Record<string, RTEFieldPair[]> = {
|
|
|
21
21
|
{ textField: 'title', styleField: 'titleStyle' },
|
|
22
22
|
{ textField: 'description', styleField: 'descriptionStyle' },
|
|
23
23
|
],
|
|
24
|
+
'faq-section': [
|
|
25
|
+
{ textField: 'title', styleField: 'titleStyle' },
|
|
26
|
+
{ textField: 'description', styleField: 'descriptionStyle' },
|
|
27
|
+
],
|
|
24
28
|
'images-section': [
|
|
25
29
|
{ textField: 'title', styleField: 'titleStyle' },
|
|
26
30
|
{ textField: 'description', styleField: 'descriptionStyle' },
|
|
@@ -60,6 +64,25 @@ export const RTE_SECTION_FIELD_PAIRS: Record<string, RTEFieldPair[]> = {
|
|
|
60
64
|
],
|
|
61
65
|
};
|
|
62
66
|
|
|
67
|
+
/**
|
|
68
|
+
* RTE fields that live on each entry of an item list, each driven by a style field
|
|
69
|
+
* on the SAME item — every question is its own title-like field. The rewrap walks and
|
|
70
|
+
* the colour strategies visit every item for these.
|
|
71
|
+
*
|
|
72
|
+
* The Text block's `textItems` is not listed: its walk has its own legacy fallbacks
|
|
73
|
+
* (single-text mode, section-level `textStyle`) in `transformToRTE`.
|
|
74
|
+
*/
|
|
75
|
+
export interface RTEItemFieldPair extends RTEFieldPair {
|
|
76
|
+
itemsField: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const RTE_ITEM_FIELD_PAIRS: Record<string, RTEItemFieldPair[]> = {
|
|
80
|
+
'faq-section': [
|
|
81
|
+
{ itemsField: 'items', textField: 'question', styleField: 'questionStyle' },
|
|
82
|
+
{ itemsField: 'items', textField: 'answer', styleField: 'answerStyle' },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
|
|
63
86
|
/**
|
|
64
87
|
* The `text` section is absent from the map above: on brandSite its RTE lives
|
|
65
88
|
* per-item in `textItems[]`, which the editor walks separately. On
|
|
@@ -206,6 +206,22 @@
|
|
|
206
206
|
"enterTextHerePlaceholder": "Enter text here...",
|
|
207
207
|
"bookNowLabel": "Book Now",
|
|
208
208
|
"shippingLabel": "Enable Shipping Address",
|
|
209
|
-
"shippingPlaceholder": "Require customers to enter a shipping address during checkout."
|
|
209
|
+
"shippingPlaceholder": "Require customers to enter a shipping address during checkout.",
|
|
210
|
+
|
|
211
|
+
"faqSectionDisplayName": "FAQ",
|
|
212
|
+
"faqItemsDisplayName": "Items",
|
|
213
|
+
"defaultFaqTitle": "Frequently Asked Questions",
|
|
214
|
+
"defaultFaqDescription": "Find answers to common questions about our products, services, or business",
|
|
215
|
+
"defaultFaqQuestion": "What services do you offer?",
|
|
216
|
+
"defaultFaqAnswer": "We provide a range of products and services tailored to meet your needs. Contact us to learn more about how we can help.",
|
|
217
|
+
"faqNewQuestion": "New question?",
|
|
218
|
+
"faqNewAnswer": "Add the answer here.",
|
|
219
|
+
"faqQuestionTitle": "Question",
|
|
220
|
+
"faqAnswerTitle": "Answer",
|
|
221
|
+
"faqQuestionPlaceholder": "Enter a question",
|
|
222
|
+
"faqAnswerPlaceholder": "Enter the answer",
|
|
223
|
+
"listItemsAddItem": "Add item",
|
|
224
|
+
"listItemsMaxReached": "Max {{max}} items reached",
|
|
225
|
+
"listItemsDeleteLabel": "Delete item"
|
|
210
226
|
}
|
|
211
227
|
}
|