@zohofinance/commerce 2.0.0 → 2.1.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/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-theme.d.ts","sourceRoot":"","sources":["../../src/commands/serve-theme.ts"],"names":[],"mappings":"AA4DA,iBAAe,UAAU,CACvB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,OAAO,EACnB,aAAa,CAAC,EAAE,OAAO,EACvB,OAAO,UAAO,EACd,QAAQ,CAAC,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"serve-theme.d.ts","sourceRoot":"","sources":["../../src/commands/serve-theme.ts"],"names":[],"mappings":"AA4DA,iBAAe,UAAU,CACvB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,OAAO,EACnB,aAAa,CAAC,EAAE,OAAO,EACvB,OAAO,UAAO,EACd,QAAQ,CAAC,EAAE,MAAM,iBA2oDlB;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -749,7 +749,7 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
749
749
|
// Runtime fields added by the CLI on section/block root objects — must NOT be written to disk.
|
|
750
750
|
// NOTE: display_name inside settings values (e.g. {display_name:"Full Width",value:"full"}) is
|
|
751
751
|
// legitimate theme data — we only strip it at the section/block root level, not inside settings.
|
|
752
|
-
const COMPONENT_RUNTIME_FIELDS = new Set(['id', 'display_name', 'settingid', 'zcommerce_attributes', 'block_key']);
|
|
752
|
+
const COMPONENT_RUNTIME_FIELDS = new Set(['id', 'display_name', 'settingid', 'zcommerce_attributes', 'block_key', 'available_sections', 'available_blocks']);
|
|
753
753
|
// Clean a single section or block object: strip runtime fields from root, recurse into blocks,
|
|
754
754
|
// but leave settings values entirely untouched (they may contain legitimate display_name/handle).
|
|
755
755
|
function cleanComponentForDisk(obj) {
|
|
@@ -774,11 +774,11 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
774
774
|
}
|
|
775
775
|
return out;
|
|
776
776
|
}
|
|
777
|
-
// Clean the full page/group JSON: only touches the sections map, leaves order/name/type intact.
|
|
778
777
|
function cleanFullJsonForDisk(json) {
|
|
779
778
|
const sections = json.sections;
|
|
780
|
-
if (!sections || typeof sections !== 'object' || Array.isArray(sections))
|
|
779
|
+
if (!sections || typeof sections !== 'object' || Array.isArray(sections)) {
|
|
781
780
|
return json;
|
|
781
|
+
}
|
|
782
782
|
const cleanedSections = {};
|
|
783
783
|
for (const [sk, sv] of Object.entries(sections)) {
|
|
784
784
|
cleanedSections[sk] = sv && typeof sv === 'object' && !Array.isArray(sv)
|
|
@@ -991,6 +991,48 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
991
991
|
next(error);
|
|
992
992
|
}
|
|
993
993
|
});
|
|
994
|
+
// Re-renders the full page with in-memory settings override (no disk write).
|
|
995
|
+
// Used by the editor when theme settings change so the preview updates immediately.
|
|
996
|
+
app.post('/_editor/renderpage', async (req, res, next) => {
|
|
997
|
+
const engineInternal = engine.engine;
|
|
998
|
+
try {
|
|
999
|
+
const body = req.body;
|
|
1000
|
+
const settingsData = body.settingsData;
|
|
1001
|
+
const pageName = body.pageName || 'index';
|
|
1002
|
+
const route = STOREFRONT_ROUTES.find((r) => r.pageTemplate === pageName)
|
|
1003
|
+
|| STOREFRONT_ROUTES.find((r) => r.expressPath === `/${pageName}`)
|
|
1004
|
+
|| STOREFRONT_ROUTES.find((r) => r.expressPath.split('/')[1]?.startsWith(pageName))
|
|
1005
|
+
|| STOREFRONT_ROUTES[0];
|
|
1006
|
+
const apiPath = resolveApiPath(route, {});
|
|
1007
|
+
const data = await fetchPageData(apiPath);
|
|
1008
|
+
// Temporarily override runtimeGlobals.settings so the engine renders with
|
|
1009
|
+
// the new values. Restored in the finally block regardless of outcome.
|
|
1010
|
+
const savedSettings = runtimeGlobals.settings;
|
|
1011
|
+
if (settingsData)
|
|
1012
|
+
runtimeGlobals.settings = settingsData;
|
|
1013
|
+
engineInternal.__editorMode = true;
|
|
1014
|
+
engineInternal.__editorJsonType = 'template';
|
|
1015
|
+
try {
|
|
1016
|
+
const { html, error } = await engine.renderJSONTemplates({
|
|
1017
|
+
pathName: resolvePageTemplate(route.pageTemplate),
|
|
1018
|
+
data,
|
|
1019
|
+
});
|
|
1020
|
+
if (error) {
|
|
1021
|
+
res.status(500).json({ error: String(error) });
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
res.json({ html });
|
|
1025
|
+
}
|
|
1026
|
+
finally {
|
|
1027
|
+
runtimeGlobals.settings = savedSettings;
|
|
1028
|
+
engineInternal.__editorMode = false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
catch (error) {
|
|
1032
|
+
engineInternal.__editorMode = false;
|
|
1033
|
+
next(error);
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
994
1036
|
app.post('/_editor/rendersection', async (req, res, next) => {
|
|
995
1037
|
try {
|
|
996
1038
|
const body = req.body;
|
|
@@ -1127,39 +1169,106 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
1127
1169
|
app.get('/_editor/availablesections', async (req, res, next) => {
|
|
1128
1170
|
try {
|
|
1129
1171
|
const needHtml = req.query.needHtml === 'true';
|
|
1172
|
+
const jsonType = req.query.jsonType || 'template';
|
|
1173
|
+
const pageName = req.query.pageName || 'index';
|
|
1130
1174
|
const sectionsDir = path.join(cwd, 'sections');
|
|
1131
1175
|
if (!fs.existsSync(sectionsDir)) {
|
|
1132
1176
|
res.json([]);
|
|
1133
1177
|
return;
|
|
1134
1178
|
}
|
|
1179
|
+
let templateName;
|
|
1180
|
+
if (jsonType === 'header') {
|
|
1181
|
+
templateName = 'header-group';
|
|
1182
|
+
}
|
|
1183
|
+
else if (jsonType === 'footer') {
|
|
1184
|
+
templateName = 'footer-group';
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
templateName = pageName === 'home' ? 'index' : pageName;
|
|
1188
|
+
}
|
|
1189
|
+
let currentTemplateJson = { sections: {}, order: [] };
|
|
1190
|
+
try {
|
|
1191
|
+
let jsonPath;
|
|
1192
|
+
if (jsonType === 'header') {
|
|
1193
|
+
jsonPath = path.join(cwd, 'sections', 'header-group.json');
|
|
1194
|
+
}
|
|
1195
|
+
else if (jsonType === 'footer') {
|
|
1196
|
+
jsonPath = path.join(cwd, 'sections', 'footer-group.json');
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
jsonPath = path.join(cwd, 'pages', `${templateName}.json`);
|
|
1200
|
+
}
|
|
1201
|
+
if (fs.existsSync(jsonPath)) {
|
|
1202
|
+
currentTemplateJson = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
catch { /* use defaults */ }
|
|
1206
|
+
const currentSections = (currentTemplateJson.sections || {});
|
|
1207
|
+
const currentOrder = (currentTemplateJson.order || []);
|
|
1208
|
+
const availableSections = [];
|
|
1135
1209
|
const files = fs.readdirSync(sectionsDir).filter((f) => f.endsWith('.liquid'));
|
|
1136
|
-
const
|
|
1210
|
+
for (const file of files) {
|
|
1137
1211
|
const sectionType = file.replace('.liquid', '');
|
|
1138
1212
|
const content = fs.readFileSync(path.join(sectionsDir, file), 'utf-8');
|
|
1139
1213
|
const schemaMatch = content.match(/{%\s*schema\s*%}([\s\S]*?){%\s*endschema\s*%}/);
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1214
|
+
if (!schemaMatch)
|
|
1215
|
+
continue;
|
|
1216
|
+
let schema;
|
|
1217
|
+
try {
|
|
1218
|
+
schema = JSON.parse(schemaMatch[1].trim());
|
|
1219
|
+
}
|
|
1220
|
+
catch {
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const templates = schema?.available_in?.templates;
|
|
1224
|
+
if (!Array.isArray(templates) || !templates.includes(templateName)) {
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
const presets = schema.presets;
|
|
1228
|
+
if (!Array.isArray(presets) || presets.length === 0) {
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (typeof schema.limit === 'number') {
|
|
1232
|
+
let count = 0;
|
|
1233
|
+
for (const key of currentOrder) {
|
|
1234
|
+
if (currentSections[key]?.type === sectionType)
|
|
1235
|
+
count++;
|
|
1236
|
+
}
|
|
1237
|
+
if (count >= schema.limit) {
|
|
1238
|
+
continue;
|
|
1148
1239
|
}
|
|
1149
|
-
catch { /* skip */ }
|
|
1150
1240
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1241
|
+
for (let p = 0; p < presets.length; p++) {
|
|
1242
|
+
const preset = presets[p];
|
|
1243
|
+
if (!preset)
|
|
1244
|
+
continue;
|
|
1245
|
+
const uuid = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
1246
|
+
const sectionName = `${sectionType}-additional_section-${uuid}`;
|
|
1247
|
+
const resolved = resolveSchemaT({ name: preset.name || sectionType, settings: [], blocks: [] }, cwd);
|
|
1248
|
+
const displayName = resolved.name || preset.name || sectionType;
|
|
1249
|
+
availableSections.push({
|
|
1250
|
+
section_name: sectionName,
|
|
1251
|
+
display_name: displayName,
|
|
1252
|
+
name: displayName,
|
|
1253
|
+
preset_index: p,
|
|
1254
|
+
category: schema.category,
|
|
1255
|
+
_sectionType: sectionType,
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1153
1259
|
if (!needHtml) {
|
|
1154
|
-
res.json(
|
|
1260
|
+
res.json(availableSections.map(({ _sectionType: _, ...s }) => s));
|
|
1155
1261
|
return;
|
|
1156
1262
|
}
|
|
1157
|
-
|
|
1158
|
-
const sectionNames = req.query.sectionNames
|
|
1263
|
+
const requestedNames = req.query.sectionNames
|
|
1159
1264
|
? JSON.parse(req.query.sectionNames)
|
|
1160
|
-
:
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1265
|
+
: availableSections.map((s) => s.section_name);
|
|
1266
|
+
const baseTypeFor = (name) => {
|
|
1267
|
+
const idx = name.indexOf('-additional_section-');
|
|
1268
|
+
return idx >= 0 ? name.substring(0, idx) : name;
|
|
1269
|
+
};
|
|
1270
|
+
const typesToRender = [...new Set(requestedNames.map(baseTypeFor))];
|
|
1271
|
+
const renderedMap = await renderSectionsForResponse(typesToRender, {});
|
|
1163
1272
|
const assetsDir = path.join(cwd, 'assets');
|
|
1164
1273
|
const cssLinks = fs.existsSync(assetsDir)
|
|
1165
1274
|
? fs.readdirSync(assetsDir)
|
|
@@ -1168,13 +1277,45 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
1168
1277
|
.join('')
|
|
1169
1278
|
: '';
|
|
1170
1279
|
const cliOrigin = `http://localhost:${port}`;
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1280
|
+
const displayNameForType = new Map();
|
|
1281
|
+
for (const entry of availableSections) {
|
|
1282
|
+
if (!displayNameForType.has(entry._sectionType)) {
|
|
1283
|
+
displayNameForType.set(entry._sectionType, entry.display_name);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const presetSettingsForType = new Map();
|
|
1287
|
+
for (const entry of availableSections) {
|
|
1288
|
+
if (!presetSettingsForType.has(entry._sectionType)) {
|
|
1289
|
+
presetSettingsForType.set(entry._sectionType, {});
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
for (const [sType] of presetSettingsForType) {
|
|
1293
|
+
try {
|
|
1294
|
+
const content = fs.readFileSync(path.join(sectionsDir, `${sType}.liquid`), 'utf-8');
|
|
1295
|
+
const sm = content.match(/{%\s*schema\s*%}([\s\S]*?){%\s*endschema\s*%}/);
|
|
1296
|
+
if (sm) {
|
|
1297
|
+
const sc = JSON.parse(sm[1].trim());
|
|
1298
|
+
if (Array.isArray(sc.presets) && sc.presets[0]?.settings) {
|
|
1299
|
+
presetSettingsForType.set(sType, sc.presets[0].settings);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
catch { /* skip */ }
|
|
1304
|
+
}
|
|
1305
|
+
const results = requestedNames.map((reqName) => {
|
|
1306
|
+
const baseType = baseTypeFor(reqName);
|
|
1307
|
+
const sectionHtml = (renderedMap[baseType] || '')
|
|
1174
1308
|
.replace(/(['"(])\/assets\//g, `$1${cliOrigin}/assets/`)
|
|
1175
1309
|
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
1176
1310
|
const html = `<!DOCTYPE html><html><head><meta charset="utf-8">${cssLinks}</head><body>${sectionHtml}</body></html>`;
|
|
1177
|
-
|
|
1311
|
+
const displayName = displayNameForType.get(baseType) || baseType;
|
|
1312
|
+
const sectionJson = {
|
|
1313
|
+
type: baseType,
|
|
1314
|
+
settings: presetSettingsForType.get(baseType) || {},
|
|
1315
|
+
blocks: {},
|
|
1316
|
+
block_order: [],
|
|
1317
|
+
};
|
|
1318
|
+
return { section_name: reqName, display_name: displayName, name: displayName, sectionJson, html };
|
|
1178
1319
|
});
|
|
1179
1320
|
res.json(results);
|
|
1180
1321
|
}
|
|
@@ -1182,6 +1323,44 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
1182
1323
|
next(error);
|
|
1183
1324
|
}
|
|
1184
1325
|
});
|
|
1326
|
+
app.get('/_editor/themesettings', (req, res, next) => {
|
|
1327
|
+
try {
|
|
1328
|
+
const settingsDir = path.join(cwd, 'settings');
|
|
1329
|
+
let settings_schema = [];
|
|
1330
|
+
const schemaPath = path.join(settingsDir, 'schema.json');
|
|
1331
|
+
if (fs.existsSync(schemaPath)) {
|
|
1332
|
+
try {
|
|
1333
|
+
const raw = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
1334
|
+
settings_schema = resolveSchemaT(raw, cwd);
|
|
1335
|
+
}
|
|
1336
|
+
catch { /* use empty */ }
|
|
1337
|
+
}
|
|
1338
|
+
let settings_data = { selected: {}, presets: {} };
|
|
1339
|
+
const themePath = path.join(settingsDir, 'theme.json');
|
|
1340
|
+
if (fs.existsSync(themePath)) {
|
|
1341
|
+
try {
|
|
1342
|
+
settings_data = JSON.parse(fs.readFileSync(themePath, 'utf-8'));
|
|
1343
|
+
if (!settings_data.selected)
|
|
1344
|
+
settings_data.selected = {};
|
|
1345
|
+
const { selected, presets } = settings_data;
|
|
1346
|
+
if (selected && typeof selected.preset_name === 'string' && presets) {
|
|
1347
|
+
const preset = presets[selected.preset_name];
|
|
1348
|
+
if (preset && typeof preset === 'object') {
|
|
1349
|
+
settings_data = {
|
|
1350
|
+
...settings_data,
|
|
1351
|
+
selected: { ...preset, preset_name: selected.preset_name },
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
catch { /* use defaults */ }
|
|
1357
|
+
}
|
|
1358
|
+
res.json({ settings_schema, settings_data });
|
|
1359
|
+
}
|
|
1360
|
+
catch (error) {
|
|
1361
|
+
next(error);
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1185
1364
|
// Persists the full editor JSON to disk (for structural changes: add/delete/reorder sections).
|
|
1186
1365
|
app.post('/_editor/savejson', (req, res, next) => {
|
|
1187
1366
|
try {
|
|
@@ -1238,7 +1417,8 @@ async function serveTheme(portNumber, openInWeb, livereloadOff, useMock = true,
|
|
|
1238
1417
|
}
|
|
1239
1418
|
res.set('Cache-Control', 'no-store').send('OK');
|
|
1240
1419
|
});
|
|
1241
|
-
|
|
1420
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1421
|
+
app.use((err, _req, res, _next) => {
|
|
1242
1422
|
if (res.headersSent) {
|
|
1243
1423
|
return;
|
|
1244
1424
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor-attributes.d.ts","sourceRoot":"","sources":["../../src/utils/editor-attributes.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"editor-attributes.d.ts","sourceRoot":"","sources":["../../src/utils/editor-attributes.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CASR;AAED,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAWR"}
|
|
@@ -2,10 +2,9 @@ export function buildSectionComponentAttr(sectionType, sectionKey, jsonType, dis
|
|
|
2
2
|
return JSON.stringify({
|
|
3
3
|
id: `section_${sectionKey}`,
|
|
4
4
|
'data-zcommerce-section-id': '',
|
|
5
|
-
'data-zcommerce-component-name': sectionType,
|
|
5
|
+
'data-zcommerce-component-name': displayName || sectionType,
|
|
6
6
|
'data-zcommerce-component-key': `section_${sectionKey}`,
|
|
7
7
|
'data-zcommerce-json-type': jsonType,
|
|
8
|
-
'data-zcommerce-display-name': displayName || sectionType,
|
|
9
8
|
settingid: `setting_section_${sectionType}`
|
|
10
9
|
});
|
|
11
10
|
}
|
|
@@ -14,10 +13,9 @@ export function buildBlockZcommerceAttributes(blockType, blockKey, sectionKey, j
|
|
|
14
13
|
const attrJson = JSON.stringify({
|
|
15
14
|
id,
|
|
16
15
|
'data-zcommerce-section-id': `section_${sectionKey}`,
|
|
17
|
-
'data-zcommerce-component-name': blockType,
|
|
16
|
+
'data-zcommerce-component-name': displayName || blockType,
|
|
18
17
|
'data-zcommerce-component-key': id,
|
|
19
18
|
'data-zcommerce-json-type': jsonType,
|
|
20
|
-
'data-zcommerce-display-name': displayName || blockType,
|
|
21
19
|
settingid: `setting_block_${blockType}`
|
|
22
20
|
}).replace(/'/g, ''');
|
|
23
21
|
return `data-zcommerce-component='${attrJson}'`;
|