iwgt 2.4.12 → 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-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 -1098
- 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/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
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Генераторы для snippet файлов (liquid, scss, js)
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Генерирует snippet.liquid
|
|
6
|
+
* ВАЖНО: Код автоматически оборачивается системой в div.layout с CSS-переменными
|
|
7
|
+
* НЕ добавляйте обёртку layout вручную!
|
|
8
|
+
*/
|
|
9
|
+
export function generateSnippetLiquid(config, cleanMode = false) {
|
|
10
|
+
const useBlocks = config.type === 'block_list_widget_type';
|
|
11
|
+
let liquid = '';
|
|
12
|
+
if (!cleanMode) {
|
|
13
|
+
liquid += `{% comment %}
|
|
14
|
+
Виджет: ${generateWidgetName(config.description)}
|
|
15
|
+
Категория: ${config.category}
|
|
16
|
+
Тип: ${config.type}
|
|
17
|
+
|
|
18
|
+
ВАЖНО:
|
|
19
|
+
- Этот код будет автоматически обёрнут в div.layout с классом widget-type_${config.handle}
|
|
20
|
+
- CSS-переменные из настроек будут доступны через var(--handle-настройки)
|
|
21
|
+
- Используйте widget_settings для доступа к настройкам
|
|
22
|
+
${useBlocks ? '- Блоки доступны через data.blocks (не widget.blocks!)' : ''}
|
|
23
|
+
{% endcomment %}
|
|
24
|
+
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
liquid += `<div class="widget-container">
|
|
28
|
+
`;
|
|
29
|
+
// Заголовок виджета (если есть в настройках)
|
|
30
|
+
liquid += ` {% if widget_settings.show-heading %}
|
|
31
|
+
<h2 class="widget-heading">{{ widget_settings.heading-text }}</h2>
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
34
|
+
`;
|
|
35
|
+
if (useBlocks) {
|
|
36
|
+
const blockTemplate = config.blockTemplate;
|
|
37
|
+
const exampleFields = blockTemplate ? Object.keys(blockTemplate.fields).slice(0, 3) : ['name', 'image', 'link'];
|
|
38
|
+
liquid += ` {% comment %} Блоки используют шаблон: ${blockTemplate?.handle || 'не указан'} {% endcomment %}
|
|
39
|
+
{% if data.blocks and data.blocks.size > 0 %}
|
|
40
|
+
<div class="widget-blocks">
|
|
41
|
+
{% for block in data.blocks %}
|
|
42
|
+
<div class="widget-block">
|
|
43
|
+
{% comment %} Доступные поля блока: ${exampleFields.join(', ')} {% endcomment %}
|
|
44
|
+
|
|
45
|
+
{% if block.image %}
|
|
46
|
+
{% assign img_width = widget_settings.layout-content-max-width | default: 1200 %}
|
|
47
|
+
<picture class="block-image">
|
|
48
|
+
<source media="(min-width:769px)" data-srcset="{{ block.image | image_url: img_width, format: 'webp', resizing_type: 'fit_width' }}" type="image/webp" class="lazyload">
|
|
49
|
+
<source media="(max-width:768px)" data-srcset="{{ block.image | image_url: 768, format: 'webp', resizing_type: 'fit_width' }}" type="image/webp" class="lazyload">
|
|
50
|
+
<img data-src="{{ block.image | image_url: 800, resizing_type: 'fit_width' }}" alt="{{ block.name }}" class="lazyload">
|
|
51
|
+
</picture>
|
|
52
|
+
{% endif %}
|
|
53
|
+
|
|
54
|
+
{% if block.name %}
|
|
55
|
+
<h3 class="block-title">{{ block.name }}</h3>
|
|
56
|
+
{% endif %}
|
|
57
|
+
|
|
58
|
+
{% if block.description or block.content %}
|
|
59
|
+
<div class="block-content">
|
|
60
|
+
{{ block.description | default: block.content }}
|
|
61
|
+
</div>
|
|
62
|
+
{% endif %}
|
|
63
|
+
|
|
64
|
+
{% if block.link %}
|
|
65
|
+
<a href="{{ block.link }}" class="block-link">
|
|
66
|
+
{{ block.button_text | default: widget_messages.read_more }}
|
|
67
|
+
</a>
|
|
68
|
+
{% endif %}
|
|
69
|
+
</div>
|
|
70
|
+
{% endfor %}
|
|
71
|
+
</div>
|
|
72
|
+
{% else %}
|
|
73
|
+
{% comment %} Заглушка для редактора {% endcomment %}
|
|
74
|
+
{% if editor_mode? %}
|
|
75
|
+
<div class="widget-empty">
|
|
76
|
+
<p>{{ widget_messages.no_blocks | default: 'Добавьте блоки для отображения контента' }}</p>
|
|
77
|
+
</div>
|
|
78
|
+
{% endif %}
|
|
79
|
+
{% endif %}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
liquid += ` <div class="widget-content">
|
|
84
|
+
{% comment %} Здесь размещается контент простого виджета {% endcomment %}
|
|
85
|
+
<p>{{ widget_messages.widget_description | default: 'Контент виджета' }}</p>
|
|
86
|
+
</div>
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
liquid += `</div>
|
|
90
|
+
`;
|
|
91
|
+
return liquid;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Генерирует snippet.scss
|
|
95
|
+
* ВАЖНО: Весь код оборачивается в класс виджета
|
|
96
|
+
* Используйте & для обращения к родительскому элементу (layout + класс виджета)
|
|
97
|
+
*/
|
|
98
|
+
export function generateSnippetScss(config, cleanMode = false) {
|
|
99
|
+
let scss = '';
|
|
100
|
+
if (!cleanMode) {
|
|
101
|
+
scss += `// ВАЖНО: Код автоматически оборачивается в .layout.widget-type_${config.handle}
|
|
102
|
+
// Используйте & для обращения к родительскому классу
|
|
103
|
+
// CSS-переменные из настроек доступны через var(--название-настройки)
|
|
104
|
+
|
|
105
|
+
// КРИТИЧЕСКИ ВАЖНО: Миксин background-color устанавливает фон И автоматически
|
|
106
|
+
// определяет контрастный цвет текста (светлый на тёмном фоне и наоборот)
|
|
107
|
+
// Переменная --bg всегда доступна из настроек design
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
scss += `@include background-color(--bg);
|
|
111
|
+
|
|
112
|
+
& {
|
|
113
|
+
// Основные стили родительского элемента
|
|
114
|
+
padding-top: var(--layout-pt, 2rem);
|
|
115
|
+
padding-bottom: var(--layout-pb, 2rem);
|
|
116
|
+
position: relative;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Широкий фон
|
|
120
|
+
&[style*="--layout-wide-bg:true"] {
|
|
121
|
+
width: 100vw;
|
|
122
|
+
margin-left: calc(50% - 50vw);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Скрытие на устройствах
|
|
126
|
+
&[style*="--hide-desktop:true"] {
|
|
127
|
+
@media (min-width: 1024px) {
|
|
128
|
+
display: none;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&[style*="--hide-mobile:true"] {
|
|
133
|
+
@media (max-width: 767px) {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Состояния на основе настроек
|
|
139
|
+
&[style*="--heading-hide:true"] {
|
|
140
|
+
.widget-heading {
|
|
141
|
+
display: none;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Контейнер виджета
|
|
146
|
+
.widget-container {
|
|
147
|
+
max-width: var(--layout-content-max-width, 1200px);
|
|
148
|
+
margin: 0 auto;
|
|
149
|
+
padding: 0 15px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Заголовок виджета
|
|
153
|
+
.widget-heading {
|
|
154
|
+
text-align: var(--align-title, left);
|
|
155
|
+
margin-bottom: 2rem;
|
|
156
|
+
font-size: 1.5rem;
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Блоки (для block_list_widget_type)
|
|
161
|
+
.widget-blocks {
|
|
162
|
+
display: grid;
|
|
163
|
+
gap: var(--slide-gap, 2rem);
|
|
164
|
+
|
|
165
|
+
@media (min-width: 768px) {
|
|
166
|
+
grid-template-columns: repeat(auto-fit, minmax(var(--slide-width, 300px), 1fr));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@media (max-width: 767px) {
|
|
170
|
+
grid-template-columns: repeat(auto-fit, minmax(var(--slide-width-mobile, 150px), 1fr));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.widget-block {
|
|
175
|
+
position: relative;
|
|
176
|
+
|
|
177
|
+
.block-image {
|
|
178
|
+
width: 100%;
|
|
179
|
+
aspect-ratio: var(--img-ratio, 1);
|
|
180
|
+
object-fit: cover;
|
|
181
|
+
border-radius: var(--banner-border-radius, 0);
|
|
182
|
+
overflow: hidden;
|
|
183
|
+
|
|
184
|
+
@media (max-width: 767px) {
|
|
185
|
+
aspect-ratio: var(--img-ratio-mobile, 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
img {
|
|
189
|
+
width: 100%;
|
|
190
|
+
height: 100%;
|
|
191
|
+
object-fit: cover;
|
|
192
|
+
transition: transform 0.3s ease;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
&:hover .block-image img {
|
|
197
|
+
transform: scale(1.05);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.block-title {
|
|
201
|
+
margin: 1rem 0 0.5rem;
|
|
202
|
+
font-size: 1.25rem;
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.block-content {
|
|
207
|
+
margin: 0.5rem 0;
|
|
208
|
+
color: var(--text-color, inherit);
|
|
209
|
+
line-height: 1.6;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.block-link {
|
|
213
|
+
display: inline-block;
|
|
214
|
+
margin-top: 1rem;
|
|
215
|
+
padding: 0.5rem 1.5rem;
|
|
216
|
+
background: var(--button-bg, #000);
|
|
217
|
+
color: var(--button-color, #fff);
|
|
218
|
+
text-decoration: none;
|
|
219
|
+
border-radius: var(--button-radius, 4px);
|
|
220
|
+
transition: opacity 0.2s;
|
|
221
|
+
|
|
222
|
+
&:hover {
|
|
223
|
+
opacity: 0.8;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Заглушка для редактора
|
|
229
|
+
.widget-empty {
|
|
230
|
+
padding: 3rem;
|
|
231
|
+
text-align: center;
|
|
232
|
+
background: #f5f5f5;
|
|
233
|
+
border: 2px dashed #ddd;
|
|
234
|
+
border-radius: 8px;
|
|
235
|
+
|
|
236
|
+
p {
|
|
237
|
+
margin: 0;
|
|
238
|
+
color: #999;
|
|
239
|
+
font-size: 0.875rem;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Адаптивность
|
|
244
|
+
@media screen and (max-width: 767px) {
|
|
245
|
+
& {
|
|
246
|
+
// На мобильных используются те же переменные --layout-pt и --layout-pb
|
|
247
|
+
// Значения задаются в настройках в vw
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
`;
|
|
251
|
+
return scss;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Генерирует snippet.js
|
|
255
|
+
* ВАЖНО:
|
|
256
|
+
* - Код автоматически оборачивается в try/catch
|
|
257
|
+
* - Доступны переменные: widget (селектор) и $widget (jQuery объект)
|
|
258
|
+
* - НЕ используйте Vue.js или другие фреймворки!
|
|
259
|
+
* - На странице может быть несколько экземпляров виджета - используйте $widget.each()
|
|
260
|
+
*/
|
|
261
|
+
export function generateSnippetJs(config, cleanMode = false) {
|
|
262
|
+
const hasJquery = config.libraries.includes('jquery');
|
|
263
|
+
const hasCommonjs = config.libraries.includes('commonjs_v2');
|
|
264
|
+
const hasSplide = config.libraries.includes('splide') || config.libraries.includes('splide3');
|
|
265
|
+
const widgetClass = `widget-type_${config.handle}`;
|
|
266
|
+
let js = '';
|
|
267
|
+
if (!cleanMode) {
|
|
268
|
+
js += `/**
|
|
269
|
+
* Виджет: ${generateWidgetName(config.description)}
|
|
270
|
+
* Handle: ${config.handle}
|
|
271
|
+
*
|
|
272
|
+
* ВАЖНО:
|
|
273
|
+
* - Этот код автоматически оборачивается в try/catch
|
|
274
|
+
* - Доступны переменные: widget = '.${widgetClass}' и $widget = $('.${widgetClass}')
|
|
275
|
+
* - Виджет обёрнут в .layout.${widgetClass}
|
|
276
|
+
* - На странице может быть несколько виджетов - используйте $widget.each()
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
if (hasCommonjs && !cleanMode) {
|
|
282
|
+
js += `// CommonJS API InSales
|
|
283
|
+
// Документация: https://liquidhub.ru/collection/start
|
|
284
|
+
// Основные модули: EventBus, Cart, Products, Shop
|
|
285
|
+
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
if (hasJquery) {
|
|
289
|
+
if (!cleanMode) {
|
|
290
|
+
js += `// Переменная $widget уже доступна (создана системой)
|
|
291
|
+
// ВАЖНО: На странице может быть несколько виджетов одного типа
|
|
292
|
+
`;
|
|
293
|
+
}
|
|
294
|
+
js += `$widget.each(function(index, el) {
|
|
295
|
+
const $widgetInstance = $(el);
|
|
296
|
+
|
|
297
|
+
// Пример: получение настроек из CSS-переменных для каждого экземпляра
|
|
298
|
+
const settings = {
|
|
299
|
+
slideWidth: getComputedStyle(el).getPropertyValue('--slide-width'),
|
|
300
|
+
slideGap: getComputedStyle(el).getPropertyValue('--slide-gap'),
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
console.log(\`Инициализация виджета #\${index}\`, settings);
|
|
304
|
+
|
|
305
|
+
`;
|
|
306
|
+
if (hasSplide) {
|
|
307
|
+
js += ` // Инициализация Splide слайдера для этого экземпляра
|
|
308
|
+
const splideEl = $widgetInstance.find('.splide');
|
|
309
|
+
if (splideEl.length) {
|
|
310
|
+
new Splide(splideEl[0], {
|
|
311
|
+
type: 'loop',
|
|
312
|
+
perPage: 3,
|
|
313
|
+
perMove: 1,
|
|
314
|
+
gap: '1rem',
|
|
315
|
+
pagination: false,
|
|
316
|
+
breakpoints: {
|
|
317
|
+
768: {
|
|
318
|
+
perPage: 1,
|
|
319
|
+
},
|
|
320
|
+
1024: {
|
|
321
|
+
perPage: 2,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
}).mount();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
js += ` // Ваш код для каждого экземпляра виджета
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
`;
|
|
333
|
+
if (hasCommonjs && !cleanMode) {
|
|
334
|
+
js += `// Пример работы с EventBus (глобальные события, вне цикла)
|
|
335
|
+
EventBus.subscribe('cart:add', function(cart) {
|
|
336
|
+
console.log('Товар добавлен в корзину', cart);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Реакция на изменение настроек в редакторе
|
|
340
|
+
EventBus.subscribe([
|
|
341
|
+
'widget:input-setting:insales:system:editor',
|
|
342
|
+
'widget:change-setting:insales:system:editor'
|
|
343
|
+
], function(data) {
|
|
344
|
+
console.log('Настройка изменена:', data.setting_name, data.value);
|
|
345
|
+
// Здесь можно обновить виджет при изменении настроек
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
if (!cleanMode) {
|
|
353
|
+
js += `// Переменная widget уже доступна (создана системой)
|
|
354
|
+
// ВАЖНО: На странице может быть несколько виджетов одного типа
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
js += `const widgetElements = document.querySelectorAll(widget);
|
|
358
|
+
|
|
359
|
+
if (widgetElements.length === 0) return;
|
|
360
|
+
|
|
361
|
+
console.log('Виджет ${config.handle} инициализирован, найдено экземпляров:', widgetElements.length);
|
|
362
|
+
|
|
363
|
+
// Перебираем каждый экземпляр виджета на странице
|
|
364
|
+
widgetElements.forEach((el, index) => {
|
|
365
|
+
// Пример: получение настроек из CSS-переменных для каждого экземпляра
|
|
366
|
+
const styles = getComputedStyle(el);
|
|
367
|
+
const settings = {
|
|
368
|
+
slideWidth: styles.getPropertyValue('--slide-width'),
|
|
369
|
+
slideGap: styles.getPropertyValue('--slide-gap'),
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
console.log(\`Инициализация виджета #\${index}\`, settings);
|
|
373
|
+
|
|
374
|
+
`;
|
|
375
|
+
if (hasSplide) {
|
|
376
|
+
js += ` // Инициализация Splide слайдера для этого экземпляра
|
|
377
|
+
const splideEl = el.querySelector('.splide');
|
|
378
|
+
if (splideEl) {
|
|
379
|
+
new Splide(splideEl, {
|
|
380
|
+
type: 'loop',
|
|
381
|
+
perPage: 3,
|
|
382
|
+
perMove: 1,
|
|
383
|
+
gap: '1rem',
|
|
384
|
+
pagination: false,
|
|
385
|
+
breakpoints: {
|
|
386
|
+
768: {
|
|
387
|
+
perPage: 1,
|
|
388
|
+
},
|
|
389
|
+
1024: {
|
|
390
|
+
perPage: 2,
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
}).mount();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
`;
|
|
397
|
+
}
|
|
398
|
+
js += ` // Ваш код для каждого экземпляра виджета
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
`;
|
|
402
|
+
if (hasCommonjs && !cleanMode) {
|
|
403
|
+
js += `// Пример работы с EventBus (глобальные события, вне цикла)
|
|
404
|
+
if (typeof EventBus !== 'undefined') {
|
|
405
|
+
EventBus.subscribe('cart:add', function(cart) {
|
|
406
|
+
console.log('Товар добавлен в корзину', cart);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Реакция на изменение настроек в редакторе
|
|
410
|
+
EventBus.subscribe([
|
|
411
|
+
'widget:input-setting:insales:system:editor',
|
|
412
|
+
'widget:change-setting:insales:system:editor'
|
|
413
|
+
], function(data) {
|
|
414
|
+
console.log('Настройка изменена:', data.setting_name, data.value);
|
|
415
|
+
// Здесь можно обновить виджет при изменении настроек
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return js;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Генерирует setup.json для block_list_widget_type
|
|
426
|
+
*/
|
|
427
|
+
export function generateSetupJson(blockTemplate) {
|
|
428
|
+
// Создаем дефолтный блок с полями из block template
|
|
429
|
+
const createDefaultBlock = () => {
|
|
430
|
+
const block = {};
|
|
431
|
+
blockTemplate.fields.forEach(field => {
|
|
432
|
+
// Генерируем дефолтные значения в зависимости от типа поля
|
|
433
|
+
switch (field.kind) {
|
|
434
|
+
case 'text':
|
|
435
|
+
case 'link':
|
|
436
|
+
block[field.handle] = '';
|
|
437
|
+
break;
|
|
438
|
+
case 'textarea':
|
|
439
|
+
case 'html':
|
|
440
|
+
block[field.handle] = '';
|
|
441
|
+
break;
|
|
442
|
+
case 'account_file':
|
|
443
|
+
case 'image':
|
|
444
|
+
// Для изображений оставляем пустую строку или placeholder ID
|
|
445
|
+
block[field.handle] = '';
|
|
446
|
+
break;
|
|
447
|
+
case 'checkbox':
|
|
448
|
+
block[field.handle] = false;
|
|
449
|
+
break;
|
|
450
|
+
case 'number':
|
|
451
|
+
case 'range':
|
|
452
|
+
block[field.handle] = 0;
|
|
453
|
+
break;
|
|
454
|
+
case 'color':
|
|
455
|
+
block[field.handle] = '#000000';
|
|
456
|
+
break;
|
|
457
|
+
case 'collection':
|
|
458
|
+
case 'product':
|
|
459
|
+
case 'blog':
|
|
460
|
+
case 'article':
|
|
461
|
+
case 'page':
|
|
462
|
+
block[field.handle] = null;
|
|
463
|
+
break;
|
|
464
|
+
default:
|
|
465
|
+
block[field.handle] = '';
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
return block;
|
|
469
|
+
};
|
|
470
|
+
// Создаем 3-4 дефолтных блока для примера
|
|
471
|
+
const defaultBlocksCount = Math.min(4, Math.max(2, blockTemplate.fields.length > 5 ? 2 : 3));
|
|
472
|
+
const blocks = Array.from({ length: defaultBlocksCount }, () => createDefaultBlock());
|
|
473
|
+
return {
|
|
474
|
+
blocks
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Вспомогательная функция для генерации имени виджета
|
|
479
|
+
*/
|
|
480
|
+
function generateWidgetName(description) {
|
|
481
|
+
const words = description.split(' ').slice(0, 4);
|
|
482
|
+
return words.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=snippet-generator.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Типы и интерфейсы для генераторов виджетов
|
|
3
|
+
*/
|
|
4
|
+
import type { BlockTemplate } from '../types/index.js';
|
|
5
|
+
import type { BaseSetting as Setting } from '../types/settings.js';
|
|
6
|
+
export interface WidgetConfig {
|
|
7
|
+
handle: string;
|
|
8
|
+
category: string;
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
blockTemplate?: BlockTemplate;
|
|
12
|
+
libraries: string[];
|
|
13
|
+
commonSettings: Setting[];
|
|
14
|
+
}
|
|
15
|
+
export interface ValidationError {
|
|
16
|
+
filter: string;
|
|
17
|
+
line: number;
|
|
18
|
+
column: number;
|
|
19
|
+
suggestion: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ValidationResult {
|
|
22
|
+
isValid: boolean;
|
|
23
|
+
errors: ValidationError[];
|
|
24
|
+
}
|
|
25
|
+
export interface FixResult {
|
|
26
|
+
fixedCode: string;
|
|
27
|
+
fixes: Array<{
|
|
28
|
+
filter: string;
|
|
29
|
+
line: number;
|
|
30
|
+
suggestion: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
export interface CollectionsMenuResult {
|
|
34
|
+
liquidCode: string;
|
|
35
|
+
requiredSettings: any[];
|
|
36
|
+
cssPrefix: string;
|
|
37
|
+
dataAttributes: any[];
|
|
38
|
+
}
|
|
39
|
+
export interface SettingsFormResult {
|
|
40
|
+
form: Record<string, any[]>;
|
|
41
|
+
count: number;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидация Liquid кода
|
|
3
|
+
*/
|
|
4
|
+
import type { ValidationResult, FixResult } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Валидирует Liquid код на наличие несуществующих фильтров
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateLiquidFilters(liquidCode: string): ValidationResult;
|
|
9
|
+
/**
|
|
10
|
+
* Проверяет и исправляет несуществующие фильтры в Liquid коде
|
|
11
|
+
*/
|
|
12
|
+
export declare function fixInvalidFilters(liquidCode: string): FixResult;
|
|
13
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидация Liquid кода
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Валидирует Liquid код на наличие несуществующих фильтров
|
|
6
|
+
*/
|
|
7
|
+
export function validateLiquidFilters(liquidCode) {
|
|
8
|
+
const invalidFiltersList = [
|
|
9
|
+
{ name: 't', suggestion: 'Используйте messages или widget_messages для переводов' },
|
|
10
|
+
{ name: 'translate', suggestion: 'Используйте messages для переводов' },
|
|
11
|
+
{ name: 'i18n', suggestion: 'Используйте messages для переводов' },
|
|
12
|
+
{ name: 'l', suggestion: 'Используйте messages для переводов' },
|
|
13
|
+
{ name: 'localize', suggestion: 'Используйте messages для переводов' },
|
|
14
|
+
{ name: 'tr', suggestion: 'Используйте messages для переводов' }
|
|
15
|
+
];
|
|
16
|
+
const errors = [];
|
|
17
|
+
const lines = liquidCode.split('\n');
|
|
18
|
+
lines.forEach((line, lineIndex) => {
|
|
19
|
+
invalidFiltersList.forEach(invalidFilter => {
|
|
20
|
+
// Ищем паттерн | filter_name
|
|
21
|
+
const regex = new RegExp(`\\|\\s*${invalidFilter.name}\\b`, 'g');
|
|
22
|
+
let match;
|
|
23
|
+
while ((match = regex.exec(line)) !== null) {
|
|
24
|
+
errors.push({
|
|
25
|
+
filter: invalidFilter.name,
|
|
26
|
+
line: lineIndex + 1,
|
|
27
|
+
column: match.index + 1,
|
|
28
|
+
suggestion: invalidFilter.suggestion
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
isValid: errors.length === 0,
|
|
35
|
+
errors
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Проверяет и исправляет несуществующие фильтры в Liquid коде
|
|
40
|
+
*/
|
|
41
|
+
export function fixInvalidFilters(liquidCode) {
|
|
42
|
+
const fixes = [];
|
|
43
|
+
let fixedCode = liquidCode;
|
|
44
|
+
// Исправляем фильтр 't' на messages
|
|
45
|
+
const tRegex = /\|\s*t\b/g;
|
|
46
|
+
let match;
|
|
47
|
+
while ((match = tRegex.exec(fixedCode)) !== null) {
|
|
48
|
+
const beforeMatch = fixedCode.substring(0, match.index);
|
|
49
|
+
const afterMatch = fixedCode.substring(match.index + match[0].length);
|
|
50
|
+
// Определяем, что это за контекст
|
|
51
|
+
const contextBefore = beforeMatch.substring(Math.max(0, beforeMatch.length - 50));
|
|
52
|
+
if (contextBefore.includes('widget_messages')) {
|
|
53
|
+
// Если это в контексте widget_messages, оставляем как есть
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Заменяем на messages
|
|
58
|
+
fixedCode = beforeMatch + '| messages' + afterMatch;
|
|
59
|
+
fixes.push({
|
|
60
|
+
filter: 't',
|
|
61
|
+
line: fixedCode.substring(0, match.index).split('\n').length,
|
|
62
|
+
suggestion: 'Заменен на messages'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
fixedCode,
|
|
68
|
+
fixes
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=validation.js.map
|
package/dist/server-http.js
CHANGED
package/dist/server.d.ts
CHANGED