@lightspeed/crane 1.2.3 → 1.2.5
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/CHANGELOG.md +30 -0
- package/README.md +8 -0
- package/dist/app.d.mts +74 -83
- package/dist/app.d.ts +74 -83
- package/dist/app.mjs +1 -1
- package/dist/cli.mjs +10 -8
- package/package.json +14 -7
- package/template/layouts/catalog/example-catalog/Main.vue +10 -0
- package/template/layouts/category/example-category/Main.vue +11 -0
- package/template/layouts/product/example-product/Main.vue +35 -0
- package/template/page-templates/example-template/pages/catalog.ts +2 -1
- package/template/page-templates/example-template/pages/category.ts +2 -1
- package/template/page-templates/example-template/pages/product.ts +2 -1
- package/template/preview/sections/preview.html +89 -0
- package/template/preview/shared/preview.ts +300 -0
- package/template/preview/shared/utils.ts +3 -0
- package/template/preview/vite.config.js +32 -0
- package/types.d.ts +12 -12
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<slot :name="Slot.CATEGORY_TITLE" />
|
|
4
|
+
<slot :name="Slot.PRODUCT_LIST" />
|
|
5
|
+
<slot :name="Slot.BOTTOM_BAR" />
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { CategoryLayoutSlot as Slot } from '@lightspeed/crane';
|
|
11
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="{ 'product-details--top-title-navigation': isProductNameAlwaysFirstOnMobile }">
|
|
3
|
+
<slot :name="Slot.TOP_BAR" />
|
|
4
|
+
<div
|
|
5
|
+
:class="availableProductClasses"
|
|
6
|
+
itemtype="http://schema.org/Product"
|
|
7
|
+
:itemscope="showProductDetailsProductPrice"
|
|
8
|
+
>
|
|
9
|
+
<slot :name="Slot.GALLERY" />
|
|
10
|
+
<slot :name="Slot.SIDEBAR" />
|
|
11
|
+
<slot :name="Slot.DESCRIPTION" />
|
|
12
|
+
<div class="clearboth" />
|
|
13
|
+
</div>
|
|
14
|
+
<slot :name="Slot.REVIEW_LIST" />
|
|
15
|
+
<slot :name="Slot.RELATED_PRODUCTS" />
|
|
16
|
+
<slot :name="Slot.BOTTOM_BAR" />
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import { HTMLAttributes } from 'vue';
|
|
22
|
+
import { ProductLayoutSlot as Slot } from '@lightspeed/crane';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
availableProductClasses?: HTMLAttributes['class'],
|
|
26
|
+
showProductDetailsProductPrice?: boolean,
|
|
27
|
+
isProductNameAlwaysFirstOnMobile?: boolean,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
withDefaults(defineProps<Props>(), {
|
|
31
|
+
isProductNameAlwaysFirstOnMobile: false,
|
|
32
|
+
availableProductClasses: '',
|
|
33
|
+
showProductDetailsProductPrice: true,
|
|
34
|
+
});
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Section Preview</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
|
|
8
|
+
<!-- Vue 3 -->
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.33/dist/vue.global.prod.js"></script>
|
|
10
|
+
|
|
11
|
+
<!-- Bootstrap and Fonts -->
|
|
12
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
.top-bar {
|
|
17
|
+
position: fixed;
|
|
18
|
+
top: 0;
|
|
19
|
+
left: 0;
|
|
20
|
+
right: 0;
|
|
21
|
+
background-color: #fff;
|
|
22
|
+
border-bottom: 1px solid #dee2e6;
|
|
23
|
+
padding: 12px 20px;
|
|
24
|
+
z-index: 1000;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: 12px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.label-text {
|
|
31
|
+
font-size: 30px;
|
|
32
|
+
margin: 0;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body>
|
|
37
|
+
|
|
38
|
+
<!-- Top Bar -->
|
|
39
|
+
<div class="top-bar">
|
|
40
|
+
<span class="label-text">Section Preview |</span>
|
|
41
|
+
<select id="showcaseDropdown" class="form-select w-auto"></select>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Main Content -->
|
|
45
|
+
<div id="app"></div>
|
|
46
|
+
<div id="blanket"></div>
|
|
47
|
+
|
|
48
|
+
<!-- SSR Mocking -->
|
|
49
|
+
<script type="module">
|
|
50
|
+
window.EcVue = {
|
|
51
|
+
...window.Vue,
|
|
52
|
+
ssrUtils: {
|
|
53
|
+
ensureValidVNode: vnode => vnode,
|
|
54
|
+
},
|
|
55
|
+
ssrContextKey: Symbol('ssrContextKey'),
|
|
56
|
+
initDirectivesForSSR: () => {},
|
|
57
|
+
};
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<!-- Main Script -->
|
|
61
|
+
<script type="module">
|
|
62
|
+
import { renderShowcase, dropdownOptions, setDistFolderPath } from "../shared/preview.js";
|
|
63
|
+
|
|
64
|
+
setDistFolderPath("../../../dist");
|
|
65
|
+
|
|
66
|
+
const showcaseModules = import.meta.glob('../../dist/sections/*/js/showcases/*.mjs');
|
|
67
|
+
const select = document.getElementById('showcaseDropdown');
|
|
68
|
+
const showCaseOptions = dropdownOptions(showcaseModules);
|
|
69
|
+
|
|
70
|
+
for (const { value, label } of showCaseOptions) {
|
|
71
|
+
const option = document.createElement('option');
|
|
72
|
+
option.value = value;
|
|
73
|
+
option.textContent = label;
|
|
74
|
+
select.appendChild(option);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
select.addEventListener('change', async (e) => {
|
|
78
|
+
const [sectionName, showcaseId] = e.target.value.split(':');
|
|
79
|
+
renderShowcase(sectionName, showcaseId);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Render initial showcase
|
|
83
|
+
if (select.value) {
|
|
84
|
+
const [sectionName, showcaseId] = select.value.split(':');
|
|
85
|
+
renderShowcase(sectionName, showcaseId);
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
</body>
|
|
89
|
+
</html>
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { loadModule} from "./utils.ts";
|
|
4
|
+
|
|
5
|
+
let distFolderPath: string | null = null;
|
|
6
|
+
|
|
7
|
+
export function setDistFolderPath(path: string): void {
|
|
8
|
+
distFolderPath = path;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface HSLColor {
|
|
12
|
+
h: number;
|
|
13
|
+
s: number;
|
|
14
|
+
l: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface RGBAColor {
|
|
18
|
+
r: number;
|
|
19
|
+
g: number;
|
|
20
|
+
b: number;
|
|
21
|
+
a: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ColorObject {
|
|
25
|
+
hex: string;
|
|
26
|
+
hsl: HSLColor;
|
|
27
|
+
rgba: RGBAColor;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hexToColorObject(hex: string): ColorObject {
|
|
31
|
+
const match = /^#?([0-9a-fA-F]{6})$/.exec(hex);
|
|
32
|
+
if (!match) throw new Error("Invalid hex color format");
|
|
33
|
+
const cleanHex = match[1].toLowerCase();
|
|
34
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
35
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
36
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
37
|
+
const a = 255;
|
|
38
|
+
const rNorm = r / 255, gNorm = g / 255, bNorm = b / 255;
|
|
39
|
+
const max = Math.max(rNorm, gNorm, bNorm), min = Math.min(rNorm, gNorm, bNorm);
|
|
40
|
+
const delta = max - min;
|
|
41
|
+
let h = 0, s = 0, l = (max + min) / 2;
|
|
42
|
+
if (delta !== 0) {
|
|
43
|
+
s = delta / (1 - Math.abs(2 * l - 1));
|
|
44
|
+
switch (max) {
|
|
45
|
+
case rNorm: h = ((gNorm - bNorm) / delta) % 6; break;
|
|
46
|
+
case gNorm: h = (bNorm - rNorm) / delta + 2; break;
|
|
47
|
+
case bNorm: h = (rNorm - gNorm) / delta + 4; break;
|
|
48
|
+
}
|
|
49
|
+
h *= 60;
|
|
50
|
+
if (h < 0) h += 360;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
hex: `#${cleanHex}${a.toString(16).padStart(2, '0')}`,
|
|
54
|
+
hsl: { h: Math.round(h), s: +(s * 100).toFixed(1), l: +(l * 100).toFixed(1) },
|
|
55
|
+
rgba: { r, g, b, a: 1 },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function updateHexColors(obj: any): any {
|
|
60
|
+
// Matches either 3-digit (#RGB) or 6-digit (#RRGGBB) hex
|
|
61
|
+
const hexRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
62
|
+
|
|
63
|
+
function recurse(value: any): any {
|
|
64
|
+
if (Array.isArray(value)) return value.map(recurse);
|
|
65
|
+
if (value && typeof value === 'object') {
|
|
66
|
+
for (const key in value) {
|
|
67
|
+
const v = value[key];
|
|
68
|
+
if (key === 'color' && typeof v === 'string' && hexRegex.test(v)) {
|
|
69
|
+
// If it’s a 3-digit hex, expand it to 6 digits
|
|
70
|
+
const expanded = v.replace(
|
|
71
|
+
/^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/,
|
|
72
|
+
(_m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`
|
|
73
|
+
);
|
|
74
|
+
value[key] = hexToColorObject(expanded);
|
|
75
|
+
} else {
|
|
76
|
+
value[key] = recurse(v);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
return recurse(obj);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const replaceGlobalFont = (obj: any, font: string): void => {
|
|
86
|
+
Object.entries(obj).forEach(([k, v]) => {
|
|
87
|
+
if (v && typeof v === 'object') {
|
|
88
|
+
replaceGlobalFont(v, font);
|
|
89
|
+
} else if (k === 'font' && v === 'global.fontFamily.body') {
|
|
90
|
+
obj[k] = font;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function overrideSettingsFromShowcase(content: any, showcases: any): any {
|
|
96
|
+
return Object.fromEntries(Object.entries(content).map(([k, v]) => [k, showcases[k] ?? v]));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function mergeDesign(
|
|
100
|
+
design: Record<string, any>,
|
|
101
|
+
showcase: Record<string, any>
|
|
102
|
+
): Record<string, any> {
|
|
103
|
+
const result: Record<string, any> = { ...design };
|
|
104
|
+
|
|
105
|
+
Object.keys(design).forEach((key) => {
|
|
106
|
+
if (key in showcase) {
|
|
107
|
+
const base = design[key];
|
|
108
|
+
const override = showcase[key];
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
base !== null && override !== null &&
|
|
112
|
+
typeof base === 'object' && typeof override === 'object' &&
|
|
113
|
+
!Array.isArray(base) && !Array.isArray(override)
|
|
114
|
+
) {
|
|
115
|
+
// shallow merge nested objects
|
|
116
|
+
result[key] = { ...base, ...override };
|
|
117
|
+
} else {
|
|
118
|
+
// replace with showcase value (covers primitives & arrays)
|
|
119
|
+
result[key] = override;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function designTransformer(design: Record<string, any>, showCaseDesign: Record<string, any> ): Record<string, any> {
|
|
128
|
+
const parsedDesign: Record<string, any> = {};
|
|
129
|
+
const parsedShowcaseDesign: Record<string, any> = {};
|
|
130
|
+
Object.entries(design).forEach(([key, comp]) => {
|
|
131
|
+
parsedDesign[key] = comp?.defaults;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
Object.entries(showCaseDesign).forEach(([key, comp]) => {
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
136
|
+
const { type, ...withoutType } = comp;
|
|
137
|
+
parsedShowcaseDesign[key] = type == 'TEXT' ? { ... withoutType, visible: true} : withoutType
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
let overridenDesign = mergeDesign(parsedDesign, parsedShowcaseDesign)
|
|
141
|
+
overridenDesign = updateHexColors(overridenDesign);
|
|
142
|
+
replaceGlobalFont(overridenDesign, 'Roboto');
|
|
143
|
+
return overridenDesign;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function processImage(component: any, sectionName: string, key: string): Record<string, any> {
|
|
147
|
+
const assetLocation = `${distFolderPath}/sections/${sectionName}/assets/`;
|
|
148
|
+
const set = component.defaults?.set || component.imageData?.set;
|
|
149
|
+
const newSet = {
|
|
150
|
+
'cropped-webp-100x200': { url: assetLocation + set.MOBILE_WEBP_LOW_RES.url },
|
|
151
|
+
'cropped-webp-1000x2000': { url: assetLocation + set.MOBILE_WEBP_HI_RES.url },
|
|
152
|
+
'webp-200x200': { url: assetLocation + set.WEBP_LOW_RES.url },
|
|
153
|
+
'webp-2000x2000': { url: assetLocation + set.WEBP_HI_2X_RES.url },
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
[key]: {
|
|
157
|
+
set: newSet,
|
|
158
|
+
...(component.imageData?.borderInfo && { borderInfo: component.imageData.borderInfo }),
|
|
159
|
+
...(component.imageData && { bucket: {} })
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function processComponent(key: string, component: any, translations: any, sectionName?: string): any {
|
|
165
|
+
if (!component?.type) return '';
|
|
166
|
+
switch (component.type) {
|
|
167
|
+
case 'INPUTBOX':
|
|
168
|
+
case 'TEXTAREA': {
|
|
169
|
+
const text = component?.defaults?.text ?? component?.text ?? component?.placeholder;
|
|
170
|
+
return { [key]: translations[text] };
|
|
171
|
+
}
|
|
172
|
+
case 'DIVIDER':
|
|
173
|
+
return { [key]: translations[component.label] };
|
|
174
|
+
case 'DECK': {
|
|
175
|
+
const defaultSettings = component.cards?.defaultCardContent?.settings;
|
|
176
|
+
if (defaultSettings) {
|
|
177
|
+
const result: Record<string, any> = {};
|
|
178
|
+
Object.entries(defaultSettings).forEach(([k, c]) => {
|
|
179
|
+
Object.assign(result, processComponent(k, c, translations));
|
|
180
|
+
});
|
|
181
|
+
return { [key]: result };
|
|
182
|
+
} else {
|
|
183
|
+
const cards = component.cards.map((card: any) => {
|
|
184
|
+
const cardContent: Record<string, any> = {};
|
|
185
|
+
Object.entries(card.settings).forEach(([k, c]) => {
|
|
186
|
+
Object.assign(cardContent, processComponent(k, c, translations, sectionName));
|
|
187
|
+
});
|
|
188
|
+
return { settings: cardContent };
|
|
189
|
+
});
|
|
190
|
+
return { [key]: { cards } };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
case 'BUTTON': {
|
|
194
|
+
const button = component.defaults || component;
|
|
195
|
+
return {
|
|
196
|
+
[key]: {
|
|
197
|
+
title: translations[button.title],
|
|
198
|
+
type: button.buttonType,
|
|
199
|
+
link: button.link,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
case 'TOGGLE':
|
|
204
|
+
return { [key]: component.defaults };
|
|
205
|
+
case 'SELECTBOX':
|
|
206
|
+
return { [key]: component.defaults.value };
|
|
207
|
+
case 'IMAGE':
|
|
208
|
+
return processImage(component, sectionName!, key);
|
|
209
|
+
default:
|
|
210
|
+
console.warn(`Unknown type: ${component.type}`);
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function getContentToRender(
|
|
216
|
+
content: any,
|
|
217
|
+
showcase: any,
|
|
218
|
+
contentTranslations: any,
|
|
219
|
+
showcaseTranslations: any,
|
|
220
|
+
sectionName: string
|
|
221
|
+
): any {
|
|
222
|
+
const parsedContent = Object.entries(content).reduce((acc, [k, c]) => {
|
|
223
|
+
return { ...acc, ...processComponent(k, c, contentTranslations, sectionName) };
|
|
224
|
+
}, {});
|
|
225
|
+
|
|
226
|
+
const parsedShowcase = Object.entries(showcase).reduce((acc, [k, c]) => {
|
|
227
|
+
return { ...acc, ...processComponent(k, c, showcaseTranslations, sectionName) };
|
|
228
|
+
}, {});
|
|
229
|
+
|
|
230
|
+
return overrideSettingsFromShowcase(parsedContent, parsedShowcase);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function dropdownOptions(showcaseModules: Record<string, any>): Array<{ value: string; label: string }> {
|
|
234
|
+
return Object.keys(showcaseModules).map(path => {
|
|
235
|
+
const match = path.match(/\/sections\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
|
|
236
|
+
if (!match) return null;
|
|
237
|
+
return {
|
|
238
|
+
value: `${match[1]}:${match[2]}`,
|
|
239
|
+
label: `${match[1]}: showcase ${match[2]}`,
|
|
240
|
+
};
|
|
241
|
+
}).filter(Boolean) as Array<{ value: string; label: string }>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function loadSectionCss(sectionName: string): void {
|
|
245
|
+
const link = document.createElement('link');
|
|
246
|
+
link.rel = 'stylesheet';
|
|
247
|
+
link.href = `${distFolderPath}/sections/${sectionName}/js/main/client/assets/client.css`;
|
|
248
|
+
document.head.appendChild(link);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function renderShowcase(sectionName: string, showcaseId: string): Promise<void> {
|
|
252
|
+
const client = await loadModule(`${distFolderPath}/sections/${sectionName}/js/main/client/client.js`);
|
|
253
|
+
const content = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/content.mjs`);
|
|
254
|
+
const contentTranslations = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/translations.mjs`);
|
|
255
|
+
const showcaseTranslations = await loadModule(`${distFolderPath}/sections/${sectionName}/js/showcases/translations.mjs`);
|
|
256
|
+
const showcase = await loadModule(`${distFolderPath}/sections/${sectionName}/js/showcases/${showcaseId}.mjs`);
|
|
257
|
+
const design = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/design.mjs`);
|
|
258
|
+
|
|
259
|
+
const { mount } = client.default.init();
|
|
260
|
+
|
|
261
|
+
const ovveridenDesign = designTransformer(design.default, showcase.default.design || {});
|
|
262
|
+
|
|
263
|
+
loadSectionCss(sectionName);
|
|
264
|
+
|
|
265
|
+
const backgroundDesign = {
|
|
266
|
+
background: {
|
|
267
|
+
background: {
|
|
268
|
+
type: 'solid',
|
|
269
|
+
solid: {
|
|
270
|
+
color: {
|
|
271
|
+
raw: '#F9F9F9',
|
|
272
|
+
hex: '#F9F9F9',
|
|
273
|
+
rgba: { r: 19, g: 19, b: 19, a: 1.0 },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
color: 'global.color.background',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const overriddenContent = getContentToRender(
|
|
282
|
+
content.default,
|
|
283
|
+
showcase.default.content || {},
|
|
284
|
+
contentTranslations.default.en,
|
|
285
|
+
showcaseTranslations.default.en,
|
|
286
|
+
sectionName
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
mount('#app', {
|
|
290
|
+
context: {
|
|
291
|
+
globalDesign: { color: 'global.color.background' },
|
|
292
|
+
},
|
|
293
|
+
data: {
|
|
294
|
+
content: overriddenContent,
|
|
295
|
+
design: { ...ovveridenDesign, ...backgroundDesign },
|
|
296
|
+
defaults: {},
|
|
297
|
+
background: {},
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
root: '.',
|
|
5
|
+
server: {
|
|
6
|
+
fs: { strict: false },
|
|
7
|
+
},
|
|
8
|
+
plugins: [
|
|
9
|
+
{
|
|
10
|
+
name: 'print-preview-url',
|
|
11
|
+
configureServer(server) {
|
|
12
|
+
server.httpServer?.once('listening', () => {
|
|
13
|
+
const baseUrl = `http://localhost:${server.config.server.port || 5173}`;
|
|
14
|
+
console.log("Preview URL: ", baseUrl + '/preview/sections/preview.html\n');
|
|
15
|
+
console.log('🛑 Press Ctrl+C to stop the local server 🛑\n');
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'no-vite-cache',
|
|
21
|
+
configureServer(server) {
|
|
22
|
+
// invalidate caches so that after build when user refreshes the page, the latest .mjs files are fetched
|
|
23
|
+
server.middlewares.use((req, res, next) => {
|
|
24
|
+
if (req.url?.includes('/sections/')) {
|
|
25
|
+
server.moduleGraph.invalidateAll();
|
|
26
|
+
}
|
|
27
|
+
next();
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
});
|
package/types.d.ts
CHANGED
|
@@ -9,13 +9,13 @@ declare module '*.vue' {
|
|
|
9
9
|
|
|
10
10
|
type ActionLinkType
|
|
11
11
|
= 'SCROLL_TO_TILE'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
| 'HYPER_LINK'
|
|
13
|
+
| 'MAIL_LINK'
|
|
14
|
+
| 'TEL_LINK'
|
|
15
|
+
| 'GO_TO_STORE'
|
|
16
|
+
| 'GO_TO_STORE_LINK'
|
|
17
|
+
| 'GO_TO_PAGE'
|
|
18
|
+
| 'GO_TO_CATEGORY';
|
|
19
19
|
|
|
20
20
|
interface ButtonContentData {
|
|
21
21
|
readonly title: string;
|
|
@@ -78,7 +78,7 @@ type LogoType = 'TEXT' | 'IMAGE';
|
|
|
78
78
|
interface LogoContentData {
|
|
79
79
|
readonly type: LogoType;
|
|
80
80
|
readonly text: string;
|
|
81
|
-
readonly image: ImageContentData
|
|
81
|
+
readonly image: ImageContentData;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
type GlobalColorsString =
|
|
@@ -382,7 +382,7 @@ type ContentEditor =
|
|
|
382
382
|
|
|
383
383
|
type InferContentType<T extends Record<string, ContentEditor>> = {
|
|
384
384
|
readonly [P in keyof T]: MapEditorContentTypes[T[P]['type']]
|
|
385
|
-
}
|
|
385
|
+
};
|
|
386
386
|
|
|
387
387
|
type MapEditorDesignTypes = {
|
|
388
388
|
readonly TEXT: string;
|
|
@@ -444,8 +444,8 @@ interface LogoDesignEditor {
|
|
|
444
444
|
}
|
|
445
445
|
|
|
446
446
|
interface DividerDesignEditor {
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
readonly type: 'DIVIDER';
|
|
448
|
+
readonly label: string | Record<string, string>;
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
type DesignEditor =
|
|
@@ -461,6 +461,6 @@ type DesignEditor =
|
|
|
461
461
|
|
|
462
462
|
type InferDesignType<T extends Record<string, DesignEditor>> = {
|
|
463
463
|
readonly [P in keyof T]: MapEditorDesignTypes[T[P]['type']]
|
|
464
|
-
}
|
|
464
|
+
};
|
|
465
465
|
|
|
466
466
|
type SettingsEditor = DesignEditor | ContentEditor;
|