@textback/notification-widget 2.0.0 → 2.0.1-102913

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. package/.eslintrc.js +291 -291
  2. package/build/index.js +12 -0
  3. package/build/sdk.js +9 -0
  4. package/package.json +70 -68
  5. package/promote_tag.sh +1 -1
  6. package/readme.md +569 -490
  7. package/server.js +8 -4
  8. package/src/libraries/ai.1.0.11.js +4088 -4088
  9. package/src/libraries/localization/locales/cs.js +14 -12
  10. package/src/libraries/localization/locales/en.js +14 -12
  11. package/src/libraries/localization/locales/index.js +8 -8
  12. package/src/libraries/localization/locales/pl.js +14 -12
  13. package/src/libraries/localization/locales/ro.js +14 -12
  14. package/src/libraries/localization/locales/ru.js +13 -12
  15. package/src/libraries/localization/locales/uk.js +14 -12
  16. package/src/libraries/localization/text.js +9 -9
  17. package/src/libraries/t.js +82 -82
  18. package/src/sdk/channels/channel.js +30 -30
  19. package/src/sdk/channels/facebook.js +13 -13
  20. package/src/sdk/channels/factory.js +3 -3
  21. package/src/sdk/channels/skype.js +12 -12
  22. package/src/sdk/channels/telegram.js +18 -18
  23. package/src/sdk/channels/viber.js +12 -12
  24. package/src/sdk/channels/vk-modal/vk-modal.html +17 -17
  25. package/src/sdk/channels/vk-modal/vk-modal.js +25 -25
  26. package/src/sdk/channels/vk-modal/vk-modal.scss +116 -116
  27. package/src/sdk/channels/vk.js +195 -184
  28. package/src/sdk/channels/whatsapp.js +16 -10
  29. package/src/sdk/channels/whatsappb.js +27 -0
  30. package/src/sdk/events/observer.js +46 -46
  31. package/src/sdk/index.js +5 -5
  32. package/src/sdk/sdk.js +67 -30
  33. package/src/sdk/utils/apiErrorHandler.js +11 -11
  34. package/src/sdk/utils/appInsights.js +88 -88
  35. package/src/sdk/utils/browserInfo.js +8 -8
  36. package/src/sdk/utils/constants.js +17 -17
  37. package/src/sdk/utils/cookies.js +67 -50
  38. package/src/sdk/utils/find.js +7 -7
  39. package/src/sdk/utils/loadConfig.js +20 -20
  40. package/src/sdk/utils/loadDeepLink.js +48 -21
  41. package/src/sdk/utils/loadScript.js +25 -25
  42. package/src/sdk/utils/loadSubscriptions.js +6 -6
  43. package/src/sdk/utils/parseQueryString.js +33 -33
  44. package/src/sdk/utils/windowHelper.js +25 -25
  45. package/src/sdk/widget/widget.js +192 -140
  46. package/src/widget/components/index.js +6 -2
  47. package/src/widget/components/tb-notification-button/facebook.js +9 -2
  48. package/src/widget/components/tb-notification-button/index.js +34 -34
  49. package/src/widget/components/tb-notification-button/styles.scss +657 -433
  50. package/src/widget/components/tb-notification-button/telegram.js +9 -2
  51. package/src/widget/components/tb-notification-button/viber.js +9 -2
  52. package/src/widget/components/tb-notification-button/vk.js +59 -50
  53. package/src/widget/components/tb-notification-button/whatsapp.js +15 -8
  54. package/src/widget/components/tb-notification-button/whatsappb.js +58 -0
  55. package/src/widget/components/tb-notification-widget/index.js +589 -384
  56. package/src/widget/components/tb-notification-widget/normalize.scss +395 -394
  57. package/src/widget/components/tb-notification-widget/styles.scss +502 -139
  58. package/src/widget/components/tb-nw-wahunter/index.js +259 -0
  59. package/src/widget/components/tb-nw-wahunter/styles.scss +471 -0
  60. package/src/widget/config.js +5 -5
  61. package/src/widget/icons/icon_chat_window.svg +1 -0
  62. package/src/widget/icons/icon_close.svg +1 -0
  63. package/src/widget/icons/icon_facebook.svg +7 -7
  64. package/src/widget/icons/icon_facebook_circle.svg +7 -9
  65. package/src/widget/icons/icon_instagram_circle.svg +95 -95
  66. package/src/widget/icons/icon_skype.svg +44 -44
  67. package/src/widget/icons/icon_skype_circle.svg +46 -46
  68. package/src/widget/icons/icon_skype_new.svg +113 -113
  69. package/src/widget/icons/icon_tg.svg +25 -25
  70. package/src/widget/icons/icon_tg_circle.svg +17 -27
  71. package/src/widget/icons/icon_viber.svg +75 -75
  72. package/src/widget/icons/icon_viber_circle.svg +67 -77
  73. package/src/widget/icons/icon_viber_new.svg +102 -102
  74. package/src/widget/icons/icon_vk.svg +14 -14
  75. package/src/widget/icons/icon_vk_circle.svg +16 -16
  76. package/src/widget/icons/icon_whatsapp.svg +147 -147
  77. package/src/widget/icons/icon_whatsapp_circle.svg +3 -3
  78. package/src/widget/icons/icon_whatsapp_hollow.svg +128 -0
  79. package/src/widget/icons/icon_whatsapp_new.svg +127 -127
  80. package/src/widget/icons/icon_whatsappb.svg +147 -0
  81. package/src/widget/icons/icon_whatsappb_circle.svg +4 -0
  82. package/src/widget/icons/icon_whatsappb_new.svg +127 -0
  83. package/src/widget/icons/paper-plane-arrow.svg +3 -0
  84. package/src/widget/icons/tb-logo.svg +21 -0
  85. package/src/widget/index.js +28 -28
  86. package/src/widget/locales/cs.js +42 -0
  87. package/src/widget/locales/en.js +42 -20
  88. package/src/widget/locales/index.js +2 -2
  89. package/src/widget/locales/pl.js +41 -19
  90. package/src/widget/locales/ro.js +41 -20
  91. package/src/widget/locales/ru.js +40 -19
  92. package/src/widget/locales/uk.js +40 -19
  93. package/src/widget/utils/cookiesEx.js +41 -41
  94. package/src/widget/utils/getLocale.js +4 -2
  95. package/src/widget/utils/stringifyAttributes.js +19 -19
  96. package/src/widget/utils/text.js +2 -1
  97. package/src/widget/utils/widgetsStorage.js +28 -28
  98. package/src/widget/widget.entry.js +3 -4
  99. package/tests/gf.html +35 -35
  100. package/tests/gf.js +21 -21
  101. package/tests/index.js +61 -61
  102. package/views/examples.ejs +7 -3
  103. package/views/sdk.html +274 -256
  104. package/webpack.common.js +72 -72
  105. package/webpack.dev.js +15 -15
  106. package/webpack.prod.js +10 -10
  107. package/src/widget/components/tb-notification-button/skype.js +0 -47
  108. package/src/widget/icons/text-back-badge.png +0 -0
  109. package/src/widget/locales/cz.js +0 -20
