@lightspeed/crane 1.4.2 → 2.0.0
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 +33 -0
- package/UPGRADE.md +19 -0
- package/dist/app.d.mts +1 -1028
- package/dist/app.d.ts +1 -1028
- package/dist/app.mjs +1 -1
- package/dist/cli.mjs +20 -7
- package/package.json +3 -2
- package/template/footers/example-footer/ExampleFooter.vue +1 -1
- package/template/footers/example-footer/client.ts +1 -1
- package/template/footers/example-footer/component/LegalLinks.vue +1 -1
- package/template/footers/example-footer/component/MadeWith.vue +1 -1
- package/template/footers/example-footer/component/ReportAbuse.vue +1 -1
- package/template/footers/example-footer/entity/color.ts +2 -2
- package/template/footers/example-footer/server.ts +1 -1
- package/template/headers/example-header/client.ts +1 -1
- package/template/headers/example-header/component/Account.vue +1 -1
- package/template/headers/example-header/component/Cart.vue +1 -1
- package/template/headers/example-header/component/CategoriesDropdown.vue +1 -1
- package/template/headers/example-header/component/Logo.vue +1 -1
- package/template/headers/example-header/component/NavigationMenu.vue +1 -1
- package/template/headers/example-header/component/SearchForm.vue +1 -1
- package/template/headers/example-header/server.ts +1 -1
- package/template/index.d.ts +1 -1
- package/template/layouts/catalog/example-catalog/Main.vue +1 -1
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/client.ts +1 -1
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/server.ts +1 -1
- package/template/layouts/category/example-category/Main.vue +1 -1
- package/template/layouts/category/example-category/settings/content.ts +1 -1
- package/template/layouts/category/example-category/settings/design.ts +1 -1
- package/template/layouts/product/example-product/Main.vue +1 -1
- package/template/layouts/product/example-product/settings/content.ts +1 -1
- package/template/layouts/product/example-product/settings/design.ts +1 -1
- package/template/package.json +6 -3
- package/template/page-templates/example-template/pages/catalog.ts +1 -1
- package/template/page-templates/example-template/pages/category.ts +1 -1
- package/template/page-templates/example-template/pages/product.ts +1 -1
- package/template/preview/sections/preview.html +1 -1
- package/template/preview/shared/api-routes.ts +347 -39
- package/template/preview/shared/mock.ts +43 -41
- package/template/preview/shared/preview.ts +205 -126
- package/template/preview/shared/utils.ts +208 -62
- package/template/preview/ssr-server.ts +429 -0
- package/template/preview/vite.config.js +64 -65
- package/template/reference/sections/about-us/AboutUs.vue +1 -1
- package/template/reference/sections/about-us/client.ts +1 -1
- package/template/reference/sections/about-us/component/Image.vue +1 -1
- package/template/reference/sections/about-us/component/Stats.vue +2 -2
- package/template/reference/sections/about-us/component/Title.vue +1 -1
- package/template/reference/sections/about-us/server.ts +1 -1
- package/template/reference/sections/about-us/util/visibility-provider.ts +1 -1
- package/template/reference/sections/featured-products/FeaturedProducts.vue +65 -0
- package/template/reference/sections/featured-products/assets/arrow.svg +3 -0
- package/template/reference/sections/featured-products/assets/custom_section_showcase_1_preview.png +0 -0
- package/template/reference/sections/featured-products/client.ts +5 -0
- package/template/reference/sections/featured-products/component/ProductItem.vue +71 -0
- package/template/reference/sections/featured-products/component/Title.vue +31 -0
- package/template/reference/sections/featured-products/entity/color.ts +4 -0
- package/template/reference/sections/featured-products/server.ts +5 -0
- package/template/reference/sections/featured-products/settings/content.ts +14 -0
- package/template/reference/sections/featured-products/settings/design.ts +33 -0
- package/template/reference/sections/featured-products/settings/translations.ts +24 -0
- package/template/reference/sections/featured-products/showcases/1.ts +28 -0
- package/template/reference/sections/featured-products/showcases/translations.ts +16 -0
- package/template/reference/sections/featured-products/type.ts +5 -0
- package/template/reference/sections/intro-slider/IntroSlider.vue +1 -1
- package/template/reference/sections/intro-slider/client.ts +1 -1
- package/template/reference/sections/intro-slider/component/Slider.vue +8 -2
- package/template/reference/sections/intro-slider/component/Title.vue +1 -1
- package/template/reference/sections/intro-slider/entity/color.ts +2 -2
- package/template/reference/sections/intro-slider/server.ts +1 -1
- package/template/reference/sections/tag-lines/TagLines.vue +1 -1
- package/template/reference/sections/tag-lines/client.ts +1 -1
- package/template/reference/sections/tag-lines/component/SectionImage.vue +1 -1
- package/template/reference/sections/tag-lines/component/Title.vue +1 -1
- package/template/reference/sections/tag-lines/composables/highlighted-text-image-list.ts +2 -2
- package/template/reference/sections/tag-lines/server.ts +1 -1
- package/template/reference/sections/trending-categories/TrendingCategories.vue +70 -0
- package/template/reference/sections/trending-categories/assets/arrow.svg +3 -0
- package/template/reference/sections/trending-categories/assets/custom_section_showcase_1_preview.png +0 -0
- package/template/reference/sections/trending-categories/client.ts +5 -0
- package/template/reference/sections/trending-categories/component/CategoryItem.vue +62 -0
- package/template/reference/sections/trending-categories/component/Title.vue +32 -0
- package/template/reference/sections/trending-categories/entity/color.ts +4 -0
- package/template/reference/sections/trending-categories/server.ts +5 -0
- package/template/reference/sections/trending-categories/settings/content.ts +14 -0
- package/template/reference/sections/trending-categories/settings/design.ts +33 -0
- package/template/reference/sections/trending-categories/settings/translations.ts +24 -0
- package/template/reference/sections/trending-categories/showcases/1.ts +36 -0
- package/template/reference/sections/trending-categories/showcases/translations.ts +22 -0
- package/template/reference/sections/trending-categories/type.ts +5 -0
- package/template/reference/shared/components/Button.vue +1 -1
- package/template/reference/templates/reference-template-apparel/pages/catalog.ts +1 -1
- package/template/reference/templates/reference-template-apparel/pages/category.ts +1 -1
- package/template/reference/templates/reference-template-apparel/pages/home.ts +10 -0
- package/template/reference/templates/reference-template-apparel/pages/product.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/catalog.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/category.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/home.ts +10 -0
- package/template/reference/templates/reference-template-bike/pages/product.ts +1 -1
- package/template/sections/example-section/ExampleSection.vue +8 -1
- package/template/sections/example-section/client.ts +1 -1
- package/template/sections/example-section/component/button/Button.vue +1 -1
- package/template/sections/example-section/component/image/Image.vue +1 -1
- package/template/sections/example-section/component/image/ImagesGrid.vue +1 -1
- package/template/sections/example-section/component/selectbox/Selectbox.vue +1 -1
- package/template/sections/example-section/component/title/Title.vue +1 -1
- package/template/sections/example-section/component/toggle/Toggle.vue +1 -1
- package/template/sections/example-section/entity/color.ts +2 -2
- package/template/sections/example-section/server.ts +1 -1
- package/template/sections/example-section/settings/translations.ts +1 -1
- package/template/sections/example-section/showcases/translations.ts +13 -13
- package/template/shared/components/LanguageSelector.vue +1 -1
- package/template/shared/translation.ts +16 -0
- package/template/shared/utils.ts +3 -1
- package/template/tsconfig.json +1 -0
- package/types.d.ts +6 -457
|
@@ -1,28 +1,79 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { getShowcaseState } from './preview';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders a server.js module by directly calling the SSR server.
|
|
8
|
+
* Returns HTML directly from the SSR server.
|
|
9
|
+
*/
|
|
10
|
+
export async function renderServerModule(
|
|
11
|
+
modulePath: string,
|
|
12
|
+
context?: unknown,
|
|
13
|
+
data?: unknown,
|
|
14
|
+
): Promise<string> {
|
|
15
|
+
try {
|
|
16
|
+
// Get SSR server port from environment variable
|
|
17
|
+
const ssrPort = process.env.PREVIEW_SSR_PORT || 3001;
|
|
18
|
+
const ssrUrl = `http://localhost:${ssrPort}/load-server-module`;
|
|
19
|
+
|
|
20
|
+
const response = await fetch(ssrUrl, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'Content-Type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({ modulePath, context, data }),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`Failed to render server module: ${response.statusText}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await response.json() as { html: string };
|
|
31
|
+
return result.html;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Error rendering server module via SSR server:', error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
13
36
|
}
|
|
14
37
|
|
|
15
38
|
/**
|
|
16
|
-
*
|
|
39
|
+
* Adds cache-bust parameter to URL.
|
|
40
|
+
*/
|
|
41
|
+
function addCacheBust(url: string): string {
|
|
42
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
43
|
+
return `${url}${separator}t=${Date.now()}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Loads a module with proper SSR setup for server.js files.
|
|
48
|
+
* For other modules, uses dynamic import with cache-bust.
|
|
49
|
+
* Note: server.js modules are NOT loaded here - they are rendered via renderServerModule
|
|
50
|
+
*/
|
|
51
|
+
export async function loadModule(modulePath: string): Promise<unknown> {
|
|
52
|
+
return import(/* @vite-ignore */ addCacheBust(modulePath));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fetches tiles from the provided URL using bearer token from parameter
|
|
57
|
+
* @param authToken - Bearer token from request header (e.g., "Bearer xxx")
|
|
58
|
+
* @param tilesUrl - The original tiles URL from the Chrome extension
|
|
17
59
|
* @returns The tiles response object or null if failed
|
|
18
60
|
*/
|
|
19
|
-
export async function fetchTiles(): Promise<any> {
|
|
61
|
+
export async function fetchTiles(authToken?: string, tilesUrl?: string): Promise<any> {
|
|
20
62
|
try {
|
|
21
|
-
|
|
63
|
+
if (!tilesUrl) {
|
|
64
|
+
console.error('No tiles URL provided');
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const headers: Record<string, string> = {};
|
|
69
|
+
|
|
70
|
+
if (authToken) {
|
|
71
|
+
headers['Authorization'] = authToken;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const response = await fetch(tilesUrl, {
|
|
22
75
|
method: 'GET',
|
|
23
|
-
headers
|
|
24
|
-
'Authorization': BEARER_TOKEN
|
|
25
|
-
}
|
|
76
|
+
headers,
|
|
26
77
|
});
|
|
27
78
|
|
|
28
79
|
if (!response.ok) {
|
|
@@ -30,9 +81,7 @@ export async function fetchTiles(): Promise<any> {
|
|
|
30
81
|
return null;
|
|
31
82
|
}
|
|
32
83
|
|
|
33
|
-
|
|
34
|
-
return tilesResponse;
|
|
35
|
-
|
|
84
|
+
return await response.json();
|
|
36
85
|
} catch (error) {
|
|
37
86
|
console.error('Error fetching tiles:', error);
|
|
38
87
|
return null;
|
|
@@ -53,7 +102,7 @@ export function updateTilesSection(
|
|
|
53
102
|
sectionName: string,
|
|
54
103
|
showcaseId: string,
|
|
55
104
|
content: any,
|
|
56
|
-
design: any
|
|
105
|
+
design: any,
|
|
57
106
|
): any {
|
|
58
107
|
const targetPattern = `_${sectionName}_${showcaseId}`;
|
|
59
108
|
|
|
@@ -75,64 +124,156 @@ export function updateTilesSection(
|
|
|
75
124
|
}
|
|
76
125
|
|
|
77
126
|
/**
|
|
78
|
-
* Fetches tiles and returns
|
|
127
|
+
* Fetches tiles and returns all IDs of sections matching the pattern
|
|
79
128
|
* @param sectionName - The section name to match (e.g., 'example-section')
|
|
80
129
|
* @param showcaseId - The showcase ID to match
|
|
81
|
-
* @
|
|
130
|
+
* @param authToken - Bearer token from request header (e.g., "Bearer xxx")
|
|
131
|
+
* @param tilesUrl - The original tiles URL from the Chrome extension
|
|
132
|
+
* @returns Array of section IDs or null if not found
|
|
82
133
|
*/
|
|
83
|
-
export async function
|
|
134
|
+
export async function getSectionIds(sectionName: string, showcaseId: string, authToken?: string, tilesUrl?: string): Promise<string[] | null> {
|
|
84
135
|
try {
|
|
85
|
-
const tilesResponse = await fetchTiles();
|
|
136
|
+
const tilesResponse = await fetchTiles(authToken, tilesUrl);
|
|
86
137
|
if (!tilesResponse || !tilesResponse.tiles) {
|
|
87
138
|
return null;
|
|
88
139
|
}
|
|
89
140
|
|
|
90
141
|
const targetPattern = `_${sectionName}_${showcaseId}`;
|
|
91
142
|
|
|
92
|
-
const
|
|
143
|
+
const targetItems = tilesResponse.tiles.filter((item: any) => {
|
|
93
144
|
const itemId = item.sourceId;
|
|
94
145
|
return itemId?.includes(targetPattern);
|
|
95
146
|
});
|
|
96
147
|
|
|
97
|
-
return
|
|
98
|
-
|
|
148
|
+
return targetItems.length > 0 ? targetItems.map((item: any) => item.id) : null;
|
|
99
149
|
} catch (error) {
|
|
100
|
-
console.error('Error getting section
|
|
150
|
+
console.error('Error getting section IDs:', error);
|
|
101
151
|
return null;
|
|
102
152
|
}
|
|
103
153
|
}
|
|
104
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Gets all showcase IDs for a given section
|
|
157
|
+
* @param sectionName - The section name (e.g., 'example-section')
|
|
158
|
+
* @param distPath - The dist path
|
|
159
|
+
* @returns Array of showcase IDs
|
|
160
|
+
*/
|
|
161
|
+
export function getShowcaseIds(sectionName: string, distPath: string): string[] {
|
|
162
|
+
try {
|
|
163
|
+
const showcasesPath = path.join(distPath, 'sections', sectionName, 'js', 'showcases');
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(showcasesPath)) {
|
|
166
|
+
console.error(`Showcases directory not found for section: ${sectionName}`);
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const showcaseIds = fs.readdirSync(showcasesPath)
|
|
171
|
+
.filter((file: string) => file.endsWith('.mjs') && file !== 'translations.mjs')
|
|
172
|
+
.map((file: string) => file.replace('.mjs', ''));
|
|
173
|
+
|
|
174
|
+
return showcaseIds;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`Error getting showcase IDs for section ${sectionName}:`, error);
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
105
180
|
|
|
106
181
|
/**
|
|
107
|
-
*
|
|
108
|
-
* @param sectionName - The section name
|
|
109
|
-
* @param showcaseId - The showcase ID
|
|
182
|
+
* Updates all matching section showcases in custom content
|
|
183
|
+
* @param sectionName - The section name
|
|
184
|
+
* @param showcaseId - The showcase ID
|
|
185
|
+
* @param distPath - The dist path
|
|
186
|
+
* @param customContentSections - The sections array from custom content
|
|
187
|
+
* @param authToken - Bearer token
|
|
188
|
+
* @param tilesUrl - The tiles URL
|
|
189
|
+
* @returns True if at least one section was updated successfully, false otherwise
|
|
190
|
+
*/
|
|
191
|
+
async function updateSectionShowcase(
|
|
192
|
+
sectionName: string,
|
|
193
|
+
showcaseId: string,
|
|
194
|
+
distPath: string,
|
|
195
|
+
customContentSections: Array<Record<string, unknown>>,
|
|
196
|
+
authToken?: string,
|
|
197
|
+
tilesUrl?: string,
|
|
198
|
+
): Promise<boolean> {
|
|
199
|
+
try {
|
|
200
|
+
// Get all section IDs from tiles
|
|
201
|
+
const sectionIds = await getSectionIds(sectionName, showcaseId, authToken, tilesUrl);
|
|
202
|
+
|
|
203
|
+
if (!sectionIds || sectionIds.length === 0) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Process each section ID
|
|
208
|
+
for (const sectionId of sectionIds) {
|
|
209
|
+
// Find the section by ID in custom content
|
|
210
|
+
const targetSection = customContentSections.find((section) => {
|
|
211
|
+
const state = section.state as Record<string, unknown>;
|
|
212
|
+
const data = state.data as Record<string, unknown>;
|
|
213
|
+
return data.tileId === sectionId;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (!targetSection) {
|
|
217
|
+
console.error(`Section not found in custom content with ID: ${sectionId}`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Fetch content, design, and html using getShowcaseState
|
|
222
|
+
const state = targetSection.state as Record<string, unknown>;
|
|
223
|
+
// eslint-disable-next-line no-await-in-loop
|
|
224
|
+
const { content, design, html } = await getShowcaseState(
|
|
225
|
+
sectionName,
|
|
226
|
+
showcaseId,
|
|
227
|
+
distPath,
|
|
228
|
+
state.context as Record<string, unknown>,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// Update content, design, and html
|
|
232
|
+
const data = state.data as Record<string, unknown>;
|
|
233
|
+
data.content = JSON.stringify(content);
|
|
234
|
+
data.design = JSON.stringify(design);
|
|
235
|
+
targetSection.html = html;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return true;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error(`Error updating ${sectionName} showcase ${showcaseId}:`, error);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Fetches custom content and updates all specified sections with their showcases
|
|
247
|
+
* @param sectionNames - Array of section names to update (e.g., ['example-section', 'test-section'])
|
|
110
248
|
* @param distPath - The dist path for getContentAndDesign
|
|
249
|
+
* @param authToken - Bearer token from request header (e.g., "Bearer xxx")
|
|
250
|
+
* @param customContentUrl - The custom content URL
|
|
251
|
+
* @param tilesUrl - The tiles URL to fetch section IDs
|
|
111
252
|
* @returns The updated custom content response or null if failed
|
|
112
253
|
*/
|
|
113
254
|
export async function updateCustomContent(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
255
|
+
sectionNames: string[],
|
|
256
|
+
distPath: string,
|
|
257
|
+
authToken?: string,
|
|
258
|
+
customContentUrl?: string,
|
|
259
|
+
tilesUrl?: string,
|
|
117
260
|
): Promise<any> {
|
|
118
261
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!sectionId) {
|
|
123
|
-
console.error('Section ID not found for:', sectionName, showcaseId);
|
|
262
|
+
if (!customContentUrl || !tilesUrl) {
|
|
263
|
+
console.error('No custom content or tile URL provided');
|
|
124
264
|
return null;
|
|
125
265
|
}
|
|
126
266
|
|
|
127
|
-
// Fetch content and design using getContentAndDesign from preview.ts
|
|
128
|
-
const { content, design } = await getContentAndDesign(sectionName, showcaseId, distPath);
|
|
129
|
-
|
|
130
267
|
// Fetch custom content
|
|
131
|
-
const
|
|
268
|
+
const headers: Record<string, string> = {};
|
|
269
|
+
|
|
270
|
+
if (authToken) {
|
|
271
|
+
headers['Authorization'] = authToken;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const response = await fetch(customContentUrl, {
|
|
132
275
|
method: 'GET',
|
|
133
|
-
headers
|
|
134
|
-
'Authorization': BEARER_TOKEN
|
|
135
|
-
}
|
|
276
|
+
headers,
|
|
136
277
|
});
|
|
137
278
|
|
|
138
279
|
if (!response.ok) {
|
|
@@ -140,29 +281,34 @@ export async function updateCustomContent(
|
|
|
140
281
|
return null;
|
|
141
282
|
}
|
|
142
283
|
|
|
143
|
-
const customContentResponse = await response.json()
|
|
284
|
+
const customContentResponse = await response.json() as Record<string, unknown>;
|
|
144
285
|
|
|
145
|
-
|
|
286
|
+
const customContent = customContentResponse.customContent as Record<string, unknown>;
|
|
287
|
+
if (!customContent || !customContent.sections) {
|
|
146
288
|
console.error('Invalid custom content response structure');
|
|
147
289
|
return null;
|
|
148
290
|
}
|
|
149
291
|
|
|
150
|
-
|
|
151
|
-
const targetSection = customContentResponse.customContent.sections.find((section: any) => {
|
|
152
|
-
return section.state.data.tileId === sectionId;
|
|
153
|
-
});
|
|
292
|
+
const customContentSections = customContent.sections as Array<Record<string, unknown>>;
|
|
154
293
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
294
|
+
// Process all sections and showcases in parallel
|
|
295
|
+
const updatePromises = sectionNames.flatMap((sectionName) => {
|
|
296
|
+
const showcaseIds = getShowcaseIds(sectionName, distPath);
|
|
159
297
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
298
|
+
return showcaseIds.map(showcaseId =>
|
|
299
|
+
updateSectionShowcase(
|
|
300
|
+
sectionName,
|
|
301
|
+
showcaseId,
|
|
302
|
+
distPath,
|
|
303
|
+
customContentSections,
|
|
304
|
+
authToken,
|
|
305
|
+
tilesUrl,
|
|
306
|
+
),
|
|
307
|
+
);
|
|
308
|
+
});
|
|
163
309
|
|
|
310
|
+
await Promise.all(updatePromises);
|
|
164
311
|
return customContentResponse;
|
|
165
|
-
|
|
166
312
|
} catch (error) {
|
|
167
313
|
console.error('Error updating custom content:', error);
|
|
168
314
|
return null;
|