iwgt 2.4.10 → 2.4.12

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.
@@ -0,0 +1,647 @@
1
+ # Системная обёртка виджетов и контекст выполнения
2
+
3
+ ## Системная обёртка (layout wrapper)
4
+
5
+ **ВАЖНО:** Весь код из `snippet.liquid` автоматически оборачивается системой InSales в `div` с классом `layout` и уникальным классом виджета.
6
+
7
+ ### Структура обёртки
8
+
9
+ ```html
10
+ <div
11
+ class="layout widget-type_#{widget_handle}"
12
+ style="--heading-hide:false; --delete-borders:false; --align-title:left; ..."
13
+ data-widget-drop-item-id="209226033"
14
+ >
15
+ <!-- ВАШ КОД ИЗ snippet.liquid -->
16
+ </div>
17
+ ```
18
+
19
+ ### Шаблон класса виджета
20
+
21
+ ```
22
+ widget-type_#{widget_handle}
23
+ ```
24
+
25
+ Пример: для виджета с `handle: "system-widget-story"` класс будет `widget-type_system-widget-story`
26
+
27
+ ### CSS переменные из настроек
28
+
29
+ Система автоматически преобразует настройки виджета из `settings_form.json` в CSS-переменные в атрибуте `style`.
30
+
31
+ **Правило преобразования:**
32
+ - Название настройки становится CSS-переменной: `layout-wide-bg` → `--layout-wide-bg`
33
+ - Значение настройки становится значением CSS-переменной
34
+
35
+ **Пример:**
36
+
37
+ ```json
38
+ // settings_form.json
39
+ {
40
+ "handle": "layout-wide-bg",
41
+ "type": "checkbox",
42
+ "value": true
43
+ }
44
+ ```
45
+
46
+ Преобразуется в:
47
+ ```html
48
+ style="--layout-wide-bg:true"
49
+ ```
50
+
51
+ ### Полный пример
52
+
53
+ ```html
54
+ <div
55
+ class="layout widget-type_system-widget-story"
56
+ style="--heading-hide:false; --delete-borders:false; --align-title:left; --stories-rounding:false; --slide-width:200px; --slide-gap:30rem; --open-link:normal; --img-ratio:1; --banner-border-radius:0px; --slide-width-mobile:150px; --img-ratio-mobile:1; --layout-wide-bg:true; --layout-pt:2vw; --layout-pb:2vw; --layout-wide-content:false; --layout-edge:false; --hide-desktop:false; --hide-mobile:false;"
57
+ data-widget-drop-item-id="209226033"
58
+ >
59
+ <!-- html из snippet.liquid -->
60
+ </div>
61
+ ```
62
+
63
+ ## Контекст выполнения виджета
64
+
65
+ ### Основные переменные контекста
66
+
67
+ #### 1. `widget_settings` - настройки виджета
68
+
69
+ Доступ к значениям из `settings_form.json`:
70
+
71
+ ```liquid
72
+ {{ widget_settings.layout-wide-content }}
73
+ {{ widget_settings.title_text-default }}
74
+ {{ widget_settings.menu-handle }}
75
+ ```
76
+
77
+ #### 2. `data.blocks` - блоки виджета (только для BlockListWidgetType)
78
+
79
+ **ВАЖНО:** Для виджетов типа `BlockListWidgetType` блоки доступны через переменную `data.blocks`.
80
+
81
+ **Перебор блоков:**
82
+
83
+ ```liquid
84
+ {% for block in data.blocks %}
85
+ {{ block.name }}
86
+ {{ block.image }}
87
+ {{ block.link }}
88
+ {% endfor %}
89
+ ```
90
+
91
+ **Доступные поля блока** зависят от `block_template_handle`, указанного в `info.json`.
92
+
93
+ Например, для `system-banner-video`:
94
+ - `block.link` - ссылка
95
+ - `block.image` - заставка
96
+
97
+ #### 3. `widget_messages` - переводы текстов
98
+
99
+ Содержит переводы из файла `messages.json`:
100
+
101
+ ```liquid
102
+ {{ widget_messages.button_text }}
103
+ {{ widget_messages.empty_state }}
104
+ ```
105
+
106
+ #### 4. Доступ к сущностям магазина через настройки
107
+
108
+ Через настройки виджета можно получить доступ к различным сущностям магазина.
109
+
110
+ **Пример: вывод меню**
111
+
112
+ ```liquid
113
+ {% comment %}
114
+ В settings_form.json есть настройка:
115
+ {
116
+ "handle": "menu-handle",
117
+ "type": "menu",
118
+ "value": "main-menu"
119
+ }
120
+ {% endcomment %}
121
+
122
+ {% for link in linklists[widget_settings.menu-handle].links %}
123
+ <li class="header__menu-item" data-navigation-item data-menu-item-id="{{ link.id}}">
124
+ <div class="header__menu-controls">
125
+ <a href="{{ link.url }}" class="header__menu-link" data-navigation-link="{{ link.url }}">
126
+ {{ link.title }}
127
+ </a>
128
+ </div>
129
+ </li>
130
+ {% endfor %}
131
+ ```
132
+
133
+ **Пример: вывод категории**
134
+
135
+ ```liquid
136
+ {% comment %}
137
+ В settings_form.json есть настройка:
138
+ {
139
+ "handle": "collection-handle",
140
+ "type": "collection",
141
+ "value": null
142
+ }
143
+ {% endcomment %}
144
+
145
+ {% assign collection = collections[widget_settings.collection-handle] %}
146
+ {% if collection %}
147
+ <h2>{{ collection.title }}</h2>
148
+ {% for product in collection.products %}
149
+ {{ product.title }}
150
+ {% endfor %}
151
+ {% endif %}
152
+ ```
153
+
154
+ ### Глобальные переменные Liquid
155
+
156
+ В контексте виджета доступны все глобальные переменные InSales Liquid:
157
+
158
+ - `account` - данные магазина
159
+ - `template` - текущий шаблон страницы
160
+ - `language` / `languages` - локализация
161
+ - `product` - товар (на странице товара)
162
+ - `collection` - категория (на странице категории)
163
+ - `cart` - корзина
164
+ - `linklists` - все меню магазина
165
+ - `collections` - все категории магазина
166
+ - `settings` - глобальные настройки темы
167
+
168
+ Полный список в справочнике: `get_liquid_variables`
169
+
170
+ ## JavaScript виджета (snippet.js)
171
+
172
+ ### Системная обёртка snippet.js
173
+
174
+ **ВАЖНО:** Весь код из `snippet.js` автоматически оборачивается системой в `try/catch` блок с предопределёнными переменными.
175
+
176
+ **Система оборачивает ваш код так:**
177
+
178
+ ```js
179
+ try {
180
+ let widget = '.widget-type_your-widget-handle';
181
+ let $widget = $('.widget-type_your-widget-handle');
182
+
183
+ // ВАШ КОД ИЗ snippet.js
184
+
185
+ } catch(error) {
186
+ console.error('Widget "widget-type_your-widget-handle"', error)
187
+ }
188
+ ```
189
+
190
+ **Доступные переменные:**
191
+ - `widget` - строка с селектором виджета (`.widget-type_#{handle}`)
192
+ - `$widget` - jQuery объект виджета (если подключен jQuery)
193
+
194
+ **Ваш код в snippet.js:**
195
+
196
+ ```js
197
+ // НЕ нужно оборачивать в try/catch - это делается автоматически!
198
+ // НЕ нужно объявлять widget и $widget - они уже доступны!
199
+
200
+ // Можно сразу использовать переменные:
201
+ console.log('Виджет инициализирован:', widget);
202
+
203
+ if ($widget.length) {
204
+ // Работа с jQuery
205
+ $widget.find('.block').each(function() {
206
+ // ...
207
+ });
208
+ }
209
+
210
+ // Или с vanilla JS:
211
+ const widgetEl = document.querySelector(widget);
212
+ if (widgetEl) {
213
+ // ...
214
+ }
215
+ ```
216
+
217
+ ### EventBus - события визуального редактора
218
+
219
+ **EventBus** - это сервисный объект с событиями визуального редактора виджетов InSales.
220
+
221
+ **События изменения настроек виджета:**
222
+
223
+ ```js
224
+ EventBus.subscribe([
225
+ 'widget:input-setting:insales:system:editor',
226
+ 'widget:change-setting:insales:system:editor',
227
+ 'widget:input-color:insales:system:editor'
228
+ ], (data) => {
229
+ console.log('Настройка изменена:', data);
230
+ });
231
+ ```
232
+
233
+ **Структура объекта data:**
234
+
235
+ ```js
236
+ {
237
+ widget_id: 87289537, // ID виджета
238
+ widget_item_id: 209226033, // ID экземпляра виджета
239
+ setting_name: "align-title", // Название настройки
240
+ value: "center", // Новое значение
241
+ unit: "px", // Единица измерения (для числовых)
242
+ type: "icon_group" // Тип настройки
243
+ }
244
+ ```
245
+
246
+ **Примеры событий:**
247
+
248
+ ```js
249
+ // Изменение выравнивания
250
+ {
251
+ setting_name: "align-title",
252
+ type: "icon_group",
253
+ unit: undefined,
254
+ value: "center",
255
+ widget_id: 87289537,
256
+ widget_item_id: 209226033
257
+ }
258
+
259
+ // Изменение цвета с оттенками
260
+ {
261
+ widget_id: 87289537,
262
+ widget_item_id: 209226033,
263
+ setting_name: "bg-details-stories-color",
264
+ value: "#338072",
265
+ type: "color",
266
+ shades: [
267
+ {name: "minor", light: -3, dark: 10},
268
+ {name: "major", light: -7, dark: 20},
269
+ {name: "half", light: -50, dark: 50}
270
+ ]
271
+ }
272
+ ```
273
+
274
+ **Типы настроек (type):**
275
+ - `color` - цветовая настройка
276
+ - `text` - текстовое поле
277
+ - `number` - числовое поле
278
+ - `select` - выпадающий список
279
+ - `fonts_select` - выбор шрифта
280
+ - `icon_group` - группа иконок
281
+ - и другие...
282
+
283
+ **Пример использования EventBus:**
284
+
285
+ ```js
286
+ // Обновление стилей при изменении настроек в редакторе
287
+ EventBus.subscribe([
288
+ 'widget:input-setting:insales:system:editor',
289
+ 'widget:change-setting:insales:system:editor'
290
+ ], (data) => {
291
+ if (data.setting_name === 'slide-gap') {
292
+ // Обновляем слайдер с новым значением gap
293
+ const widgetEl = document.querySelector(widget);
294
+ if (widgetEl && window.Splide) {
295
+ const splide = widgetEl.querySelector('.splide');
296
+ // Переинициализация слайдера
297
+ }
298
+ }
299
+ });
300
+ ```
301
+
302
+ ## Архитектура виджетов InSales (важно!)
303
+
304
+ ### НЕ ИСПОЛЬЗУЮТСЯ:
305
+
306
+ ❌ **Кастомные теги** - нестандартные HTML-теги являются ОШИБКОЙ
307
+ ❌ **React, Angular** и другие фреймворки
308
+
309
+ ### ИСПОЛЬЗУЮТСЯ:
310
+
311
+ ✅ **Liquid** - шаблонизатор для разметки (snippet.liquid)
312
+ ✅ **Vanilla JavaScript** - для логики виджета (snippet.js)
313
+ ✅ **CommonJS API** - для работы с магазином (Cart, Products, EventBus и т.д.)
314
+ ✅ **Библиотеки** - указанные в `info.json` в поле `libraries` (jquery, splide, swiper и т.д.)
315
+
316
+ ### Правильная структура HTML
317
+
318
+ ```liquid
319
+ <div class="widget-container">
320
+ {% if widget_settings.show-heading %}
321
+ <h2 class="widget-heading">{{ widget_settings.heading-text }}</h2>
322
+ {% endif %}
323
+
324
+ {% if data.blocks %}
325
+ <div class="widget-blocks">
326
+ {% for block in data.blocks %}
327
+ <div class="widget-block">
328
+ {% if block.image %}
329
+ <img src="{{ block.image | img_url: '800x' }}" alt="{{ block.name }}">
330
+ {% endif %}
331
+ {% if block.name %}
332
+ <h3>{{ block.name }}</h3>
333
+ {% endif %}
334
+ {% if block.link %}
335
+ <a href="{{ block.link }}" class="widget-link">Подробнее</a>
336
+ {% endif %}
337
+ </div>
338
+ {% endfor %}
339
+ </div>
340
+ {% endif %}
341
+ </div>
342
+ ```
343
+
344
+ ## Debugging
345
+
346
+ ### Вывод доступных переменных
347
+
348
+ ```liquid
349
+ {% help %}
350
+ ```
351
+
352
+ ### Вывод свойств объекта
353
+
354
+ ```liquid
355
+ {% help account %}
356
+ {% help product %}
357
+ {% help widget_settings %}
358
+ ```
359
+
360
+ ### Проверка значения переменной
361
+
362
+ ```liquid
363
+ {{ widget_settings | json }}
364
+ {{ data.blocks | json }}
365
+ ```
366
+
367
+ ## Стили виджета (snippet.scss)
368
+
369
+ ### Структура SCSS файла
370
+
371
+ **ВАЖНО:** Весь код в `snippet.scss` автоматически оборачивается в класс виджета.
372
+
373
+ Система компилирует ваш SCSS так:
374
+ ```scss
375
+ // Ваш код в snippet.scss:
376
+ & {
377
+ padding: 2rem;
378
+ }
379
+
380
+ // Компилируется в:
381
+ .layout.widget-type_your-widget {
382
+ padding: 2rem;
383
+ }
384
+ ```
385
+
386
+ ### Использование & (амперсанд)
387
+
388
+ Символ `&` обращается к родительскому классу (`.layout.widget-type_#{handle}`):
389
+
390
+ ```scss
391
+ & {
392
+ // Стили для родительского элемента
393
+ border-bottom: 1px solid var(--bg-minor-shade);
394
+ box-shadow: 0px 10px 20px -10px rgba(0,0,0,0.1);
395
+ position: relative;
396
+ }
397
+
398
+ // Состояния на основе CSS-переменных из настроек
399
+ &[style*="--article-hide-photo:true"] {
400
+ .article-photo {
401
+ display: none!important;
402
+ }
403
+ }
404
+
405
+ &[style*="--hide-favorites:true"][style*="--hide-personal:false"] {
406
+ .navigation-bar {
407
+ grid-template-columns: repeat(4, 1fr);
408
+ }
409
+ }
410
+ ```
411
+
412
+ ### Работа с CSS-переменными
413
+
414
+ Настройки виджета автоматически становятся CSS-переменными:
415
+
416
+ ```scss
417
+ & {
418
+ // Используем переменные из настроек
419
+ padding-top: var(--layout-pt, 2rem);
420
+ padding-bottom: var(--layout-pb, 2rem);
421
+
422
+ // Переопределяем переменные
423
+ --grid-list-min-width: 190px;
424
+ --grid-list-row-gap: 1rem;
425
+ --grid-list-column-gap: 2rem;
426
+
427
+ @media screen and (max-width: 767px) {
428
+ --grid-list-min-width: 50%;
429
+ }
430
+ }
431
+ ```
432
+
433
+ ### Глобальный миксин background-color
434
+
435
+ **ВАЖНО:** В системе InSales глобально доступен миксин для работы с цветами фона, который автоматически управляет цветом текста в зависимости от яркости фона.
436
+
437
+ ```scss
438
+ @mixin background-color($color) {
439
+ $dark_selector: '[style*="#{$color}-is-dark:true"]';
440
+ $light_selector: '[style*="#{$color}-is-light:true"]';
441
+ background-color: var(#{$color});
442
+ @at-root #{selector-append($dark_selector, &)} {
443
+ color: var(--color-text-light);
444
+ --color-text: var(--color-text-light);
445
+ --color-text-minor-shade: var(--color-text-light-minor-shade);
446
+ --color-text-major-shade: var(--color-text-light-major-shade);
447
+ --color-text-half-shade: var(--color-text-light-half-shade);
448
+ }
449
+ @at-root #{selector-append($light_selector, &)} {
450
+ color: var(--color-text-dark);
451
+ --color-text: var(--color-text-dark);
452
+ --color-text-minor-shade: var(--color-text-dark-minor-shade);
453
+ --color-text-major-shade: var(--color-text-dark-major-shade);
454
+ --color-text-half-shade: var(--color-text-dark-half-shade);
455
+ }
456
+ }
457
+ ```
458
+
459
+ **Использование миксина:**
460
+
461
+ ```scss
462
+ .widget-block {
463
+ // Применяем фоновый цвет с автоматической адаптацией текста
464
+ @include background-color(--bg-color);
465
+
466
+ // Система автоматически добавит переменные:
467
+ // --bg-color-is-dark:true или --bg-color-is-light:true
468
+ // в атрибут style родительского элемента
469
+ }
470
+ ```
471
+
472
+ **Как это работает:**
473
+ 1. Настройка цвета в `settings_form.json` типа `color` создаёт CSS-переменную
474
+ 2. Система анализирует яркость цвета и добавляет `--название-цвета-is-dark:true` или `--название-цвета-is-light:true`
475
+ 3. Миксин автоматически применяет правильный цвет текста (светлый для тёмного фона, тёмный для светлого)
476
+
477
+ **Доступные оттенки текста:**
478
+ - `--color-text` - основной цвет текста
479
+ - `--color-text-minor-shade` - лёгкий оттенок
480
+ - `--color-text-major-shade` - сильный оттенок
481
+ - `--color-text-half-shade` - средний оттенок
482
+
483
+ ### Полный пример SCSS
484
+
485
+ ```scss
486
+ & {
487
+ // Основные стили
488
+ padding-top: var(--layout-pt, 2rem);
489
+ padding-bottom: var(--layout-pb, 2rem);
490
+ position: relative;
491
+
492
+ // Используем миксин для фонового цвета
493
+ @include background-color(--bg-widget-color);
494
+ }
495
+
496
+ // Широкий фон
497
+ &[style*="--layout-wide-bg:true"] {
498
+ width: 100vw;
499
+ margin-left: calc(50% - 50vw);
500
+ }
501
+
502
+ // Скрытие на устройствах
503
+ &[style*="--hide-mobile:true"] {
504
+ @media (max-width: 767px) {
505
+ display: none;
506
+ }
507
+ }
508
+
509
+ // Вложенные элементы
510
+ .widget-container {
511
+ max-width: var(--layout-content-max-width, 1200px);
512
+ margin: 0 auto;
513
+ }
514
+
515
+ .widget-block {
516
+ @include background-color(--bg-block-color);
517
+
518
+ img {
519
+ width: 100%;
520
+ height: auto;
521
+ }
522
+
523
+ &:hover {
524
+ transform: scale(1.02);
525
+ }
526
+ }
527
+ ```
528
+
529
+ ## Типичные ошибки
530
+
531
+ ### ❌ НЕПРАВИЛЬНО (Liquid):
532
+
533
+ ```liquid
534
+ <!-- НЕ ИСПОЛЬЗУЙТЕ кастомные теги -->
535
+ <custom-tag>
536
+ <another-custom-tag>
537
+ Контент
538
+ </another-custom-tag>
539
+ </custom-tag>
540
+
541
+ <!-- НЕ добавляйте обёртку layout вручную -->
542
+ <div class="layout">
543
+ <!-- код виджета -->
544
+ </div>
545
+ ```
546
+
547
+ ### ❌ НЕПРАВИЛЬНО (SCSS):
548
+
549
+ ```scss
550
+ // НЕ добавляйте класс виджета вручную
551
+ .layout.widget-type_my-widget {
552
+ padding: 2rem;
553
+ }
554
+
555
+ // НЕ используйте :root или html для переменных виджета
556
+ :root {
557
+ --my-variable: 10px;
558
+ }
559
+ ```
560
+
561
+ ### ✅ ПРАВИЛЬНО (Liquid):
562
+
563
+ ```liquid
564
+ <!-- Используйте обычные HTML-теги -->
565
+ <div class="grid">
566
+ <div class="grid-cell">
567
+ Контент
568
+ </div>
569
+ </div>
570
+
571
+ <!-- Обёртка layout добавится автоматически -->
572
+ <div class="widget-content">
573
+ <!-- ваш код -->
574
+ </div>
575
+ ```
576
+
577
+ ### ✅ ПРАВИЛЬНО (SCSS):
578
+
579
+ ```scss
580
+ // Используйте & для родительского элемента
581
+ & {
582
+ padding: 2rem;
583
+
584
+ // Определяйте переменные здесь
585
+ --my-variable: 10px;
586
+ }
587
+
588
+ // Селекторы элементов без &
589
+ .widget-content {
590
+ margin: 1rem 0;
591
+ }
592
+
593
+ // Состояния с &
594
+ &[style*="--some-setting:true"] {
595
+ .element {
596
+ display: none;
597
+ }
598
+ }
599
+ ```
600
+
601
+ ## Работа с блоками
602
+
603
+ ### Проверка наличия блоков
604
+
605
+ ```liquid
606
+ {% if data.blocks and data.blocks.size > 0 %}
607
+ <!-- вывод блоков -->
608
+ {% else %}
609
+ <!-- заглушка или пустое состояние -->
610
+ <div class="widget-empty">
611
+ {{ widget_messages.no_blocks_message }}
612
+ </div>
613
+ {% endif %}
614
+ ```
615
+
616
+ ### Перебор блоков с индексом
617
+
618
+ ```liquid
619
+ {% assign slide_index = 0 %}
620
+ {% for block in data.blocks %}
621
+ <div class="slide" data-slide-index="{{ slide_index }}">
622
+ {{ block.name }}
623
+ </div>
624
+ {% assign slide_index = slide_index | plus: 1 %}
625
+ {% endfor %}
626
+ ```
627
+
628
+ ### Доступ к полям блока
629
+
630
+ Поля блока зависят от `block_template_handle`. Например:
631
+
632
+ **system-banner-2:**
633
+ - `block.name` - название (текст)
634
+ - `block.image` - изображение (файл)
635
+ - `block.link` - ссылка (текст)
636
+
637
+ **system-banner-video:**
638
+ - `block.link` - ссылка на видео (текст)
639
+ - `block.image` - заставка видео (файл)
640
+
641
+ **system-benefit-2:**
642
+ - `block.name` - заголовок (текст)
643
+ - `block.description` - описание (HTML)
644
+ - `block.image` - изображение (файл)
645
+
646
+ Полный список шаблонов блоков и их полей: `get_block_templates`
647
+
@@ -259,6 +259,11 @@ export function generateSettingsForm(commonSettings) {
259
259
  if (setting.clearable)
260
260
  item.clearable = setting.clearable;
261
261
  }
262
+ else if (setting.type === 'file') {
263
+ item.value = setting.value || null;
264
+ if (setting['with-generate-logo'])
265
+ item['with-generate-logo'] = setting['with-generate-logo'];
266
+ }
262
267
  // Общие свойства
263
268
  if (setting.help)
264
269
  item.help = setting.help;
@@ -57,7 +57,7 @@ const httpServer = createServer(async (req, res) => {
57
57
  res.end(JSON.stringify({
58
58
  status: 'ok',
59
59
  service: 'InSales Widgets MCP Server',
60
- version: '2.4.10',
60
+ version: '2.4.12',
61
61
  transport: 'SSE',
62
62
  endpoints: {
63
63
  sse: '/sse',