package/readme.md CHANGED
@@ -1,490 +1,569 @@
1
- # Установка виджета на сайт
2
-
3
- Для установки виджета подписки на сайт, сначала добавьте к вашей странице:
4
-
5
- ```
6
- <script src="//unpkg.com/@textback/notification-widget@latest/build/index.js"></script>
7
- ```
8
- **Внимание** Виджет больше не требует подключения дополнительного скрипта с полифиллом.
9
-
10
- Для корректного отображение виджета на адаптивных сайтах на мобильных требуется добавить следующий мета-тег в секцию `<head>`:
11
- ```html
12
- <meta name="viewport" content="width=device-width, initial-scale=1.0>
13
- ```
14
-
15
- Затем вы можете вставить один или несколько виджетов:
16
-
17
- ```
18
- <tb-notification-widget widget-id="YOUR_WIDGET_ID"></tb-notification-widget>
19
- ```
20
-
21
- с необходимыми widget-id. YOUR_WIDGET_ID указан в консоли администратора Textback.
22
-
23
- Если вы планируете использовать виджет в режиме "попап", то встраивайте виджет непосредственно внутри тега body, лучше сразу перед `</body>` шаблона страницы, на которую добавляется виджет.
24
-
25
- Если виджет будет использоваться в режиме "инлайн", то добавте виджет в том месте шаблона, в котором он должен отобразиться.
26
-
27
- Дополнительные параметры можно передать через data-атрибуты:
28
-
29
- ```
30
- <tb-notification-widget
31
- widget-id="YOUR_WIDGET_ID"
32
- data-user-id="USER_ID"
33
- data-order-id="ORDER_ID">
34
- </tb-notification-widget>
35
- ```
36
-
37
- Чтобы передать secureContext, нужно использовать атрибут `secure-context-token`:
38
- ```html
39
- <tb-notification-widget
40
- widget-id="YOUR_WIDGET_ID"
41
- secure-context-token="YOUR_TOKEN"
42
- </tb-notification-widget>
43
- ```
44
-
45
- ## Динамическая инициализация виджета
46
-
47
- Если вам необходимо динамически проинициализировать виджет, например во всплывающем окне или при разработке Single Page Application
48
- вы можете использовать класс, который доступен стразу после загрузки скрипта виджета
49
-
50
- ```
51
- var widgetContainer = document.querySelector('tb-notification-widget');
52
-
53
- var options = {
54
- widgetId: "YOUR_WIDGET_ID",
55
- element: widgetContainer,
56
- data: {userId: "USER_ID", orderId: "ORDER_ID"}
57
- };
58
-
59
- new TextBack.NotificationWidget(options)
60
- ```
61
-
62
- где widgetContainer - корневой HTML элемент, в котором необходимо отобразить виджет, data - дополнительные параметры.
63
-
64
- Чтобы изменить параметры после инициализации виджета повторно проинициализируйте виджет на элементе с новыми параметрами:
65
-
66
- ```
67
- options.data.orderId = "NEW_ORDER_ID";
68
-
69
- new TextBack.NotificationWidget(options)
70
- ```
71
-
72
- ### Мгновенно отобразить popup-виджет
73
- По умолчанию при инициализации виджета используются параметры тайминга из настроек - через сколько секунд отобразить виджет, сколько раз за сессию и т.д.
74
-
75
- Если требуется отобразить виджет сразу, то можно воспользоваться разработанным для этого API.
76
-
77
- Сначала нужно добавить виджет на страницу, указав атрибут ```only-manual```
78
- ```html
79
- <tb-notification-widget widget-id="YOUR_WIDGET_ID" only-manual></tb-notification-widget>
80
- ```
81
- Добавленный таким образом виджет будет скрыт и не будет показан автоматически.
82
-
83
- Чтобы теперь отобразить виджет, нужно выполнить следующий код:
84
- ```javascript
85
- TextBack.NotificationWidget.getWidget('YOUR_WIDGET_ID').then(function(widget) {
86
- widget.show();
87
- });
88
- ```
89
-
90
- Пример отображения виджета при нажатии на кнопку:
91
- ```javascript
92
- document.querySelector('#YOUR_BUTTON_ID').addEventListener('click', function() {
93
- TextBack.NotificationWidget.getWidget('YOUR_WIDGET_ID').then(function(widget) {
94
- widget.show();
95
- });
96
- });
97
- ```
98
-
99
- ### Предпросмотр виджета
100
- Релизован механизм, позволяющий задать JSON виджета динамически, не
101
- подгружая его по ID с нашего **API**. Для того, чтобы подписки работали
102
- в предпросомтре, необходимо, чтобы виджет был сохранен в БАЗУ
103
- и имел присвоенный ID.
104
-
105
- ---
106
- Этот механизм внедрян для добавления функционала пред-просмотра виджета
107
- в нашем редакторе, но ижет быть использован сторонними разработчикми,
108
- для динамической генерации сообщения приветствия, однака надо
109
- иметь в виду, что **ЭТО апи не финальное, и может изменится в любой момент**,
110
- используйте на свой страх и риск.
111
-
112
- ---
113
-
114
- Использование:
115
-
116
- ```javascript
117
- //получаем копию настроек виджета
118
- var data = angular.copy(notificationWidgetJSON);
119
-
120
-
121
- //override settings for preview purpose
122
- //переопределяем некторыесвойства, сохраненные на сервере
123
- //отображатся в виджете будет нвое значение.
124
- data.displayOptions.onLeave = null;
125
- data.displayOptions.timeoutDelay = 0;
126
- data.displayOptions.onTimeout = "yes";
127
-
128
- //к примеру vkApiId привязан к домену, на котром отображается виджет
129
- data.vkApiId=TextBack.configuration.get('vkAppId');
130
-
131
-
132
-
133
- //динамически создаем контейнер для виджета
134
- var widgetContainerJq = $('<div></div>');
135
- $('body').append(widgetContainerJq);
136
-
137
- var widgetContainer = widgetContainerJq[0];
138
-
139
-
140
- var options = {
141
- apiPath: 'https://api.textback.io/api', //необязатльный параметр
142
- element: widgetContainer,
143
- widgetConfig: data
144
- };
145
-
146
- new TextBack.NotificationWidget(options)
147
- ```
148
-
149
- # Локализация
150
-
151
- Все поддерживаемые языки находятся в переменной `TextBack.NotificationWidget.locales`. Чтобы применить нужный язык к виджету
152
- необходимо передать соответсвующий параметр `lang`:
153
-
154
- ```
155
- <tb-notification-widget
156
- widget-id="YOUR_WIDGET_ID"
157
- lang="ru">
158
- </tb-notification-widget>
159
- ```
160
-
161
- или
162
-
163
- ```
164
- var widgetContainer = document.querySelector('tb-notification-widget');
165
-
166
- var options = {
167
- widgetId: "YOUR_WIDGET_ID",
168
- element: widgetContainer,
169
- lang: 'ru'
170
- };
171
-
172
- new TextBack.NotificationWidget(options)
173
- ```
174
-
175
- Чтобы добавить или изменить существующий перевод достаточно изменить `TextBack.NotificationWidget.locales`:
176
-
177
- ```
178
- TextBack.NotificationWidget.locales.ru['vkontakte'] = 'ВКонтакте';
179
- TextBack.NotificationWidget.locales.newLang['vkontakte'] = 'ВК';
180
- ```
181
-
182
- # TextBack Widget SDK
183
- TextBack Widget SDK предоставляет набор функций для управления подпиской.
184
-
185
- SDK входит в состав виджета, но может быть подключен отдельно(см. ниже).
186
-
187
- **Преимущества SDK, в сравнении с виджетом подписок:**
188
- - SDK позволяет полностью настраивать внешний вид вашего UI, предоставляя только функции подписки пользователей.
189
- - Файл с SDK имеет меньший размер, чем файл с виджетом подписок.
190
-
191
- ## Как использовать SDK
192
-
193
- ### Подключение
194
- Добавить в код страницы
195
- ```
196
- <script src="//unpkg.com/@textback/notification-widget@latest/build/sdk.js"></script>
197
- ```
198
-
199
- После этого на странице будет доступен объект ```TextBack.SDK```
200
-
201
- ### Использование
202
- Приведенный ниже код будет подписывать пользователя в Telegram при клике на ссылку с `id="MY_TG_BUTTON"`:
203
-
204
- ```javascript
205
- var config = {
206
- widgetId: 'YOUR_WIDGET_ID'
207
- };
208
-
209
- TextBack.SDK.initWidget(config).then(
210
- function(widget) {
211
- document.getElementById('MY_TG_BUTTON').addEventListener('click', function(event) {
212
- event.preventDefault();
213
- widget.subscribe('tg');
214
- })
215
- }
216
- );
217
- ```
218
- **ВАЖНО!** Элемент с `id="MY_TG_BUTTON"` должен присутствовать на странице во время исполнения скрипта.
219
-
220
- Более подробное описание функции `initWidget()`, состава полей объекта конфигурации и других функций SDK смотрите ниже.
221
-
222
- ## SDK API
223
- **Примечание.** В документации по SDK под "виджетом" подразумевается не "виджет подписок", упоминавшийся ранее, а объект, который содержит настройки и методы виджета. Таким образом "**инициализация виджета**" - это не отрисовка виджета на странице, а **загрузка настроек с сервера и подготовка к работе**.
224
-
225
- Иными словами: "виджет" = "объект в приложении", "виджет подписок" = "объект в JavaScript" + "UI".
226
-
227
- ### Глобальное пространство имен
228
- SDK и виджет подписок доступны как свойства `SDK` и `NotificationWidget` глобального объекта `TextBack` соответственно.
229
-
230
- Для своей работы SDK использует полифиллы для метода `window.fetch()` и объекта ES6 `Promise`.
231
-
232
- Также, для работы с каналами VKontakte, используется VK JS API, представленное глобальным объектом VK.
233
-
234
- ### TextBack.SDK API
235
- Объект `TextBack.SDK` предоставляет API для инициализации виджетов и работы с ними.
236
-
237
- #### Метод `initWidget(config)`
238
- Инициализирует новый виджет по заданному объекту конфигурации.
239
-
240
- **Принимает**
241
- Объект `config` со следующими полями:
242
- - widgetId - (обязательный) идентификатор виджета. Если виджет с таким идентификатором уже есть, то виджет будет перезагружен;
243
- - secureContextToken - строка;
244
- - insecureContext - объект;
245
- - apiPath - адрес сервера;
246
- - overrideConfig - объект с настройками виджета. Если задан, то настройки виджета не будут загружаться с сервера, и для инициализации виджета будет использован данный объект;
247
- - customData - произвольные пользовательские данные.
248
-
249
- **Возвращает** Promise, который будет fulfilled объектом виджета.
250
-
251
- ##### Пример использования
252
- ```javascript
253
- var config = {
254
- widgetId: 'YOUR_WIDGET_ID',
255
- insecureContext: {
256
- data: 'data'
257
- }
258
- };
259
-
260
- TextBack.SDK.initWidget(config).then(
261
- function(widget) {
262
- console.log('Widget has been initialized.');
263
- }
264
- );
265
- ```
266
-
267
-
268
- #### Метод `getWidget(widgetId)`
269
- **Принимает** `widgetId` - идентификатор виджета.
270
- **Возвращает** Promise, возвращаемый функцией `initWidget()`, соответствующий переданному идентификатору.
271
-
272
- ##### Пример использования
273
- ```javascript
274
- TextBack.SDK.getWidget('YOUR_WIDGET_ID').then(
275
- function(widget) {
276
- console.log('Widget has id = ' + widget.id);
277
- }
278
- )
279
-
280
- ```
281
-
282
- #### Метод `on(eventName, callback)`
283
- Вешает обработчик `callback` на событие `eventName`. При наступлении события в `callback` первым аргументом будет передан объект события. Описание полей объекта события для различных событий см. ниже.
284
-
285
- **Поддерживаемые события**
286
- - 'widget.init' - успеная инициализация виджета. поля события:
287
- - widgetId - идентификатор виджета
288
- - 'subscribe.start' - вызвана функция подписки на канал. поля события:
289
- - widgetId - идентификатор виджета
290
- - channel - объект канала, на который осуществляется подписка
291
-
292
-
293
- **Возвращает**
294
- Функцию отписки обработчика `callback` от события.
295
-
296
- ##### Пример использования
297
- При инициализации первого виджета вывести в консоль сообщение.
298
- ```javascript
299
- const config = {
300
- widgetId: 'YOUR_WIDGET_ID'
301
- };
302
-
303
- TextBack.SDK.initWidget(config);
304
- const off = TextBack.SDK.on('widget.init', function(event) {
305
- console.log('First widget with id = ' + event.widgetId + ' has been initilized');
306
- off();
307
- })
308
-
309
- ```
310
-
311
-
312
- ### Widget API
313
- Объект Widget предоставляет функции для работы с конкретным виджетом.
314
-
315
- #### Метод `subscribe(channelType)`
316
- Инициализирует подписку пользователя на канал.
317
-
318
- **Принимает** channelType - тип канала, на который следует осуществить подписку. Поддерживаются следующие коды каналов:
319
- - `'facebook'` - Facebook
320
- - `'tg'` - Telegram
321
- - `'viber'` - Viber
322
- - `'vk'` - Vkontakte
323
- - `'whatsapp'` - WhatsApp
324
- - `'skype'` - Skype
325
-
326
-
327
-
328
- **Внимание!**
329
- - Мы **настоятельно рекомендуем** вызывать эту функцию непосредственно из обработчика клика на кнопку/ссылку, во избежание блокирования функции браузером.
330
-
331
- ##### Пример использования
332
- ```javascript
333
- var config = {
334
- widgetId: 'YOUR_WIDGET_ID'
335
- };
336
-
337
- TextBack.SDK.initWidget(config).then(
338
- function(widget) {
339
- document.getElementById('MY_TG_BUTTON').addEventListener('click', function(event) {
340
- event.preventDefault();
341
- widget.subscribe('tg');
342
- });
343
-
344
- document.getElementById('MY_VK_BUTTON').addEventListener('click', function(event) {
345
- event.preventDefault();
346
- widget.subscribe('vk');
347
- })
348
- }
349
- );
350
- ```
351
-
352
-
353
- #### Метод `getConfig()`
354
- **Возвращает** загруженный с сервера объект конфигурации виджета
355
-
356
-
357
- #### Метод `getChannels()`
358
- **Возвращает** объекты каналов виджета(см. `Channel` ниже)
359
-
360
-
361
- #### Метод `getEnabledChannels()`
362
- **Возвращает** объекты каналов виджета, которые сейчас активны и инициализированны без ошибок.
363
-
364
- ##### Пример использования
365
- ```javascript
366
- var config = {
367
- widgetId: 'YOUR_WIDGET_ID'
368
- };
369
-
370
- TextBack.SDK.initWidget(config).then(
371
- function(widget) {
372
- console.log('Total channels: ' + widget.getChannels().length);
373
- console.log('Enabled channels: ' + widget.getEnabledChannels().length);
374
- }
375
- );
376
- ```
377
-
378
- ### Channel API
379
- Объекты `Channel` предоставляют API конкретных каналов в виджете. Каждый канал реализует свою собственную функцию подписки пользователя.
380
-
381
- #### Свойство `hasError`
382
- Устанавливается в значение `true`, если при инициализации канала произошла ошибка (например, если не удалось загрузить внешнюю библиотеку для канала VKontakte). Информация об ошибке будет выведена в консоль.
383
-
384
- Каналы, для которых `hasError == true` не вовзращаются при вызове метода `getEnabledChannels()` у объекта виджета.
385
-
386
- #### Метод `subscribe()`
387
- Инициализирует процесс подписки пользователя на канал.
388
-
389
- ##### Пример использования
390
- ```javascript
391
- var config = {
392
- widgetId: 'YOUR_WIDGET_ID'
393
- };
394
-
395
- TextBack.SDK.initWidget(config).then(
396
- function(widget) {
397
- document.getElementById('MY_BUTTON').addEventListener('click', function(event) {
398
- event.preventDefault();
399
- const channels = widget.getEnabledChannels();
400
- if (channels.length > 0) {
401
- channels[0].subscribe();
402
- console.log('User has been subscribed on ' + channels[0].channel + ' channel');
403
- } else {
404
- console.log('No channels available for subscription');
405
- }
406
- });
407
- }
408
- );
409
- ```
410
-
411
-
412
- ## Пример
413
- Страница с двумя ссылками, подписывающими в Telegram и Vkontakte.
414
- ```html
415
- <!DOCTYPE html>
416
- <html>
417
- <head>
418
- <meta charset="UTF-8">
419
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
420
- <title>TextBack SDK Example</title>
421
- </head>
422
- <body>
423
- <h1>TextBack SDK</h1>
424
- <a href="" id="sign_tg">Telegram</a>
425
- <a href="" id="sign_vk">Vkontakte</a>
426
-
427
- <script src="//unpkg.com/@textback/notification-widget@latest/build/sdk.js"></script>
428
-
429
- <script type="text/javascript">
430
- var config = {
431
- widgetId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
432
- };
433
-
434
- var off = TextBack.SDK.on('widget.init', function(event) {
435
- console.log('First widget has been initialized. ' + event.widgetId);
436
- off();
437
- });
438
-
439
- TextBack.SDK.initWidget(config).then(function(widget) {
440
- document.getElementById('sign_tg').addEventListener('click', function(event) {
441
- event.preventDefault();
442
- widget.subscribe('tg');
443
- });
444
-
445
- document.getElementById('sign_vk').addEventListener('click', function(event) {
446
- event.preventDefault();
447
- widget.subscribe('vk');
448
- });
449
- });
450
- </script>
451
- </body>
452
- </html>
453
- ```
454
-
455
-
456
- # Разработка
457
-
458
- В терминале перейдите в директорию исходного кода виджета и выполните команды:
459
-
460
- ```
461
- npm run dev
462
- PORT=8080 npm start
463
- ```
464
-
465
- После этого тестовый стенд будет доступен локально по адресу http://localhost:8080/examples
466
-
467
- Если переменная PORT не указана, по умолчанию будет использован порт 3000
468
-
469
- После любых изменений кода обновите страницу браузера
470
-
471
- Для проверки codestyle выполните
472
-
473
- ```
474
- npm run test:lint
475
- ```
476
-
477
- # Тестирование
478
-
479
- Для запуска тестов выполните
480
-
481
- ```
482
- npm run build
483
- npm run test:all
484
- ```
485
-
486
- Тесты будут запущенны во всех браузерах, установленных на локальной машине.
487
-
488
- Тестирование осуществляется с помощью https://github.com/DevExpress/testcafe
489
-
490
- Все тесты запускаются из директории `./tests`
1
+ # Установка виджета на сайт
2
+
3
+ Для установки виджета подписки на сайт, сначала добавьте к вашей странице:
4
+
5
+ ```
6
+ <script src="//cdn.jsdelivr.net/npm/@textback/notification-widget@latest/build/index.js"></script>
7
+ ```
8
+ **Внимание** Виджет больше не требует подключения дополнительного скрипта с полифиллом.
9
+
10
+ Для корректного отображение виджета на адаптивных сайтах на мобильных требуется добавить следующий мета-тег в секцию `<head>`:
11
+ ```html
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0>
13
+ ```
14
+
15
+ Затем вы можете вставить один или несколько виджетов:
16
+
17
+ ```
18
+ <tb-notification-widget widget-id="YOUR_WIDGET_ID"></tb-notification-widget>
19
+ ```
20
+
21
+ с необходимыми widget-id. YOUR_WIDGET_ID указан в консоли администратора Textback.
22
+
23
+ Если вы планируете использовать виджет в режиме "попап", то встраивайте виджет непосредственно внутри тега body, лучше сразу перед `</body>` шаблона страницы, на которую добавляется виджет.
24
+
25
+ Если виджет будет использоваться в режиме "инлайн", то добавте виджет в том месте шаблона, в котором он должен отобразиться.
26
+
27
+ Дополнительные параметры можно передать через data-атрибуты:
28
+
29
+ ```
30
+ <tb-notification-widget
31
+ widget-id="YOUR_WIDGET_ID"
32
+ data-user-id="USER_ID"
33
+ data-order-id="ORDER_ID">
34
+ </tb-notification-widget>
35
+ ```
36
+
37
+ Чтобы передать secureContext, нужно использовать атрибут `secure-context-token`:
38
+ ```html
39
+ <tb-notification-widget
40
+ widget-id="YOUR_WIDGET_ID"
41
+ secure-context-token="YOUR_TOKEN"
42
+ </tb-notification-widget>
43
+ ```
44
+
45
+ ## Динамическая инициализация виджета
46
+
47
+ Если вам необходимо динамически проинициализировать виджет, например во всплывающем окне или при разработке Single Page Application
48
+ вы можете использовать класс, который доступен стразу после загрузки скрипта виджета
49
+
50
+ ```
51
+ var widgetContainer = document.querySelector('tb-notification-widget');
52
+
53
+ var options = {
54
+ widgetId: "YOUR_WIDGET_ID",
55
+ element: widgetContainer,
56
+ data: {userId: "USER_ID", orderId: "ORDER_ID"}
57
+ };
58
+
59
+ new TextBack.NotificationWidget(options)
60
+ ```
61
+
62
+ где widgetContainer - корневой HTML элемент, в котором необходимо отобразить виджет, data - дополнительные параметры.
63
+
64
+ Чтобы изменить параметры после инициализации виджета повторно проинициализируйте виджет на элементе с новыми параметрами:
65
+
66
+ ```
67
+ options.data.orderId = "NEW_ORDER_ID";
68
+
69
+ new TextBack.NotificationWidget(options)
70
+ ```
71
+
72
+ ### Мгновенно отобразить popup-виджет
73
+ По умолчанию при инициализации виджета используются параметры тайминга из настроек - через сколько секунд отобразить виджет, сколько раз за сессию и т.д.
74
+
75
+ Если требуется отобразить виджет сразу, то можно воспользоваться разработанным для этого API.
76
+
77
+ Сначала нужно добавить виджет на страницу, указав атрибут ```only-manual```
78
+ ```html
79
+ <tb-notification-widget widget-id="YOUR_WIDGET_ID" only-manual></tb-notification-widget>
80
+ ```
81
+ Добавленный таким образом виджет будет скрыт и не будет показан автоматически.
82
+
83
+ Чтобы теперь отобразить виджет, нужно выполнить следующий код:
84
+ ```javascript
85
+ TextBack.NotificationWidget.getWidget('YOUR_WIDGET_ID').then(function(widget) {
86
+ widget.show();
87
+ });
88
+ ```
89
+
90
+ Пример отображения виджета при нажатии на кнопку:
91
+ ```javascript
92
+ document.querySelector('#YOUR_BUTTON_ID').addEventListener('click', function() {
93
+ TextBack.NotificationWidget.getWidget('YOUR_WIDGET_ID').then(function(widget) {
94
+ widget.show();
95
+ });
96
+ });
97
+ ```
98
+
99
+ ### Предпросмотр виджета
100
+ Релизован механизм, позволяющий задать JSON виджета динамически, не
101
+ подгружая его по ID с нашего **API**. Для того, чтобы подписки работали
102
+ в предпросомтре, необходимо, чтобы виджет был сохранен в БАЗУ
103
+ и имел присвоенный ID.
104
+
105
+ ---
106
+ Этот механизм внедрян для добавления функционала пред-просмотра виджета
107
+ в нашем редакторе, но ижет быть использован сторонними разработчикми,
108
+ для динамической генерации сообщения приветствия, однака надо
109
+ иметь в виду, что **ЭТО апи не финальное, и может изменится в любой момент**,
110
+ используйте на свой страх и риск.
111
+
112
+ ---
113
+
114
+ Использование:
115
+
116
+ ```javascript
117
+ //получаем копию настроек виджета
118
+ var data = angular.copy(notificationWidgetJSON);
119
+
120
+
121
+ //override settings for preview purpose
122
+ //переопределяем некторыесвойства, сохраненные на сервере
123
+ //отображатся в виджете будет нвое значение.
124
+ data.displayOptions.onLeave = null;
125
+ data.displayOptions.timeoutDelay = 0;
126
+ data.displayOptions.onTimeout = "yes";
127
+
128
+ //к примеру vkApiId привязан к домену, на котром отображается виджет
129
+ data.vkApiId=TextBack.configuration.get('vkAppId');
130
+
131
+
132
+
133
+ //динамически создаем контейнер для виджета
134
+ var widgetContainerJq = $('<div></div>');
135
+ $('body').append(widgetContainerJq);
136
+
137
+ var widgetContainer = widgetContainerJq[0];
138
+
139
+
140
+ var options = {
141
+ apiPath: 'https://api.textback.io/api', //необязатльный параметр
142
+ element: widgetContainer,
143
+ widgetConfig: data
144
+ };
145
+
146
+ new TextBack.NotificationWidget(options)
147
+ ```
148
+
149
+ # Локализация
150
+
151
+ Все поддерживаемые языки находятся в переменной `TextBack.NotificationWidget.locales`. Перевод затрагивает только статичные элементы виджета, значения которых не задаются в настройках виджета.
152
+
153
+ Чтобы применить нужный язык к виджету, передайте соответсвующий параметр `lang` в тег виджета:
154
+
155
+ ```
156
+ <tb-notification-widget widget-id="YOUR_WIDGET_ID" lang="ru">
157
+ </tb-notification-widget>
158
+ ```
159
+
160
+ или
161
+
162
+ ```
163
+ var widgetContainer = document.querySelector('tb-notification-widget');
164
+
165
+ var options = {
166
+ widgetId: "YOUR_WIDGET_ID",
167
+ element: widgetContainer,
168
+ lang: 'ru'
169
+ };
170
+
171
+ new TextBack.NotificationWidget(options)
172
+ ```
173
+
174
+ Чтобы изменить существующий перевод, передайте новое значение для нужной переменной в **TextBack.NotificationWidget.locales**:
175
+
176
+ ```
177
+ <script>TextBack.NotificationWidget.locales.ru.whatsappb = 'Узнать больше'</script>
178
+ ```
179
+
180
+ Вы можете создать новый перевод для всех элементов виджета (это касается как виджета подписок, так и WhatsApp Hunter):
181
+ 1. В личном кабинете установите значение языка по умолчанию, чтобы аттрибут lang не передавался с нашего сервера;
182
+ 2. В теге виджета на странице установите аттрибут lang со значением языка (это будет название новой локализации - locales);
183
+ 3. Передайте значения для всех переменных в массиве после кода виджета. Для незаданных переменных будет использоваться язык по умолчанию - английский.
184
+
185
+ **Важно!** И для виджета подписок, и для WhatsApp Hunter перевод берется из TextBack.NotificationWidget.locales. Если на странице используются оба виджета и обоим нужно задать полную кастомную локализацию в рамках одного locales, значения переменных нужно объявлять в одном массиве. Если объявить переменные по отдельности, то использоваться будет последняя переменная - неполная.
186
+
187
+ Если для виджетов заданы разные lang, то объявите переменные для каждого виджета в отдельном массиве.
188
+
189
+ **Пример перевода виджета подписок на французский**
190
+ ```
191
+ <script>
192
+ TextBack.NotificationWidget.locales.fr = {
193
+ facebook: "Facebook",
194
+ facebookExtended: "Abonnez-vous à Facebook",
195
+ telegram: "Telegram",
196
+ telegramExtended: "Abonnez-vous à Telegram",
197
+ viber: "Viber",
198
+ viberExtended: "Subscribe to Viber",
199
+ vkontakte: "VK",
200
+ vkontakteExtended: "Abonnez-vous à Viber",
201
+ whatsapp: "WhatsApp",
202
+ whatsappExtended: "Abonnez-vous à WhatsApp",
203
+ whatsappb: "WhatsApp",
204
+ whatsappbExtended: "Abonnez-vous à WhatsApp"
205
+ }
206
+ </script>
207
+ ```
208
+
209
+ ### Список всех переменных
210
+
211
+ Виджет подписок (пример для английского языка)
212
+
213
+ ```
214
+ facebook: "Facebook", //для всех кнопок, кроме варианта показа "Прямоугольные кнопки" или "Всплывающее окно"
215
+ facebookExtended: "Subscribe to Facebook", //если выбран вариант показа "Прямоугольные кнопки" или "Всплывающее окно"
216
+ telegram: "Telegram", //для всех кнопок, кроме варианта показа "Прямоугольные кнопки" или "Всплывающее окно"
217
+ telegramExtended: "Subscribe to Telegram", //если выбран вариант показа "Прямоугольные кнопки" или "Всплывающее окно"
218
+ viber: "Viber", //для всех кнопок, кроме варианта показа "Прямоугольные кнопки" или "Всплывающее окно"
219
+ viberExtended: "Subscribe to Viber", //если выбран вариант показа "Прямоугольные кнопки" или "Всплывающее окно"
220
+ vkontakte: "VK", //для всех кнопок, кроме варианта показа "Прямоугольные кнопки" и "попап".
221
+ vkontakteExtended: "Subscribe to VK", //если выбран лендинг ВК и вариант показа "Прямоугольные кнопки" или "Всплывающее окно"
222
+ whatsapp: "WhatsApp", //для всех кнопок, кроме варианта показа "Прямоугольные кнопки" или "Всплывающее окно"
223
+ whatsappExtended: "Subscribe to WhatsApp", //если выбран вариант показа "Прямоугольные кнопки" или "Всплывающее окно"
224
+ whatsappb: "WhatsApp", //для всех кнопок, кроме варианта показа "Прямоугольные кнопки" или "Всплывающее окно"
225
+ whatsappbExtended: "Subscribe to WhatsApp", //если выбран вариант показа "Прямоугольные кнопки" или "Всплывающее окно"
226
+
227
+ //NB: если не использовать лендинг ВК, то при выборе прямоугольных кнопок или попапа используется нативная кнопка, перевод которой пока не реализован
228
+ ```
229
+ WhatsApp Hunter (пример для английского языка)
230
+
231
+ ```
232
+ getAnswer: "Get an answer in ", //согласие на получение сообщений
233
+ enterNumber: "Enter your phone number", //placeholder в инпуте
234
+ sendMessage: "We'll send a message in 3-2-1 sec", //В процессе отправки сообщения
235
+ answerSuccessful: "We've answered you in WhatsApp. <br> Check your phone", //После успешной отправки
236
+ numberNotFound: "There is no such user in WhatsApp. <br> Are you from Mars?🤔", //Ошибка, формат номера некорректный
237
+ tryAgain: "Try another number", //Повтор ввода, если номер не найден
238
+ somethingWentWrong: "Oops! Something went wrong", //на 500 при отправке сообщения (например, канал не подключен)
239
+ errorTryAgain: "Try again", //Повтор ввода при ошибки 500
240
+ ```
241
+
242
+ # TextBack Widget SDK
243
+ TextBack Widget SDK предоставляет набор функций для управления подпиской.
244
+
245
+ SDK входит в состав виджета, но может быть подключен отдельно(см. ниже).
246
+
247
+ **Преимущества SDK, в сравнении с виджетом подписок:**
248
+ - SDK позволяет полностью настраивать внешний вид вашего UI, предоставляя только функции подписки пользователей.
249
+ - Файл с SDK имеет меньший размер, чем файл с виджетом подписок.
250
+
251
+ ## Как использовать SDK
252
+
253
+ ### Подключение
254
+ Добавить в код страницы
255
+ ```
256
+ <script src="//cdn.jsdelivr.net/npm/@textback/notification-widget@latest/build/sdk.js"></script>
257
+ ```
258
+
259
+ После этого на странице будет доступен объект ```TextBack.SDK```
260
+
261
+ ### Использование
262
+ Приведенный ниже код будет подписывать пользователя в Telegram при клике на ссылку с `id="MY_TG_BUTTON"`:
263
+
264
+ ```javascript
265
+ var config = {
266
+ widgetId: 'YOUR_WIDGET_ID'
267
+ };
268
+
269
+ TextBack.SDK.initWidget(config).then(
270
+ function(widget) {
271
+ document.getElementById('MY_TG_BUTTON').addEventListener('click', function(event) {
272
+ event.preventDefault();
273
+ widget.subscribe('tg');
274
+ })
275
+ }
276
+ );
277
+ ```
278
+ **ВАЖНО!** Элемент с `id="MY_TG_BUTTON"` должен присутствовать на странице во время исполнения скрипта.
279
+
280
+ Более подробное описание функции `initWidget()`, состава полей объекта конфигурации и других функций SDK смотрите ниже.
281
+
282
+ ## SDK API
283
+ **Примечание.** В документации по SDK под "виджетом" подразумевается не "виджет подписок", упоминавшийся ранее, а объект, который содержит настройки и методы виджета. Таким образом "**инициализация виджета**" - это не отрисовка виджета на странице, а **загрузка настроек с сервера и подготовка к работе**.
284
+
285
+ Иными словами: "виджет" = "объект в приложении", "виджет подписок" = "объект в JavaScript" + "UI".
286
+
287
+ ### Глобальное пространство имен
288
+ SDK и виджет подписок доступны как свойства `SDK` и `NotificationWidget` глобального объекта `TextBack` соответственно.
289
+
290
+ Для своей работы SDK использует полифиллы для метода `window.fetch()` и объекта ES6 `Promise`.
291
+
292
+ Также, для работы с каналами VKontakte, используется VK JS API, представленное глобальным объектом VK.
293
+
294
+ ### TextBack.SDK API
295
+ Объект `TextBack.SDK` предоставляет API для инициализации виджетов и работы с ними.
296
+
297
+ #### Метод `initWidget(config)`
298
+ Инициализирует новый виджет по заданному объекту конфигурации.
299
+
300
+ **Принимает**
301
+ Объект `config` со следующими полями:
302
+ - widgetId - (обязательный) идентификатор виджета. Если виджет с таким идентификатором уже есть, то виджет будет перезагружен;
303
+ - secureContextToken - строка;
304
+ - insecureContext - объект;
305
+ - apiPath - адрес сервера;
306
+ - overrideConfig - объект с настройками виджета. Если задан, то настройки виджета не будут загружаться с сервера, и для инициализации виджета будет использован данный объект;
307
+ - customData - произвольные пользовательские данные.
308
+
309
+ **Возвращает** Promise, который будет fulfilled объектом виджета.
310
+
311
+ ##### Пример использования
312
+ ```javascript
313
+ var config = {
314
+ widgetId: 'YOUR_WIDGET_ID',
315
+ insecureContext: {
316
+ data: 'data'
317
+ }
318
+ };
319
+
320
+ TextBack.SDK.initWidget(config).then(
321
+ function(widget) {
322
+ console.log('Widget has been initialized.');
323
+ }
324
+ );
325
+ ```
326
+
327
+
328
+ #### Метод `getWidget(widgetId)`
329
+ **Принимает** `widgetId` - идентификатор виджета.
330
+ **Возвращает** Promise, возвращаемый функцией `initWidget()`, соответствующий переданному идентификатору.
331
+
332
+ ##### Пример использования
333
+ ```javascript
334
+ TextBack.SDK.getWidget('YOUR_WIDGET_ID').then(
335
+ function(widget) {
336
+ console.log('Widget has id = ' + widget.id);
337
+ }
338
+ )
339
+
340
+ ```
341
+
342
+ #### Метод `on(eventName, callback)`
343
+ Вешает обработчик `callback` на событие `eventName`. При наступлении события в `callback` первым аргументом будет передан объект события. Описание полей объекта события для различных событий см. ниже.
344
+
345
+ **Поддерживаемые события**
346
+ - 'widget.init' - успеная инициализация виджета. поля события:
347
+ - widgetId - идентификатор виджета
348
+ - 'subscribe.start' - вызвана функция подписки на канал. поля события:
349
+ - widgetId - идентификатор виджета
350
+ - channel - объект канала, на который осуществляется подписка
351
+
352
+
353
+ **Возвращает**
354
+ Функцию отписки обработчика `callback` от события.
355
+
356
+ ##### Пример использования
357
+ При инициализации первого виджета вывести в консоль сообщение.
358
+ ```javascript
359
+ const config = {
360
+ widgetId: 'YOUR_WIDGET_ID'
361
+ };
362
+
363
+ TextBack.SDK.initWidget(config);
364
+ const off = TextBack.SDK.on('widget.init', function(event) {
365
+ console.log('First widget with id = ' + event.widgetId + ' has been initilized');
366
+ off();
367
+ })
368
+
369
+ ```
370
+
371
+ #### Метод `deeplinkUpdater(String)`
372
+ #### deeplinkUpdater :: String -> Promise -> Function
373
+
374
+ Ф-ция getDeeplinkUpdater() принимает идентефикатор виджета и возвращает Promise, содержащий ф-цию, позволяющую обновлять данные в insecureContext с помощью PATCH запроса.
375
+
376
+ ##### Пример использования
377
+
378
+ Данный пример отправит в insecureContext данные на событие подписки.
379
+ ```javascript
380
+ TextBack.SDK.getDeeplinkUpdater('myWidgetId').then(f => {
381
+ let data = {
382
+ foo: 'foo',
383
+ bar: 'bar'
384
+ };
385
+
386
+ TextBack.SDK.on('subscribe.start', () => f(data));
387
+ });
388
+ ```
389
+
390
+ ### Widget API
391
+ Объект Widget предоставляет функции для работы с конкретным виджетом.
392
+
393
+ #### Метод `subscribe(channelType)`
394
+ Инициализирует подписку пользователя на канал.
395
+
396
+ **Принимает** channelType - тип канала, на который следует осуществить подписку. Поддерживаются следующие коды каналов:
397
+ - `'facebook'` - Facebook
398
+ - `'tg'` - Telegram
399
+ - `'viber'` - Viber
400
+ - `'vk'` - Vkontakte
401
+ - `'whatsapp'` - WhatsApp
402
+ - `'whatsappb'` - WhatsApp Business API
403
+ - `'skype'` - Skype
404
+
405
+
406
+
407
+ **Внимание!**
408
+ - Мы **настоятельно рекомендуем** вызывать эту функцию непосредственно из обработчика клика на кнопку/ссылку, во избежание блокирования функции браузером.
409
+
410
+ ##### Пример использования
411
+ ```javascript
412
+ var config = {
413
+ widgetId: 'YOUR_WIDGET_ID'
414
+ };
415
+
416
+ TextBack.SDK.initWidget(config).then(
417
+ function(widget) {
418
+ document.getElementById('MY_TG_BUTTON').addEventListener('click', function(event) {
419
+ event.preventDefault();
420
+ widget.subscribe('tg');
421
+ });
422
+
423
+ document.getElementById('MY_VK_BUTTON').addEventListener('click', function(event) {
424
+ event.preventDefault();
425
+ widget.subscribe('vk');
426
+ })
427
+ }
428
+ );
429
+ ```
430
+
431
+
432
+ #### Метод `getConfig()`
433
+ **Возвращает** загруженный с сервера объект конфигурации виджета
434
+
435
+
436
+ #### Метод `getChannels()`
437
+ **Возвращает** объекты каналов виджета(см. `Channel` ниже)
438
+
439
+
440
+ #### Метод `getEnabledChannels()`
441
+ **Возвращает** объекты каналов виджета, которые сейчас активны и инициализированны без ошибок.
442
+
443
+ ##### Пример использования
444
+ ```javascript
445
+ var config = {
446
+ widgetId: 'YOUR_WIDGET_ID'
447
+ };
448
+
449
+ TextBack.SDK.initWidget(config).then(
450
+ function(widget) {
451
+ console.log('Total channels: ' + widget.getChannels().length);
452
+ console.log('Enabled channels: ' + widget.getEnabledChannels().length);
453
+ }
454
+ );
455
+ ```
456
+
457
+ ### Channel API
458
+ Объекты `Channel` предоставляют API конкретных каналов в виджете. Каждый канал реализует свою собственную функцию подписки пользователя.
459
+
460
+ #### Свойство `hasError`
461
+ Устанавливается в значение `true`, если при инициализации канала произошла ошибка (например, если не удалось загрузить внешнюю библиотеку для канала VKontakte). Информация об ошибке будет выведена в консоль.
462
+
463
+ Каналы, для которых `hasError == true` не вовзращаются при вызове метода `getEnabledChannels()` у объекта виджета.
464
+
465
+ #### Метод `subscribe()`
466
+ Инициализирует процесс подписки пользователя на канал.
467
+
468
+ ##### Пример использования
469
+ ```javascript
470
+ var config = {
471
+ widgetId: 'YOUR_WIDGET_ID'
472
+ };
473
+
474
+ TextBack.SDK.initWidget(config).then(
475
+ function(widget) {
476
+ document.getElementById('MY_BUTTON').addEventListener('click', function(event) {
477
+ event.preventDefault();
478
+ const channels = widget.getEnabledChannels();
479
+ if (channels.length > 0) {
480
+ channels[0].subscribe();
481
+ console.log('User has been subscribed on ' + channels[0].channel + ' channel');
482
+ } else {
483
+ console.log('No channels available for subscription');
484
+ }
485
+ });
486
+ }
487
+ );
488
+ ```
489
+
490
+
491
+ ## Пример
492
+ Страница с двумя ссылками, подписывающими в Telegram и Vkontakte.
493
+ ```html
494
+ <!DOCTYPE html>
495
+ <html>
496
+ <head>
497
+ <meta charset="UTF-8">
498
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
499
+ <title>TextBack SDK Example</title>
500
+ </head>
501
+ <body>
502
+ <h1>TextBack SDK</h1>
503
+ <a href="" id="sign_tg">Telegram</a>
504
+ <a href="" id="sign_vk">Vkontakte</a>
505
+
506
+ <script src="//cdn.jsdelivr.net/npm/@textback/notification-widget@latest/build/sdk.js"></script>
507
+
508
+ <script type="text/javascript">
509
+ var config = {
510
+ widgetId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
511
+ };
512
+
513
+ var off = TextBack.SDK.on('widget.init', function(event) {
514
+ console.log('First widget has been initialized. ' + event.widgetId);
515
+ off();
516
+ });
517
+
518
+ TextBack.SDK.initWidget(config).then(function(widget) {
519
+ document.getElementById('sign_tg').addEventListener('click', function(event) {
520
+ event.preventDefault();
521
+ widget.subscribe('tg');
522
+ });
523
+
524
+ document.getElementById('sign_vk').addEventListener('click', function(event) {
525
+ event.preventDefault();
526
+ widget.subscribe('vk');
527
+ });
528
+ });
529
+ </script>
530
+ </body>
531
+ </html>
532
+ ```
533
+
534
+
535
+ # Разработка
536
+
537
+ В терминале перейдите в директорию исходного кода виджета и выполните команды:
538
+
539
+ ```
540
+ npm run dev
541
+ PORT=8080 npm start
542
+ ```
543
+
544
+ После этого тестовый стенд будет доступен локально по адресу http://localhost:8080/examples
545
+
546
+ Если переменная PORT не указана, по умолчанию будет использован порт 3000
547
+
548
+ После любых изменений кода обновите страницу браузера
549
+
550
+ Для проверки codestyle выполните
551
+
552
+ ```
553
+ npm run test:lint
554
+ ```
555
+
556
+ # Тестирование
557
+
558
+ Для запуска тестов выполните
559
+
560
+ ```
561
+ npm run build
562
+ npm run test:all
563
+ ```
564
+
565
+ Тесты будут запущенны во всех браузерах, установленных на локальной машине.
566
+
567
+ Тестирование осуществляется с помощью https://github.com/DevExpress/testcafe
568
+
569
+ Все тесты запускаются из директории `./tests`