iwgt 2.4.11 → 2.4.13
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/dist/data/references/commonjs-complete-guide.json +1082 -0
- package/dist/data/references/commonjs-reference.json +1 -1
- package/dist/data/references/javascript-guide.json +0 -13
- package/dist/data/references/liquid-filters.json +1 -1
- package/dist/data/references/liquid-variables.json +1 -1
- package/dist/data/references/settings-types.json +3717 -0
- package/dist/generators/collections-menu.d.ts +13 -0
- package/dist/generators/collections-menu.js +268 -0
- package/dist/generators/index.d.ts +15 -104
- package/dist/generators/index.js +22 -1093
- package/dist/generators/info-generator.d.ts +21 -0
- package/dist/generators/info-generator.js +70 -0
- package/dist/generators/settings-generator.d.ts +22 -0
- package/dist/generators/settings-generator.js +455 -0
- package/dist/generators/snippet-generator.d.ts +31 -0
- package/dist/generators/snippet-generator.js +484 -0
- package/dist/generators/types.d.ts +43 -0
- package/dist/generators/types.js +5 -0
- package/dist/generators/validation.d.ts +13 -0
- package/dist/generators/validation.js +71 -0
- package/dist/server-http.js +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.js +99 -4
- package/dist/tools/index.js +14 -5
- package/dist/types/index.d.ts +3 -1
- package/dist/types/settings.d.ts +123 -0
- package/dist/types/settings.js +71 -0
- package/dist/utils/settings.d.ts +258 -0
- package/dist/utils/settings.js +412 -0
- package/package.json +1 -1
package/dist/generators/index.js
CHANGED
|
@@ -1,1031 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Генераторы файлов виджетов
|
|
3
|
-
*
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'products-cards': ['product'],
|
|
22
|
-
// Все остальные категории
|
|
23
|
-
};
|
|
24
|
-
return categoryToListKinds[category] || ['content', 'footer'];
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Генерирует info.json
|
|
28
|
-
*/
|
|
29
|
-
export function generateInfoJson(config) {
|
|
30
|
-
const infoJson = {
|
|
31
|
-
type: config.type === 'simple_widget_type' ? 'SimpleWidgetType' : 'BlockListWidgetType',
|
|
32
|
-
handle: config.handle,
|
|
33
|
-
page_kinds: ["all"],
|
|
34
|
-
widget_list_kinds: getWidgetListKinds(config.category),
|
|
35
|
-
generation: 4,
|
|
36
|
-
name: generateWidgetName(config.description),
|
|
37
|
-
widget_category_handle: config.category,
|
|
38
|
-
libraries: config.libraries.includes('my-layout') ? config.libraries : [...config.libraries, 'my-layout'],
|
|
39
|
-
};
|
|
40
|
-
// block_template_handle обязателен только для BlockListWidgetType
|
|
41
|
-
if (config.type === 'block_list_widget_type' && config.blockTemplate) {
|
|
42
|
-
infoJson.block_template_handle = config.blockTemplate.handle;
|
|
43
|
-
}
|
|
44
|
-
return infoJson;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Генерирует messages.json
|
|
48
|
-
* Использует только те ключи, которых нет в глобальных messages
|
|
49
|
-
* Глобальные ключи: background, indentation, content, adaptive, widget_background_color,
|
|
50
|
-
* wide_background, wide_content, padding_top, padding_bottom, content_max_width,
|
|
51
|
-
* remove_padding_at_edges, hide_desktop, hide_mobile и другие
|
|
52
|
-
*/
|
|
53
|
-
export function generateMessages(description) {
|
|
54
|
-
const name = generateWidgetName(description);
|
|
55
|
-
return {
|
|
56
|
-
ru: {
|
|
57
|
-
widget_name: name,
|
|
58
|
-
// Только специфичные для виджета переводы
|
|
59
|
-
// Все стандартные (background, indentation, content, adaptive и т.д.) уже есть в глобальных
|
|
60
|
-
},
|
|
61
|
-
en: {
|
|
62
|
-
widget_name: name,
|
|
63
|
-
// Только специфичные для виджета переводы
|
|
64
|
-
},
|
|
65
|
-
ua: {
|
|
66
|
-
widget_name: name,
|
|
67
|
-
// Только специфичные для виджета переводы
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Генерирует settings_data.json
|
|
73
|
-
*/
|
|
74
|
-
export function generateSettingsData(commonSettings) {
|
|
75
|
-
const data = {
|
|
76
|
-
// Стандартные настройки design
|
|
77
|
-
bg: "",
|
|
78
|
-
"layout-wide-bg": false,
|
|
79
|
-
"layout-pt": 2,
|
|
80
|
-
"layout-pb": 2,
|
|
81
|
-
"layout-content-max-width": 1200,
|
|
82
|
-
"layout-wide-content": false,
|
|
83
|
-
"layout-edge": false,
|
|
84
|
-
"hide-desktop": false,
|
|
85
|
-
"hide-mobile": false,
|
|
86
|
-
};
|
|
87
|
-
// Добавляем дефолтное значение для заголовка
|
|
88
|
-
data.title = "";
|
|
89
|
-
// Добавляем настройки из категории
|
|
90
|
-
commonSettings.forEach(setting => {
|
|
91
|
-
if (setting.type === 'checkbox') {
|
|
92
|
-
// Всегда используем false для булевых, null превращается в true на сервере
|
|
93
|
-
const value = setting.value;
|
|
94
|
-
data[setting.name] = value === true ? true : false;
|
|
95
|
-
}
|
|
96
|
-
else if (setting.type === 'text' || setting.type === 'rich-text') {
|
|
97
|
-
data[setting.name] = setting.value || '';
|
|
98
|
-
}
|
|
99
|
-
else if (setting.type === 'number' || setting.type === 'range') {
|
|
100
|
-
data[setting.name] = setting.value !== undefined ? setting.value : (setting.min || 0);
|
|
101
|
-
}
|
|
102
|
-
else if (setting.type === 'color') {
|
|
103
|
-
data[setting.name] = setting.value || '';
|
|
104
|
-
}
|
|
105
|
-
else if (setting.type === 'select') {
|
|
106
|
-
data[setting.name] = setting.value || (setting.options?.[0]?.value || 'default');
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
data[setting.name] = setting.value || '';
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
return data;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Генерирует settings_form.json
|
|
116
|
-
* Структура согласно документации:
|
|
117
|
-
* - content: специфичные настройки виджета
|
|
118
|
-
* - design: типовые настройки (фон, отступы, адаптивность)
|
|
119
|
-
*/
|
|
120
|
-
export function generateSettingsForm(commonSettings) {
|
|
121
|
-
const form = {
|
|
122
|
-
content: [],
|
|
123
|
-
design: [
|
|
124
|
-
{
|
|
125
|
-
type: "group",
|
|
126
|
-
name: "{{ messages.background }}",
|
|
127
|
-
items: [
|
|
128
|
-
{
|
|
129
|
-
name: "bg",
|
|
130
|
-
label: "{{ messages.widget_background_color }}",
|
|
131
|
-
type: "color",
|
|
132
|
-
clearable: true
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
class: "checkbox",
|
|
136
|
-
name: "layout-wide-bg",
|
|
137
|
-
label: "{{ messages.wide_background }}",
|
|
138
|
-
type: "checkbox"
|
|
139
|
-
}
|
|
140
|
-
]
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
type: "group",
|
|
144
|
-
name: "{{ messages.indentation }}",
|
|
145
|
-
items: [
|
|
146
|
-
{
|
|
147
|
-
class: "range",
|
|
148
|
-
name: "layout-pt",
|
|
149
|
-
min: 0,
|
|
150
|
-
max: 10,
|
|
151
|
-
step: 0.5,
|
|
152
|
-
label: "{{ messages.padding_top }} (vw)",
|
|
153
|
-
type: "number",
|
|
154
|
-
with_btns: true,
|
|
155
|
-
unit: "vw"
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
class: "range",
|
|
159
|
-
name: "layout-pb",
|
|
160
|
-
min: 0,
|
|
161
|
-
max: 10,
|
|
162
|
-
step: 0.5,
|
|
163
|
-
label: "{{ messages.padding_bottom }} (vw)",
|
|
164
|
-
type: "number",
|
|
165
|
-
with_btns: true,
|
|
166
|
-
unit: "vw"
|
|
167
|
-
}
|
|
168
|
-
]
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
type: "group",
|
|
172
|
-
name: "{{ messages.content }}",
|
|
173
|
-
items: [
|
|
174
|
-
{
|
|
175
|
-
class: "number",
|
|
176
|
-
name: "layout-content-max-width",
|
|
177
|
-
min: 400,
|
|
178
|
-
max: 2000,
|
|
179
|
-
label: "{{ messages.content_max_width }}",
|
|
180
|
-
type: "number",
|
|
181
|
-
unit: "px",
|
|
182
|
-
hide_mobile: true
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
class: "checkbox",
|
|
186
|
-
name: "layout-wide-content",
|
|
187
|
-
label: "{{ messages.wide_content }}",
|
|
188
|
-
type: "checkbox",
|
|
189
|
-
hide_mobile: true
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
class: "checkbox",
|
|
193
|
-
name: "layout-edge",
|
|
194
|
-
label: "{{ messages.remove_padding_at_edges }}",
|
|
195
|
-
type: "checkbox"
|
|
196
|
-
}
|
|
197
|
-
]
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
type: "group",
|
|
201
|
-
name: "{{ messages.adaptive }}",
|
|
202
|
-
items: [
|
|
203
|
-
{
|
|
204
|
-
class: "checkbox",
|
|
205
|
-
name: "hide-desktop",
|
|
206
|
-
label: "{{ messages.hide_desktop }}",
|
|
207
|
-
type: "checkbox"
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
class: "checkbox",
|
|
211
|
-
name: "hide-mobile",
|
|
212
|
-
label: "{{ messages.hide_mobile }}",
|
|
213
|
-
type: "checkbox"
|
|
214
|
-
}
|
|
215
|
-
]
|
|
216
|
-
}
|
|
217
|
-
]
|
|
218
|
-
};
|
|
219
|
-
let count = 11; // базовые настройки в design (bg, layout-wide-bg, layout-pt, layout-pb, layout-content-max-width, layout-wide-content, layout-edge, hide-desktop, hide-mobile)
|
|
220
|
-
// Добавляем специфичные для категории настройки в content
|
|
221
|
-
if (commonSettings.length > 0) {
|
|
222
|
-
const categoryGroup = {
|
|
223
|
-
type: "group",
|
|
224
|
-
name: "{{ messages.settings }}",
|
|
225
|
-
items: [],
|
|
226
|
-
};
|
|
227
|
-
commonSettings.forEach(setting => {
|
|
228
|
-
const item = {
|
|
229
|
-
name: setting.name,
|
|
230
|
-
label: setting.label || setting.name,
|
|
231
|
-
type: setting.type,
|
|
232
|
-
};
|
|
233
|
-
// Добавляем class если указан
|
|
234
|
-
if (setting.class) {
|
|
235
|
-
item.class = setting.class;
|
|
236
|
-
}
|
|
237
|
-
// Специфичные свойства для разных типов
|
|
238
|
-
if (setting.type === 'checkbox') {
|
|
239
|
-
item.value = setting.value !== undefined ? setting.value : false;
|
|
240
|
-
}
|
|
241
|
-
else if (setting.type === 'text' || setting.type === 'rich-text') {
|
|
242
|
-
item.value = setting.value || "";
|
|
243
|
-
}
|
|
244
|
-
else if (setting.type === 'select') {
|
|
245
|
-
item.options = setting.options || [{ text: "{{ messages.default }}", value: "default" }];
|
|
246
|
-
item.value = setting.value || null;
|
|
247
|
-
}
|
|
248
|
-
else if (setting.type === 'number' || setting.type === 'range') {
|
|
249
|
-
item.min = setting.min !== undefined ? setting.min : 0;
|
|
250
|
-
item.max = setting.max !== undefined ? setting.max : 100;
|
|
251
|
-
item.step = setting.step !== undefined ? setting.step : 1;
|
|
252
|
-
if (setting.unit)
|
|
253
|
-
item.unit = setting.unit;
|
|
254
|
-
if (setting.with_btns)
|
|
255
|
-
item.with_btns = setting.with_btns;
|
|
256
|
-
}
|
|
257
|
-
else if (setting.type === 'color') {
|
|
258
|
-
item.value = setting.value || "#ffffff";
|
|
259
|
-
if (setting.clearable)
|
|
260
|
-
item.clearable = setting.clearable;
|
|
261
|
-
}
|
|
262
|
-
// Общие свойства
|
|
263
|
-
if (setting.help)
|
|
264
|
-
item.help = setting.help;
|
|
265
|
-
if (setting.general !== undefined)
|
|
266
|
-
item.general = setting.general;
|
|
267
|
-
if (setting.general_position)
|
|
268
|
-
item.general_position = setting.general_position;
|
|
269
|
-
if (setting.general_label)
|
|
270
|
-
item.general_label = setting.general_label;
|
|
271
|
-
if (setting.enable_server_reload)
|
|
272
|
-
item.enable_server_reload = setting.enable_server_reload;
|
|
273
|
-
if (setting.hide_mobile)
|
|
274
|
-
item.hide_mobile = setting.hide_mobile;
|
|
275
|
-
categoryGroup.items.push(item);
|
|
276
|
-
count++;
|
|
277
|
-
});
|
|
278
|
-
if (categoryGroup.items.length > 0) {
|
|
279
|
-
form.content.push(categoryGroup);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
// Если нет специфичных настроек, добавляем минимальную группу с заголовком
|
|
284
|
-
form.content.push({
|
|
285
|
-
type: "group",
|
|
286
|
-
name: "{{ messages.content_settings }}",
|
|
287
|
-
items: [
|
|
288
|
-
{
|
|
289
|
-
class: "text",
|
|
290
|
-
name: "title",
|
|
291
|
-
label: "{{ messages.title }}",
|
|
292
|
-
type: "text",
|
|
293
|
-
general: true
|
|
294
|
-
}
|
|
295
|
-
]
|
|
296
|
-
});
|
|
297
|
-
count++;
|
|
298
|
-
}
|
|
299
|
-
return { form, count };
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Генерирует snippet.liquid
|
|
303
|
-
* ВАЖНО: Код автоматически оборачивается системой в div.layout с CSS-переменными
|
|
304
|
-
* НЕ добавляйте обёртку layout вручную!
|
|
305
|
-
*/
|
|
306
|
-
export function generateSnippetLiquid(config) {
|
|
307
|
-
const useBlocks = config.type === 'block_list_widget_type';
|
|
308
|
-
let liquid = `{% comment %}
|
|
309
|
-
Виджет: ${generateWidgetName(config.description)}
|
|
310
|
-
Категория: ${config.category}
|
|
311
|
-
Тип: ${config.type}
|
|
312
|
-
|
|
313
|
-
ВАЖНО:
|
|
314
|
-
- Этот код будет автоматически обёрнут в div.layout с классом widget-type_${config.handle}
|
|
315
|
-
- CSS-переменные из настроек будут доступны через var(--handle-настройки)
|
|
316
|
-
- Используйте widget_settings для доступа к настройкам
|
|
317
|
-
${useBlocks ? '- Блоки доступны через data.blocks (не widget.blocks!)' : ''}
|
|
318
|
-
{% endcomment %}
|
|
319
|
-
|
|
320
|
-
<div class="widget-container">
|
|
321
|
-
`;
|
|
322
|
-
// Заголовок виджета (если есть в настройках)
|
|
323
|
-
liquid += ` {% if widget_settings.show-heading %}
|
|
324
|
-
<h2 class="widget-heading">{{ widget_settings.heading-text }}</h2>
|
|
325
|
-
{% endif %}
|
|
326
|
-
|
|
327
|
-
`;
|
|
328
|
-
if (useBlocks) {
|
|
329
|
-
const blockTemplate = config.blockTemplate;
|
|
330
|
-
const exampleFields = blockTemplate ? Object.keys(blockTemplate.fields).slice(0, 3) : ['name', 'image', 'link'];
|
|
331
|
-
liquid += ` {% comment %} Блоки используют шаблон: ${blockTemplate?.handle || 'не указан'} {% endcomment %}
|
|
332
|
-
{% if data.blocks and data.blocks.size > 0 %}
|
|
333
|
-
<div class="widget-blocks">
|
|
334
|
-
{% for block in data.blocks %}
|
|
335
|
-
<div class="widget-block">
|
|
336
|
-
{% comment %} Доступные поля блока: ${exampleFields.join(', ')} {% endcomment %}
|
|
337
|
-
|
|
338
|
-
{% if block.image %}
|
|
339
|
-
{% assign img_width = widget_settings.layout-content-max-width | default: 1200 %}
|
|
340
|
-
<picture class="block-image">
|
|
341
|
-
<source media="(min-width:769px)" data-srcset="{{ block.image | image_url: img_width, format: 'webp', resizing_type: 'fit_width' }}" type="image/webp" class="lazyload">
|
|
342
|
-
<source media="(max-width:768px)" data-srcset="{{ block.image | image_url: 768, format: 'webp', resizing_type: 'fit_width' }}" type="image/webp" class="lazyload">
|
|
343
|
-
<img data-src="{{ block.image | image_url: 800, resizing_type: 'fit_width' }}" alt="{{ block.name }}" class="lazyload">
|
|
344
|
-
</picture>
|
|
345
|
-
{% endif %}
|
|
346
|
-
|
|
347
|
-
{% if block.name %}
|
|
348
|
-
<h3 class="block-title">{{ block.name }}</h3>
|
|
349
|
-
{% endif %}
|
|
350
|
-
|
|
351
|
-
{% if block.description or block.content %}
|
|
352
|
-
<div class="block-content">
|
|
353
|
-
{{ block.description | default: block.content }}
|
|
354
|
-
</div>
|
|
355
|
-
{% endif %}
|
|
356
|
-
|
|
357
|
-
{% if block.link %}
|
|
358
|
-
<a href="{{ block.link }}" class="block-link">
|
|
359
|
-
{{ block.button_text | default: widget_messages.read_more }}
|
|
360
|
-
</a>
|
|
361
|
-
{% endif %}
|
|
362
|
-
</div>
|
|
363
|
-
{% endfor %}
|
|
364
|
-
</div>
|
|
365
|
-
{% else %}
|
|
366
|
-
{% comment %} Заглушка для редактора {% endcomment %}
|
|
367
|
-
{% if editor_mode? %}
|
|
368
|
-
<div class="widget-empty">
|
|
369
|
-
<p>{{ widget_messages.no_blocks | default: 'Добавьте блоки для отображения контента' }}</p>
|
|
370
|
-
</div>
|
|
371
|
-
{% endif %}
|
|
372
|
-
{% endif %}
|
|
373
|
-
`;
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
liquid += ` <div class="widget-content">
|
|
377
|
-
{% comment %} Здесь размещается контент простого виджета {% endcomment %}
|
|
378
|
-
<p>{{ widget_messages.widget_description | default: 'Контент виджета' }}</p>
|
|
379
|
-
</div>
|
|
380
|
-
`;
|
|
381
|
-
}
|
|
382
|
-
liquid += `</div>
|
|
383
|
-
`;
|
|
384
|
-
return liquid;
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Генерирует snippet.scss
|
|
388
|
-
* ВАЖНО: Весь код оборачивается в класс виджета
|
|
389
|
-
* Используйте & для обращения к родительскому элементу (layout + класс виджета)
|
|
390
|
-
*/
|
|
391
|
-
export function generateSnippetScss(config) {
|
|
392
|
-
return `// ВАЖНО: Код автоматически оборачивается в .layout.widget-type_${config.handle}
|
|
393
|
-
// Используйте & для обращения к родительскому классу
|
|
394
|
-
// CSS-переменные из настроек доступны через var(--название-настройки)
|
|
395
|
-
|
|
396
|
-
// КРИТИЧЕСКИ ВАЖНО: Миксин background-color устанавливает фон И автоматически
|
|
397
|
-
// определяет контрастный цвет текста (светлый на тёмном фоне и наоборот)
|
|
398
|
-
// Переменная --bg всегда доступна из настроек design
|
|
399
|
-
@include background-color(--bg);
|
|
400
|
-
|
|
401
|
-
& {
|
|
402
|
-
// Основные стили родительского элемента
|
|
403
|
-
padding-top: var(--layout-pt, 2rem);
|
|
404
|
-
padding-bottom: var(--layout-pb, 2rem);
|
|
405
|
-
position: relative;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Широкий фон
|
|
409
|
-
&[style*="--layout-wide-bg:true"] {
|
|
410
|
-
width: 100vw;
|
|
411
|
-
margin-left: calc(50% - 50vw);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Скрытие на устройствах
|
|
415
|
-
&[style*="--hide-desktop:true"] {
|
|
416
|
-
@media (min-width: 1024px) {
|
|
417
|
-
display: none;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
&[style*="--hide-mobile:true"] {
|
|
422
|
-
@media (max-width: 767px) {
|
|
423
|
-
display: none;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Состояния на основе настроек
|
|
428
|
-
&[style*="--heading-hide:true"] {
|
|
429
|
-
.widget-heading {
|
|
430
|
-
display: none;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Контейнер виджета
|
|
435
|
-
.widget-container {
|
|
436
|
-
max-width: var(--layout-content-max-width, 1200px);
|
|
437
|
-
margin: 0 auto;
|
|
438
|
-
padding: 0 15px;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Заголовок виджета
|
|
442
|
-
.widget-heading {
|
|
443
|
-
text-align: var(--align-title, left);
|
|
444
|
-
margin-bottom: 2rem;
|
|
445
|
-
font-size: 1.5rem;
|
|
446
|
-
font-weight: 600;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Блоки (для block_list_widget_type)
|
|
450
|
-
.widget-blocks {
|
|
451
|
-
display: grid;
|
|
452
|
-
gap: var(--slide-gap, 2rem);
|
|
453
|
-
|
|
454
|
-
@media (min-width: 768px) {
|
|
455
|
-
grid-template-columns: repeat(auto-fit, minmax(var(--slide-width, 300px), 1fr));
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
@media (max-width: 767px) {
|
|
459
|
-
grid-template-columns: repeat(auto-fit, minmax(var(--slide-width-mobile, 150px), 1fr));
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
.widget-block {
|
|
464
|
-
position: relative;
|
|
465
|
-
|
|
466
|
-
.block-image {
|
|
467
|
-
width: 100%;
|
|
468
|
-
aspect-ratio: var(--img-ratio, 1);
|
|
469
|
-
object-fit: cover;
|
|
470
|
-
border-radius: var(--banner-border-radius, 0);
|
|
471
|
-
overflow: hidden;
|
|
472
|
-
|
|
473
|
-
@media (max-width: 767px) {
|
|
474
|
-
aspect-ratio: var(--img-ratio-mobile, 1);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
img {
|
|
478
|
-
width: 100%;
|
|
479
|
-
height: 100%;
|
|
480
|
-
object-fit: cover;
|
|
481
|
-
transition: transform 0.3s ease;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
&:hover .block-image img {
|
|
486
|
-
transform: scale(1.05);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
.block-title {
|
|
490
|
-
margin: 1rem 0 0.5rem;
|
|
491
|
-
font-size: 1.25rem;
|
|
492
|
-
font-weight: 600;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.block-content {
|
|
496
|
-
margin: 0.5rem 0;
|
|
497
|
-
color: var(--text-color, inherit);
|
|
498
|
-
line-height: 1.6;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
.block-link {
|
|
502
|
-
display: inline-block;
|
|
503
|
-
margin-top: 1rem;
|
|
504
|
-
padding: 0.5rem 1.5rem;
|
|
505
|
-
background: var(--button-bg, #000);
|
|
506
|
-
color: var(--button-color, #fff);
|
|
507
|
-
text-decoration: none;
|
|
508
|
-
border-radius: var(--button-radius, 4px);
|
|
509
|
-
transition: opacity 0.2s;
|
|
510
|
-
|
|
511
|
-
&:hover {
|
|
512
|
-
opacity: 0.8;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Заглушка для редактора
|
|
518
|
-
.widget-empty {
|
|
519
|
-
padding: 3rem;
|
|
520
|
-
text-align: center;
|
|
521
|
-
background: #f5f5f5;
|
|
522
|
-
border: 2px dashed #ddd;
|
|
523
|
-
border-radius: 8px;
|
|
524
|
-
|
|
525
|
-
p {
|
|
526
|
-
margin: 0;
|
|
527
|
-
color: #999;
|
|
528
|
-
font-size: 0.875rem;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Адаптивность
|
|
533
|
-
@media screen and (max-width: 767px) {
|
|
534
|
-
& {
|
|
535
|
-
// На мобильных используются те же переменные --layout-pt и --layout-pb
|
|
536
|
-
// Значения задаются в настройках в vw
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
`;
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Генерирует snippet.js
|
|
543
|
-
* ВАЖНО:
|
|
544
|
-
* - Код автоматически оборачивается в try/catch
|
|
545
|
-
* - Доступны переменные: widget (селектор) и $widget (jQuery объект)
|
|
546
|
-
* - НЕ используйте Vue.js или другие фреймворки!
|
|
547
|
-
* - На странице может быть несколько экземпляров виджета - используйте $widget.each()
|
|
548
|
-
*/
|
|
549
|
-
export function generateSnippetJs(config) {
|
|
550
|
-
const hasJquery = config.libraries.includes('jquery');
|
|
551
|
-
const hasCommonjs = config.libraries.includes('commonjs_v2');
|
|
552
|
-
const hasSplide = config.libraries.includes('splide') || config.libraries.includes('splide3');
|
|
553
|
-
const widgetClass = `widget-type_${config.handle}`;
|
|
554
|
-
let js = `/**
|
|
555
|
-
* Виджет: ${generateWidgetName(config.description)}
|
|
556
|
-
* Handle: ${config.handle}
|
|
557
|
-
*
|
|
558
|
-
* ВАЖНО:
|
|
559
|
-
* - Этот код автоматически оборачивается в try/catch
|
|
560
|
-
* - Доступны переменные: widget = '.${widgetClass}' и $widget = $('.${widgetClass}')
|
|
561
|
-
* - Виджет обёрнут в .layout.${widgetClass}
|
|
562
|
-
* - На странице может быть несколько виджетов - используйте $widget.each()
|
|
563
|
-
*/
|
|
564
|
-
|
|
565
|
-
`;
|
|
566
|
-
if (hasCommonjs) {
|
|
567
|
-
js += `// CommonJS API InSales
|
|
568
|
-
// Документация: https://liquidhub.ru/collection/start
|
|
569
|
-
// Основные модули: EventBus, Cart, Products, Shop
|
|
570
|
-
|
|
571
|
-
`;
|
|
572
|
-
}
|
|
573
|
-
if (hasJquery) {
|
|
574
|
-
js += `// Переменная $widget уже доступна (создана системой)
|
|
575
|
-
// ВАЖНО: На странице может быть несколько виджетов одного типа
|
|
576
|
-
if ($widget.length === 0) return;
|
|
577
|
-
|
|
578
|
-
console.log('Виджет ${config.handle} инициализирован, найдено экземпляров:', $widget.length);
|
|
579
|
-
|
|
580
|
-
// Перебираем каждый экземпляр виджета на странице
|
|
581
|
-
$widget.each(function(index, el) {
|
|
582
|
-
const $widgetInstance = $(el);
|
|
583
|
-
|
|
584
|
-
// Пример: получение настроек из CSS-переменных для каждого экземпляра
|
|
585
|
-
const settings = {
|
|
586
|
-
slideWidth: getComputedStyle(el).getPropertyValue('--slide-width'),
|
|
587
|
-
slideGap: getComputedStyle(el).getPropertyValue('--slide-gap'),
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
console.log(\`Инициализация виджета #\${index}\`, settings);
|
|
591
|
-
|
|
592
|
-
`;
|
|
593
|
-
if (hasSplide) {
|
|
594
|
-
js += ` // Инициализация Splide слайдера для этого экземпляра
|
|
595
|
-
const splideEl = $widgetInstance.find('.splide');
|
|
596
|
-
if (splideEl.length) {
|
|
597
|
-
new Splide(splideEl[0], {
|
|
598
|
-
type: 'loop',
|
|
599
|
-
perPage: 3,
|
|
600
|
-
perMove: 1,
|
|
601
|
-
gap: '1rem',
|
|
602
|
-
pagination: false,
|
|
603
|
-
breakpoints: {
|
|
604
|
-
768: {
|
|
605
|
-
perPage: 1,
|
|
606
|
-
},
|
|
607
|
-
1024: {
|
|
608
|
-
perPage: 2,
|
|
609
|
-
},
|
|
610
|
-
},
|
|
611
|
-
}).mount();
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
`;
|
|
615
|
-
}
|
|
616
|
-
js += ` // Ваш код для каждого экземпляра виджета
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
`;
|
|
620
|
-
if (hasCommonjs) {
|
|
621
|
-
js += `// Пример работы с EventBus (глобальные события, вне цикла)
|
|
622
|
-
EventBus.subscribe('cart:add', function(cart) {
|
|
623
|
-
console.log('Товар добавлен в корзину', cart);
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
// Реакция на изменение настроек в редакторе
|
|
627
|
-
EventBus.subscribe([
|
|
628
|
-
'widget:input-setting:insales:system:editor',
|
|
629
|
-
'widget:change-setting:insales:system:editor'
|
|
630
|
-
], function(data) {
|
|
631
|
-
console.log('Настройка изменена:', data.setting_name, data.value);
|
|
632
|
-
// Здесь можно обновить виджет при изменении настроек
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
`;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
js += `// Переменная widget уже доступна (создана системой)
|
|
640
|
-
// ВАЖНО: На странице может быть несколько виджетов одного типа
|
|
641
|
-
const widgetElements = document.querySelectorAll(widget);
|
|
642
|
-
|
|
643
|
-
if (widgetElements.length === 0) return;
|
|
644
|
-
|
|
645
|
-
console.log('Виджет ${config.handle} инициализирован, найдено экземпляров:', widgetElements.length);
|
|
646
|
-
|
|
647
|
-
// Перебираем каждый экземпляр виджета на странице
|
|
648
|
-
widgetElements.forEach((el, index) => {
|
|
649
|
-
// Пример: получение настроек из CSS-переменных для каждого экземпляра
|
|
650
|
-
const styles = getComputedStyle(el);
|
|
651
|
-
const settings = {
|
|
652
|
-
slideWidth: styles.getPropertyValue('--slide-width'),
|
|
653
|
-
slideGap: styles.getPropertyValue('--slide-gap'),
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
console.log(\`Инициализация виджета #\${index}\`, settings);
|
|
657
|
-
|
|
658
|
-
`;
|
|
659
|
-
if (hasSplide) {
|
|
660
|
-
js += ` // Инициализация Splide слайдера для этого экземпляра
|
|
661
|
-
const splideEl = el.querySelector('.splide');
|
|
662
|
-
if (splideEl) {
|
|
663
|
-
new Splide(splideEl, {
|
|
664
|
-
type: 'loop',
|
|
665
|
-
perPage: 3,
|
|
666
|
-
perMove: 1,
|
|
667
|
-
gap: '1rem',
|
|
668
|
-
pagination: false,
|
|
669
|
-
breakpoints: {
|
|
670
|
-
768: {
|
|
671
|
-
perPage: 1,
|
|
672
|
-
},
|
|
673
|
-
1024: {
|
|
674
|
-
perPage: 2,
|
|
675
|
-
},
|
|
676
|
-
},
|
|
677
|
-
}).mount();
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
`;
|
|
681
|
-
}
|
|
682
|
-
js += ` // Ваш код для каждого экземпляра виджета
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
`;
|
|
686
|
-
if (hasCommonjs) {
|
|
687
|
-
js += `// Пример работы с EventBus (глобальные события, вне цикла)
|
|
688
|
-
if (typeof EventBus !== 'undefined') {
|
|
689
|
-
EventBus.subscribe('cart:add', function(cart) {
|
|
690
|
-
console.log('Товар добавлен в корзину', cart);
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
// Реакция на изменение настроек в редакторе
|
|
694
|
-
EventBus.subscribe([
|
|
695
|
-
'widget:input-setting:insales:system:editor',
|
|
696
|
-
'widget:change-setting:insales:system:editor'
|
|
697
|
-
], function(data) {
|
|
698
|
-
console.log('Настройка изменена:', data.setting_name, data.value);
|
|
699
|
-
// Здесь можно обновить виджет при изменении настроек
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
`;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
return js;
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Генерирует setup.json для block_list_widget_type
|
|
710
|
-
*/
|
|
711
|
-
export function generateSetupJson(blockTemplate) {
|
|
712
|
-
// Создаем дефолтный блок с полями из block template
|
|
713
|
-
const createDefaultBlock = () => {
|
|
714
|
-
const block = {};
|
|
715
|
-
blockTemplate.fields.forEach(field => {
|
|
716
|
-
// Генерируем дефолтные значения в зависимости от типа поля
|
|
717
|
-
switch (field.kind) {
|
|
718
|
-
case 'text':
|
|
719
|
-
case 'link':
|
|
720
|
-
block[field.handle] = '';
|
|
721
|
-
break;
|
|
722
|
-
case 'textarea':
|
|
723
|
-
case 'html':
|
|
724
|
-
block[field.handle] = '';
|
|
725
|
-
break;
|
|
726
|
-
case 'account_file':
|
|
727
|
-
case 'image':
|
|
728
|
-
// Для изображений оставляем пустую строку или placeholder ID
|
|
729
|
-
block[field.handle] = '';
|
|
730
|
-
break;
|
|
731
|
-
case 'checkbox':
|
|
732
|
-
block[field.handle] = false;
|
|
733
|
-
break;
|
|
734
|
-
case 'number':
|
|
735
|
-
case 'range':
|
|
736
|
-
block[field.handle] = 0;
|
|
737
|
-
break;
|
|
738
|
-
case 'color':
|
|
739
|
-
block[field.handle] = '#000000';
|
|
740
|
-
break;
|
|
741
|
-
case 'collection':
|
|
742
|
-
case 'product':
|
|
743
|
-
case 'blog':
|
|
744
|
-
case 'article':
|
|
745
|
-
case 'page':
|
|
746
|
-
block[field.handle] = null;
|
|
747
|
-
break;
|
|
748
|
-
default:
|
|
749
|
-
block[field.handle] = '';
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
return block;
|
|
753
|
-
};
|
|
754
|
-
// Создаем 3-4 дефолтных блока для примера
|
|
755
|
-
const defaultBlocksCount = Math.min(4, Math.max(2, blockTemplate.fields.length > 5 ? 2 : 3));
|
|
756
|
-
const blocks = Array.from({ length: defaultBlocksCount }, () => createDefaultBlock());
|
|
757
|
-
return {
|
|
758
|
-
blocks
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Генерирует код меню категорий на основе выбранного паттерна
|
|
763
|
-
*/
|
|
764
|
-
export function generateCollectionsMenu(args) {
|
|
765
|
-
// Импортируем паттерны
|
|
766
|
-
const collectionsMenuPatterns = {
|
|
767
|
-
patterns: [
|
|
768
|
-
{
|
|
769
|
-
id: 'two_level_hierarchical',
|
|
770
|
-
cacheKeyPattern: 'cache_menu_key_{{ widget_drop.list_item_id }}_{{ collections.last_updated_at }}_{{ language.locale }}_{{ custom_params }}',
|
|
771
|
-
requiredSettings: [
|
|
772
|
-
{
|
|
773
|
-
handle: 'subcollections-items-limit',
|
|
774
|
-
type: 'to_integer',
|
|
775
|
-
default: 10,
|
|
776
|
-
description: 'Ограничение количества подкатегорий для отображения'
|
|
777
|
-
},
|
|
778
|
-
{
|
|
779
|
-
handle: 'hide-menu-photo',
|
|
780
|
-
type: 'checkbox',
|
|
781
|
-
default: false,
|
|
782
|
-
description: 'Скрыть фотографии категорий'
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
handle: 'hide-counts',
|
|
786
|
-
type: 'checkbox',
|
|
787
|
-
default: false,
|
|
788
|
-
description: 'Скрыть счетчики товаров'
|
|
789
|
-
}
|
|
790
|
-
],
|
|
791
|
-
liquidCode: `{% assign subcollections_items_limit = widget_settings.subcollections-items-limit | to_integer %}
|
|
792
|
-
{% capture cache_menu_key %}cache_menu_key_{{ collections.last_updated_at }}_{{ language.locale }}_{{ widget_settings.hide-menu-photo }}_{{ widget_settings.subcollections-items-limit }}{% endcapture %}
|
|
793
|
-
|
|
794
|
-
<ul class="menu" data-navigation data-subcollections-items-limit={{subcollections_items_limit}}>
|
|
795
|
-
{% cache cache_menu_key %}
|
|
796
|
-
{% assign level_1 = collections.root_category.subcollections %}
|
|
797
|
-
{% for level_1_item in level_1 %}
|
|
798
|
-
{% assign level_2 = level_1_item.subcollections %}
|
|
799
|
-
|
|
800
|
-
<li class="menu__item is-level-1" data-navigation-item>
|
|
801
|
-
<div class="menu__controls">
|
|
802
|
-
<a href="{{ level_1_item.url }}" class="menu__link" data-navigation-link="{{ level_1_item.url }}">
|
|
803
|
-
{{ level_1_item.title }}
|
|
804
|
-
</a>
|
|
805
|
-
|
|
806
|
-
{% if level_2.size > 0 %}
|
|
807
|
-
<button class="menu__show-submenu-btn" type="button">
|
|
808
|
-
<i class="icon icon-angle-down"></i>
|
|
809
|
-
</button>
|
|
810
|
-
{% endif %}
|
|
811
|
-
</div>
|
|
812
|
-
|
|
813
|
-
{% if level_2.size > 0 %}
|
|
814
|
-
<ul class="menu__submenu" data-navigation-submenu>
|
|
815
|
-
{% for level_2_item in level_2 %}
|
|
816
|
-
<li class="menu__item {%if forloop.index > subcollections_items_limit %}is-hide{% endif%}" data-navigation-item>
|
|
817
|
-
<div class="menu__controls">
|
|
818
|
-
<a href="{{ level_2_item.url }}" class="menu__link" data-navigation-link="{{ level_2_item.url }}">
|
|
819
|
-
{{ level_2_item.title }}
|
|
820
|
-
{% if editor_mode? or widget_settings.hide-counts != true %}
|
|
821
|
-
{% if level_2_item.products_count > 0 %}
|
|
822
|
-
<span class="menu__item-count">{{ level_2_item.products_count }}</span>
|
|
823
|
-
{% endif %}
|
|
824
|
-
{% endif %}
|
|
825
|
-
</a>
|
|
826
|
-
</div>
|
|
827
|
-
</li>
|
|
828
|
-
{% endfor %}
|
|
829
|
-
</ul>
|
|
830
|
-
{% endif %}
|
|
831
|
-
</li>
|
|
832
|
-
{% endfor %}
|
|
833
|
-
{% endcache %}
|
|
834
|
-
</ul>`
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
id: 'multi_level_flatten',
|
|
838
|
-
cacheKeyPattern: 'cache_menu_key_{{ widget_drop.list_item_id }}_{{ collections.last_updated_at }}_{{ language.locale }}_{{ level_limit }}',
|
|
839
|
-
requiredSettings: [
|
|
840
|
-
{
|
|
841
|
-
handle: 'level-limit',
|
|
842
|
-
type: 'to_integer',
|
|
843
|
-
default: 3,
|
|
844
|
-
description: 'Максимальная глубина вложенности меню'
|
|
845
|
-
}
|
|
846
|
-
],
|
|
847
|
-
liquidCode: `{% assign level_limit = widget_settings.level-limit | default: 3 | to_integer %}
|
|
848
|
-
{% capture cache_menu_key %}cache_menu_key_{{ collections.last_updated_at }}_{{ language.locale }}_{{ level_limit }}{% endcapture %}
|
|
849
|
-
{% assign prev_link_level = 1 %}
|
|
850
|
-
|
|
851
|
-
<ul class="menu" data-navigation>
|
|
852
|
-
{% cache cache_menu_key %}
|
|
853
|
-
{% assign root_level = collections.root_category.level %}
|
|
854
|
-
{% for link in collections.root_category.flatten_branch %}
|
|
855
|
-
{% assign node_level = link.level | minus: root_level %}
|
|
856
|
-
{% assign _in_limit = level_limit | minus: node_level %}
|
|
857
|
-
{% assign _next_level_in_limit = _in_limit | minus: 1 %}
|
|
858
|
-
|
|
859
|
-
{% assign show_level = false %}
|
|
860
|
-
{% if _in_limit >= 0 %}
|
|
861
|
-
{% assign show_level = true %}
|
|
862
|
-
{% endif %}
|
|
863
|
-
|
|
864
|
-
{% assign show_next_level = false %}
|
|
865
|
-
{% if link.subcollections.size > 0 and _next_level_in_limit >= 0 %}
|
|
866
|
-
{% assign show_next_level = true %}
|
|
867
|
-
{% endif %}
|
|
868
|
-
|
|
869
|
-
{% if show_level %}
|
|
870
|
-
{% assign level_difference = prev_link_level | minus: link.level | plus: root_level %}
|
|
871
|
-
|
|
872
|
-
{% if level_difference > 0 %}
|
|
873
|
-
{% for i in (1..level_difference) %}
|
|
874
|
-
{% unless forloop.first %}</li>{% endunless %}
|
|
875
|
-
</ul>
|
|
876
|
-
{% endfor %}
|
|
877
|
-
</li>
|
|
878
|
-
{% endif %}
|
|
879
|
-
|
|
880
|
-
<li class="menu__item" data-navigation-item>
|
|
881
|
-
<div class="menu__controls {% if show_next_level %}with-submenu{% endif %}">
|
|
882
|
-
<a href="{{ link.url }}" class="menu__link" data-navigation-link="{{ link.url }}">
|
|
883
|
-
{{ link.title }}
|
|
884
|
-
</a>
|
|
885
|
-
|
|
886
|
-
{% if show_next_level %}
|
|
887
|
-
<button class="menu__show-submenu-btn" type="button">
|
|
888
|
-
<i class="icon icon-angle-down"></i>
|
|
889
|
-
</button>
|
|
890
|
-
{% endif %}
|
|
891
|
-
</div>
|
|
892
|
-
|
|
893
|
-
{% if show_next_level %}
|
|
894
|
-
<ul class="menu__submenu" data-navigation-submenu>
|
|
895
|
-
{% endif %}
|
|
896
|
-
|
|
897
|
-
{% assign prev_link_level = node_level %}
|
|
898
|
-
|
|
899
|
-
{% unless show_next_level %}
|
|
900
|
-
</li>
|
|
901
|
-
{% endunless %}
|
|
902
|
-
|
|
903
|
-
{% if forloop.last %}
|
|
904
|
-
{% assign prev_link_level = node_level | minus: 1 %}
|
|
905
|
-
{% for i in (1..prev_link_level) %}
|
|
906
|
-
</ul>
|
|
907
|
-
{% endfor %}
|
|
908
|
-
{% endif %}
|
|
909
|
-
{% endif %}
|
|
910
|
-
{% endfor %}
|
|
911
|
-
{% endcache %}
|
|
912
|
-
</ul>`
|
|
913
|
-
},
|
|
914
|
-
{
|
|
915
|
-
id: 'simple_subcollections',
|
|
916
|
-
cacheKeyPattern: 'cache_menu_key_{{ widget_drop.list_item_id }}_{{ collections.last_updated_at }}_{{ language.locale }}_{{ collection_handle }}',
|
|
917
|
-
requiredSettings: [
|
|
918
|
-
{
|
|
919
|
-
handle: 'collection-handle',
|
|
920
|
-
type: 'string',
|
|
921
|
-
default: '',
|
|
922
|
-
description: 'Handle категории (по умолчанию root_category)'
|
|
923
|
-
}
|
|
924
|
-
],
|
|
925
|
-
liquidCode: `{% capture cache_menu_key %}cache_menu_key_{{ collections.last_updated_at }}_{{ language.locale }}_{{ widget_settings.collection-handle }}{% endcapture %}
|
|
926
|
-
{% cache cache_menu_key %}
|
|
927
|
-
{% assign source_handle = widget_settings.collection-handle | default: collections.root_category.handle %}
|
|
928
|
-
{% assign collection_list = collections[source_handle].subcollections %}
|
|
929
|
-
{% if collection_list.size > 0 %}
|
|
930
|
-
<div class="menu-wrapper">
|
|
931
|
-
<ul class="menu" data-navigation>
|
|
932
|
-
{% for collection_item in collection_list %}
|
|
933
|
-
<li class="menu__item" data-navigation-item>
|
|
934
|
-
<div class="menu__controls">
|
|
935
|
-
<a href="{{ collection_item.url }}" class="menu__link" data-navigation-link="{{ collection_item.url }}">
|
|
936
|
-
{{ collection_item.title }}
|
|
937
|
-
</a>
|
|
938
|
-
</div>
|
|
939
|
-
</li>
|
|
940
|
-
{% endfor %}
|
|
941
|
-
</ul>
|
|
942
|
-
</div>
|
|
943
|
-
{% endif %}
|
|
944
|
-
{% endcache %}`
|
|
945
|
-
},
|
|
946
|
-
{
|
|
947
|
-
id: 'flat_collections_list',
|
|
948
|
-
cacheKeyPattern: 'cache_menu_key_{{ widget_drop.list_item_id }}_{{ collections.last_updated_at }}_{{ language.locale }}',
|
|
949
|
-
requiredSettings: [],
|
|
950
|
-
liquidCode: `{% capture cache_menu_key %}cache_menu_key_{{ collections.last_updated_at }}_{{ language.locale }}{% endcapture %}
|
|
951
|
-
{% cache cache_menu_key %}
|
|
952
|
-
{% if collections.root_category.subcollections.size > 0 %}
|
|
953
|
-
<div class="menu-wrapper" data-navigation>
|
|
954
|
-
{% for collection in collections %}
|
|
955
|
-
{% if collection.products_count > 0 %}
|
|
956
|
-
<div class="menu__item" data-navigation-item>
|
|
957
|
-
<a class="menu__link" href="{{ collection.url }}" data-navigation-link="{{ collection.url }}">
|
|
958
|
-
<span class="menu__title">{{ collection.title }}</span>
|
|
959
|
-
</a>
|
|
960
|
-
</div>
|
|
961
|
-
{% endif %}
|
|
962
|
-
{% endfor %}
|
|
963
|
-
</div>
|
|
964
|
-
{% endif %}
|
|
965
|
-
{% endcache %}`
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
id: 'tree_structure_flatten',
|
|
969
|
-
cacheKeyPattern: 'cache_menu_key_{{ widget_drop.list_item_id }}_{{ collections.last_updated_at }}_{{ language.locale }}',
|
|
970
|
-
requiredSettings: [],
|
|
971
|
-
liquidCode: `{% capture cache_menu_key %}cache_menu_key_{{ collections.last_updated_at }}_{{ language.locale }}{% endcapture %}
|
|
972
|
-
{% cache cache_menu_key %}
|
|
973
|
-
{% for collection in collections.flatten %}
|
|
974
|
-
{% if collection.first? %}<ul class="menu" data-navigation>{% endif %}
|
|
975
|
-
{% if collection.show? %}
|
|
976
|
-
<li class="menu__item" data-navigation-item>
|
|
977
|
-
<a href="{{ collection.url }}" class="menu__link" data-navigation-link="{{ collection.url }}">
|
|
978
|
-
{{ collection.title }}
|
|
979
|
-
</a>
|
|
980
|
-
</li>
|
|
981
|
-
{% endif %}
|
|
982
|
-
{% if collection.last? %}
|
|
983
|
-
{% for i in (1..collection.level_difference) %}</ul>{% endfor %}
|
|
984
|
-
{% endif %}
|
|
985
|
-
{% endfor %}
|
|
986
|
-
{% endcache %}`
|
|
987
|
-
}
|
|
988
|
-
]
|
|
989
|
-
};
|
|
990
|
-
const { patternId, customSettings = {}, cssPrefix = 'menu' } = args;
|
|
991
|
-
// Находим паттерн
|
|
992
|
-
const pattern = collectionsMenuPatterns.patterns.find(p => p.id === patternId);
|
|
993
|
-
if (!pattern) {
|
|
994
|
-
throw new Error(`Паттерн меню "${patternId}" не найден. Доступные: ${collectionsMenuPatterns.patterns.map(p => p.id).join(', ')}`);
|
|
995
|
-
}
|
|
996
|
-
// Применяем кастомный CSS префикс если задан
|
|
997
|
-
let liquidCode = pattern.liquidCode;
|
|
998
|
-
if (cssPrefix !== 'menu') {
|
|
999
|
-
liquidCode = liquidCode.replace(/class="menu/g, `class="${cssPrefix}`);
|
|
1000
|
-
}
|
|
1001
|
-
// Объединяем настройки
|
|
1002
|
-
const requiredSettings = [...pattern.requiredSettings];
|
|
1003
|
-
if (customSettings && Object.keys(customSettings).length > 0) {
|
|
1004
|
-
Object.entries(customSettings).forEach(([key, value]) => {
|
|
1005
|
-
requiredSettings.push({
|
|
1006
|
-
handle: key,
|
|
1007
|
-
type: 'string',
|
|
1008
|
-
default: value,
|
|
1009
|
-
description: `Custom setting: ${key}`
|
|
1010
|
-
});
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
|
-
return {
|
|
1014
|
-
liquidCode,
|
|
1015
|
-
requiredSettings,
|
|
1016
|
-
cssPrefix,
|
|
1017
|
-
dataAttributes: [
|
|
1018
|
-
{ name: 'data-navigation', description: 'Основной контейнер навигации' },
|
|
1019
|
-
{ name: 'data-navigation-item', description: 'Элемент навигации' },
|
|
1020
|
-
{ name: 'data-navigation-link', description: 'Ссылка навигации' },
|
|
1021
|
-
{ name: 'data-navigation-submenu', description: 'Подменю' }
|
|
1022
|
-
]
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
3
|
+
* Главный модуль для экспорта всех функций генерации
|
|
4
|
+
*/
|
|
5
|
+
// Импортируем функции генерации
|
|
6
|
+
import { generateWidgetName, generateInfoJson, generateMessages } from './info-generator.js';
|
|
7
|
+
import { generateSettingsData, generateSettingsForm } from './settings-generator.js';
|
|
8
|
+
import { generateSnippetLiquid, generateSnippetScss, generateSnippetJs, generateSetupJson } from './snippet-generator.js';
|
|
9
|
+
import { validateLiquidFilters, fixInvalidFilters } from './validation.js';
|
|
10
|
+
import { generateCollectionsMenu } from './collections-menu.js';
|
|
11
|
+
// Экспортируем функции генерации info.json и messages.json
|
|
12
|
+
export { generateWidgetName, generateInfoJson, generateMessages };
|
|
13
|
+
// Экспортируем функции генерации настроек
|
|
14
|
+
export { generateSettingsData, generateSettingsForm };
|
|
15
|
+
// Экспортируем функции генерации snippet файлов
|
|
16
|
+
export { generateSnippetLiquid, generateSnippetScss, generateSnippetJs, generateSetupJson };
|
|
17
|
+
// Экспортируем функции валидации
|
|
18
|
+
export { validateLiquidFilters, fixInvalidFilters };
|
|
19
|
+
// Экспортируем функции генерации меню категорий
|
|
20
|
+
export { generateCollectionsMenu };
|
|
1025
21
|
/**
|
|
1026
22
|
* Генерирует все файлы виджета
|
|
1027
23
|
*/
|
|
1028
|
-
export function generateWidgetFiles(config) {
|
|
24
|
+
export function generateWidgetFiles(config, cleanMode = false) {
|
|
1029
25
|
const files = {};
|
|
1030
26
|
// info.json
|
|
1031
27
|
files['info.json'] = generateInfoJson(config);
|
|
@@ -1038,7 +34,7 @@ export function generateWidgetFiles(config) {
|
|
|
1038
34
|
files['settings_form.json'] = settingsForm.form;
|
|
1039
35
|
files['settings_form'] = { settingsCount: settingsForm.count }; // метаинформация
|
|
1040
36
|
// snippet.liquid
|
|
1041
|
-
const liquidCode = generateSnippetLiquid(config);
|
|
37
|
+
const liquidCode = generateSnippetLiquid(config, cleanMode);
|
|
1042
38
|
// Валидируем Liquid код на несуществующие фильтры
|
|
1043
39
|
const validation = validateLiquidFilters(liquidCode);
|
|
1044
40
|
if (!validation.isValid) {
|
|
@@ -1063,80 +59,13 @@ export function generateWidgetFiles(config) {
|
|
|
1063
59
|
files['snippet.liquid'] = liquidCode;
|
|
1064
60
|
}
|
|
1065
61
|
// snippet.scss
|
|
1066
|
-
files['snippet.scss'] = generateSnippetScss(config);
|
|
62
|
+
files['snippet.scss'] = generateSnippetScss(config, cleanMode);
|
|
1067
63
|
// snippet.js
|
|
1068
|
-
files['snippet.js'] = generateSnippetJs(config);
|
|
64
|
+
files['snippet.js'] = generateSnippetJs(config, cleanMode);
|
|
1069
65
|
// setup.json (только для block_list_widget_type)
|
|
1070
66
|
if (config.type === 'block_list_widget_type' && config.blockTemplate) {
|
|
1071
67
|
files['setup.json'] = generateSetupJson(config.blockTemplate);
|
|
1072
68
|
}
|
|
1073
69
|
return files;
|
|
1074
70
|
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Валидирует Liquid код на наличие несуществующих фильтров
|
|
1077
|
-
*/
|
|
1078
|
-
export function validateLiquidFilters(liquidCode) {
|
|
1079
|
-
const invalidFiltersList = [
|
|
1080
|
-
{ name: 't', suggestion: 'Используйте messages или widget_messages для переводов' },
|
|
1081
|
-
{ name: 'translate', suggestion: 'Используйте messages для переводов' },
|
|
1082
|
-
{ name: 'i18n', suggestion: 'Используйте messages для переводов' },
|
|
1083
|
-
{ name: 'l', suggestion: 'Используйте messages для переводов' },
|
|
1084
|
-
{ name: 'localize', suggestion: 'Используйте messages для переводов' },
|
|
1085
|
-
{ name: 'tr', suggestion: 'Используйте messages для переводов' }
|
|
1086
|
-
];
|
|
1087
|
-
const errors = [];
|
|
1088
|
-
const lines = liquidCode.split('\n');
|
|
1089
|
-
lines.forEach((line, lineIndex) => {
|
|
1090
|
-
invalidFiltersList.forEach(invalidFilter => {
|
|
1091
|
-
// Ищем паттерн | filter_name
|
|
1092
|
-
const regex = new RegExp(`\\|\\s*${invalidFilter.name}\\b`, 'g');
|
|
1093
|
-
let match;
|
|
1094
|
-
while ((match = regex.exec(line)) !== null) {
|
|
1095
|
-
errors.push({
|
|
1096
|
-
filter: invalidFilter.name,
|
|
1097
|
-
line: lineIndex + 1,
|
|
1098
|
-
column: match.index + 1,
|
|
1099
|
-
suggestion: invalidFilter.suggestion
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
});
|
|
1103
|
-
});
|
|
1104
|
-
return {
|
|
1105
|
-
isValid: errors.length === 0,
|
|
1106
|
-
errors
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Проверяет и исправляет несуществующие фильтры в Liquid коде
|
|
1111
|
-
*/
|
|
1112
|
-
export function fixInvalidFilters(liquidCode) {
|
|
1113
|
-
const fixes = [];
|
|
1114
|
-
let fixedCode = liquidCode;
|
|
1115
|
-
// Исправляем фильтр 't' на messages
|
|
1116
|
-
const tRegex = /\|\s*t\b/g;
|
|
1117
|
-
let match;
|
|
1118
|
-
while ((match = tRegex.exec(fixedCode)) !== null) {
|
|
1119
|
-
const beforeMatch = fixedCode.substring(0, match.index);
|
|
1120
|
-
const afterMatch = fixedCode.substring(match.index + match[0].length);
|
|
1121
|
-
// Определяем, что это за контекст
|
|
1122
|
-
const contextBefore = beforeMatch.substring(Math.max(0, beforeMatch.length - 50));
|
|
1123
|
-
if (contextBefore.includes('widget_messages')) {
|
|
1124
|
-
// Если это в контексте widget_messages, оставляем как есть
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
else {
|
|
1128
|
-
// Заменяем на messages
|
|
1129
|
-
fixedCode = beforeMatch + '| messages' + afterMatch;
|
|
1130
|
-
fixes.push({
|
|
1131
|
-
filter: 't',
|
|
1132
|
-
line: fixedCode.substring(0, match.index).split('\n').length,
|
|
1133
|
-
suggestion: 'Заменен на messages'
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
return {
|
|
1138
|
-
fixedCode,
|
|
1139
|
-
fixes
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
71
|
//# sourceMappingURL=index.js.map
|