web-push-notifications 3.40.3 → 3.44.1

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.
Files changed (149) hide show
  1. package/.editorconfig +11 -0
  2. package/.gitlab-ci.yml +190 -0
  3. package/babel.config.js +7 -0
  4. package/ci/cdn/Dockerfile +12 -0
  5. package/ci/dev/Dockerfile +30 -0
  6. package/ci/dev/rootfs/entrypoint.sh +18 -0
  7. package/ci/dev/rootfs/entrypoint.sh.d/nginx.sh +6 -0
  8. package/ci/dev/rootfs/entrypoint.sh.d/supervisor.sh +5 -0
  9. package/ci/dev/rootfs/etc/nginx/_real_ip.conf +2 -0
  10. package/ci/dev/rootfs/etc/nginx/conf.d/default.conf +20 -0
  11. package/ci/dev/rootfs/etc/supervisor.d/nginx.ini +11 -0
  12. package/ci/github/Dockerfile +59 -0
  13. package/ci/github/release-zip.js +61 -0
  14. package/ci/npm/Dockerfile +19 -0
  15. package/config/config.js +24 -0
  16. package/config/configBuilder.js +126 -0
  17. package/config/helpers.js +9 -0
  18. package/config/index.js +1 -0
  19. package/develop/README.md +42 -0
  20. package/develop/favicon.png +0 -0
  21. package/develop/index.html +511 -0
  22. package/eslint.config.mjs +114 -0
  23. package/package.json +4 -34
  24. package/{lib → public}/index.d.ts +10 -10
  25. package/scripts/zip.js +26 -0
  26. package/src/core/Pushwoosh.ts +768 -0
  27. package/src/core/Pushwoosh.types.ts +254 -0
  28. package/src/core/Safari.types.ts +26 -0
  29. package/src/core/constants.ts +58 -0
  30. package/src/core/events.types.ts +46 -0
  31. package/src/core/functions.ts +33 -0
  32. package/src/core/legacyEventsMap.ts +64 -0
  33. package/src/core/logger.ts +64 -0
  34. package/src/core/modules/EventBus/EventBus.ts +66 -0
  35. package/src/core/modules/EventBus/index.ts +1 -0
  36. package/src/core/storage.ts +254 -0
  37. package/src/helpers/logger.ts +81 -0
  38. package/src/helpers/pwlogger/Logger.constants.ts +31 -0
  39. package/src/helpers/pwlogger/Logger.ts +218 -0
  40. package/src/helpers/pwlogger/Logger.types.ts +66 -0
  41. package/src/helpers/pwlogger/handlers/handler-console/handler-console.ts +40 -0
  42. package/src/helpers/pwlogger/index.ts +2 -0
  43. package/src/helpers/unescape.ts +36 -0
  44. package/src/models/InboxMessages.ts +202 -0
  45. package/src/models/InboxMessages.types.ts +111 -0
  46. package/src/models/NotificationPayload.ts +216 -0
  47. package/src/models/NotificationPayload.types.ts +65 -0
  48. package/src/modules/Api/Api.ts +386 -0
  49. package/src/modules/Api/Api.types.ts +7 -0
  50. package/src/modules/ApiClient/ApiClient.ts +153 -0
  51. package/src/modules/ApiClient/ApiClient.types.ts +222 -0
  52. package/src/modules/Data/Data.ts +345 -0
  53. package/src/modules/DateModule.ts +53 -0
  54. package/src/modules/InboxMessagesPublic.ts +222 -0
  55. package/src/modules/PlatformChecker/PlatformChecker.ts +170 -0
  56. package/src/modules/PlatformChecker/PlatformChecker.types.ts +5 -0
  57. package/src/modules/PlatformChecker/index.ts +1 -0
  58. package/src/modules/storage/Storage.ts +164 -0
  59. package/src/modules/storage/Storage.types.ts +54 -0
  60. package/src/modules/storage/Store.ts +104 -0
  61. package/src/modules/storage/migrations/26-11-2018.ts +25 -0
  62. package/src/modules/storage/migrations/MigrationExecutor.ts +31 -0
  63. package/src/modules/storage/migrations/Migrations.ts +41 -0
  64. package/src/modules/storage/migrations/constants.ts +8 -0
  65. package/src/modules/storage/migrations/helpers.ts +16 -0
  66. package/src/modules/storage/migrations/initial.ts +47 -0
  67. package/src/modules/storage/version.ts +2 -0
  68. package/src/npm.ts +1 -0
  69. package/src/pushwoosh-web-notifications.ts +47 -0
  70. package/src/pushwoosh-widget-inbox.ts +8 -0
  71. package/src/pushwoosh-widget-subscribe-popup.ts +9 -0
  72. package/src/pushwoosh-widget-subscription-button.ts +8 -0
  73. package/src/pushwoosh-widget-subscription-prompt.ts +6 -0
  74. package/src/service-worker.ts +455 -0
  75. package/src/services/PushService/PushService.ts +2 -0
  76. package/src/services/PushService/PushService.types.ts +74 -0
  77. package/src/services/PushService/drivers/PushServiceDefault/PushServiceDefault.ts +235 -0
  78. package/src/services/PushService/drivers/PushServiceDefault/PushServiceDefault.types.ts +3 -0
  79. package/src/services/PushService/drivers/PushServiceSafari/PushServiceSafari.ts +125 -0
  80. package/src/services/PushService/drivers/PushServiceSafari/PushServiceSafari.types.ts +4 -0
  81. package/src/widget-inbox.ts +1 -0
  82. package/src/widget-subscribe-popup.ts +1 -0
  83. package/src/widget-subscription-button.ts +1 -0
  84. package/src/widget-subscription-prompt.ts +33 -0
  85. package/src/widgets/Inbox/InboxWidget.ts +564 -0
  86. package/src/widgets/Inbox/constants.ts +49 -0
  87. package/src/widgets/Inbox/css/inboxWidgetStyle.css +274 -0
  88. package/src/widgets/Inbox/helpers.ts +63 -0
  89. package/src/widgets/Inbox/inbox.d.ts +9 -0
  90. package/src/widgets/Inbox/inbox_widget.types.ts +41 -0
  91. package/src/widgets/Inbox/index.ts +1 -0
  92. package/src/widgets/Inbox/widgetTemplates.ts +55 -0
  93. package/src/widgets/SubscribePopup/SubscribePopup.ts +241 -0
  94. package/src/widgets/SubscribePopup/constants.ts +66 -0
  95. package/src/widgets/SubscribePopup/helpers.ts +11 -0
  96. package/src/widgets/SubscribePopup/index.ts +1 -0
  97. package/src/widgets/SubscribePopup/popupTemplates.ts +24 -0
  98. package/src/widgets/SubscribePopup/styles/popup.css +226 -0
  99. package/src/widgets/SubscribePopup/types/subscribe-popup.ts +68 -0
  100. package/src/widgets/SubscriptionButton/assets/css/main.css +205 -0
  101. package/src/widgets/SubscriptionButton/bell.ts +67 -0
  102. package/src/widgets/SubscriptionButton/constants.ts +28 -0
  103. package/src/widgets/SubscriptionButton/index.ts +377 -0
  104. package/src/widgets/SubscriptionButton/positioning.ts +165 -0
  105. package/src/widgets/SubscriptionButton/subscribe_widget.types.ts +53 -0
  106. package/src/widgets/SubscriptionPrompt/SubscriptionPromptWidget.constants.ts +1 -0
  107. package/src/widgets/SubscriptionPrompt/SubscriptionPromptWidget.helpers.ts +110 -0
  108. package/src/widgets/SubscriptionPrompt/SubscriptionPromptWidget.ts +102 -0
  109. package/src/widgets/SubscriptionPrompt/SubscriptionPromptWidget.types.ts +23 -0
  110. package/src/widgets/SubscriptionPrompt/constants.ts +22 -0
  111. package/src/widgets/SubscriptionPrompt/helpers.ts +42 -0
  112. package/src/widgets/widgets.d.ts +4 -0
  113. package/src/worker/global.ts +36 -0
  114. package/src/worker/notification.ts +34 -0
  115. package/src/worker/worker.types.ts +4 -0
  116. package/test/__helpers__/apiHelpers.ts +22 -0
  117. package/test/__helpers__/keyValueHelpers.ts +15 -0
  118. package/test/__helpers__/platformHelpers.ts +54 -0
  119. package/test/__helpers__/sinonHelpers.ts +7 -0
  120. package/test/__helpers__/storageHelpers.ts +56 -0
  121. package/test/__mocks__/apiRequests.ts +26 -0
  122. package/test/__mocks__/idbMock.ts +12 -0
  123. package/test/__mocks__/idbObjectStoreMock.ts +38 -0
  124. package/test/__mocks__/inboxMessages.ts +292 -0
  125. package/test/__mocks__/models/inboxModel.ts +71 -0
  126. package/test/__mocks__/modules/apiClientModule.ts +18 -0
  127. package/test/__mocks__/modules/dateModule.ts +34 -0
  128. package/test/__mocks__/modules/inboxParamsModule.ts +21 -0
  129. package/test/__mocks__/modules/paramsBuilder.ts +12 -0
  130. package/test/__mocks__/modules/paramsModule.ts +35 -0
  131. package/test/__mocks__/modules/payloadBuilderModule.ts +15 -0
  132. package/test/__mocks__/modules/storageModule.ts +58 -0
  133. package/test/__mocks__/navigator.ts +38 -0
  134. package/test/__mocks__/notification.ts +84 -0
  135. package/test/__mocks__/pushwoosh.ts +12 -0
  136. package/test/__mocks__/userAgents +8 -0
  137. package/test/functions.test.ts +22 -0
  138. package/test/ignore-html.js +6 -0
  139. package/test/mocha.opts +6 -0
  140. package/test/modules/DateModule/unit.test.ts +80 -0
  141. package/test/modules/storage/Storage/unit.test.ts +180 -0
  142. package/test/modules/storage/Store/unit.test.ts +192 -0
  143. package/testRegister.js +24 -0
  144. package/tsconfig.json +31 -0
  145. package/webpack.config.js +163 -0
  146. package/lib/index.js +0 -2
  147. package/lib/index.js.map +0 -1
  148. package/lib/service-worker.js +0 -2
  149. package/lib/service-worker.js.map +0 -1
@@ -0,0 +1,564 @@
1
+ import {
2
+ CONFIG_STYLES,
3
+ DEFAULT_CONFIG,
4
+ } from './constants';
5
+ import inboxWidgetStyleCss from './css/inboxWidgetStyle.css';
6
+ import {
7
+ getValidColor,
8
+ isElementFixed,
9
+ compareBySendDate,
10
+ } from './helpers';
11
+ import { type IConfigStyles, type IInboxWidgetConfig, type TMessagesElementsType } from './inbox_widget.types';
12
+ import {
13
+ messageTemplate,
14
+ widgetTemplate,
15
+ widgetTemplateEmpty,
16
+ } from './widgetTemplates';
17
+ import { type Pushwoosh } from '../../core/Pushwoosh';
18
+ import { type IInboxMessagePublic } from '../../models/InboxMessages.types';
19
+
20
+ export class PWInboxWidget {
21
+ pw: Pushwoosh;
22
+ widget: HTMLElement;
23
+ trigger: HTMLElement;
24
+ list: HTMLElement;
25
+ widgetParent: HTMLElement;
26
+ count: number;
27
+ messages: Array<IInboxMessagePublic>;
28
+ messagesElements: TMessagesElementsType;
29
+ readItems: Array<string>;
30
+ isOpened: boolean;
31
+ config: IInboxWidgetConfig;
32
+ defaultMargin: number;
33
+ isFixed: boolean;
34
+
35
+ constructor(pw: Pushwoosh) {
36
+ // Set Pushwoosh object
37
+ this.pw = pw;
38
+
39
+ const inboxWidgetConfig = this.pw.initParams.inboxWidget!;
40
+
41
+ this.config = {
42
+ ...DEFAULT_CONFIG,
43
+ arrowBorderColor: inboxWidgetConfig.borderColor && inboxWidgetConfig.borderColor !== 'transparent'
44
+ ? inboxWidgetConfig.borderColor
45
+ : 'rgba(0,0,0,.1)',
46
+ ...this.pw.initParams.inboxWidget,
47
+ };
48
+
49
+ this.updateInbox = this.updateInbox.bind(this);
50
+ this.markVisibleItemsAsRead = this.markVisibleItemsAsRead.bind(this);
51
+ this.onWidgetClickHandler = this.onWidgetClickHandler.bind(this);
52
+ this.onTriggerClickHandler = this.onTriggerClickHandler.bind(this);
53
+ this.onWindowScrollHandler = this.onWindowScrollHandler.bind(this);
54
+ this.toggle = this.toggle.bind(this);
55
+
56
+ try {
57
+ this.initTrigger();
58
+ this.updateInbox();
59
+ this.addListeners();
60
+ } catch (err) {
61
+ console.warn(err);
62
+ }
63
+ }
64
+
65
+ public toggle(isOpened?: boolean) {
66
+ const openWidget = typeof isOpened === 'undefined'
67
+ ? !this.isOpened
68
+ : isOpened;
69
+
70
+ if (openWidget) {
71
+ this.openWidget();
72
+ } else {
73
+ this.closeWidget();
74
+ }
75
+ }
76
+
77
+ // set inbox widget to trigger element on page
78
+ private initTrigger() {
79
+ if (!this.pw.pwinbox) {
80
+ throw new Error('Web inbox is not allowed.');
81
+ }
82
+
83
+ const trigger = document.getElementById(this.config.triggerId);
84
+
85
+ if (!trigger) {
86
+ throw new Error('Inbox trigger element doesn\'t exist. You must set triggerId in inboxWidget config. See the documentation.');
87
+ }
88
+
89
+ this.trigger = trigger;
90
+ this.trigger.classList.add('pw-inbox-trigger');
91
+
92
+ this.defaultMargin = 12;
93
+ this.messagesElements = {};
94
+ this.messages = [];
95
+ this.readItems = [];
96
+ this.updateCounter(0);
97
+ this.isOpened = false;
98
+
99
+ this.renderWidget();
100
+
101
+ this.isFixed = isElementFixed(this.trigger);
102
+ }
103
+
104
+ private renderWidget() {
105
+ this.widget = document.createElement('div');
106
+ this.widget.id = 'pwInboxWidget';
107
+ this.widget.className = 'pw-inbox-widget';
108
+ this.widget.classList.toggle('pw-open', this.isOpened);
109
+
110
+ this.widgetParent = document.querySelector(this.config.appendTo) || document.body;
111
+
112
+ this.widgetParent.appendChild(this.widget);
113
+ this.widgetParent.appendChild(this.getStyle());
114
+ this.renderWidgetInner();
115
+ }
116
+
117
+ private getStyle(): HTMLStyleElement {
118
+ const styleNode = document.createElement('style');
119
+ styleNode.innerHTML = this.configureStyle(inboxWidgetStyleCss);
120
+ return styleNode;
121
+ }
122
+
123
+ private configureStyle(styles: string): string {
124
+ let resultStyles = styles.toString();
125
+ CONFIG_STYLES.forEach((style: IConfigStyles) => {
126
+ const template = new RegExp(`var\\(--${style.name}\\)`, 'ig');
127
+ const result = this.getStyleFormatter(style);
128
+
129
+ resultStyles = resultStyles.replace(template, result);
130
+ });
131
+ return resultStyles;
132
+ }
133
+
134
+ private getStyleFormatter(style: IConfigStyles): string {
135
+ switch (style.type) {
136
+ case 'size':
137
+ return `${this.config[style.name] || 0}px`;
138
+ case 'number':
139
+ return parseFloat(this.config[style.name].toString()).toString();
140
+ case 'string':
141
+ return this.config[style.name].toString();
142
+ case 'color':
143
+ return getValidColor(this.config[style.name].toString());
144
+ default:
145
+ return 'none';
146
+ }
147
+ }
148
+
149
+ private renderWidgetInner() {
150
+ if (this.messages.length > 0) {
151
+ this.widget.classList.remove('pw-inbox-widget--empty');
152
+ this.widget.innerHTML = widgetTemplate(this.config.title);
153
+ this.renderMessages();
154
+ } else {
155
+ this.widget.classList.add('pw-inbox-widget--empty');
156
+ const {
157
+ emptyInboxTitle,
158
+ emptyInboxText,
159
+ emptyInboxIconUrl,
160
+ } = this.config;
161
+ this.widget.innerHTML = widgetTemplateEmpty(emptyInboxIconUrl, emptyInboxTitle, emptyInboxText);
162
+ }
163
+ }
164
+
165
+ private renderMessages() {
166
+ this.list = this.widget.querySelector('.pw-inbox_list') || document.createElement('ul');
167
+ this.messages.forEach((message: IInboxMessagePublic) => {
168
+ const messageElement = document.createElement('li');
169
+ messageElement.className = 'pw-inbox_item';
170
+ messageElement.classList.toggle('pw-new', !message.isRead);
171
+ messageElement.classList.toggle('pw-unread', !message.isActionPerformed);
172
+ messageElement.setAttribute('data-pw-inbox-message-id', message.code);
173
+ messageElement.innerHTML = messageTemplate(message);
174
+
175
+ this.list.appendChild(messageElement);
176
+ this.messagesElements[message.code] = messageElement;
177
+ });
178
+ }
179
+
180
+ private updateCounter(count: number) {
181
+ this.count = count;
182
+ this.trigger.setAttribute('data-pw-count', `${this.count}`);
183
+ this.trigger.classList.toggle('pw-empty', this.count === 0);
184
+ }
185
+
186
+ private updateInboxMessages(messages: Array<IInboxMessagePublic>) {
187
+ this.messages = messages.sort(({ sendDate: sendDateOne }, { sendDate: sendDateTwo }) => compareBySendDate(sendDateOne, sendDateTwo));
188
+ this.renderWidgetInner();
189
+ }
190
+
191
+ openWidget() {
192
+ this.isOpened = true;
193
+ this.widget.classList.add('pw-open');
194
+ document.addEventListener('click', this.onWidgetClickHandler);
195
+ window.addEventListener('scroll', this.onWindowScrollHandler);
196
+ window.addEventListener('resize', this.onWindowScrollHandler);
197
+ this.markVisibleItemsAsRead();
198
+ if (this.messages.length > 0) {
199
+ this.list.addEventListener('scroll', this.markVisibleItemsAsRead);
200
+ }
201
+ this.positionWidget();
202
+ }
203
+
204
+ closeWidget() {
205
+ this.isOpened = false;
206
+ document.removeEventListener('click', this.onWidgetClickHandler);
207
+ document.removeEventListener('click', this.onWindowScrollHandler);
208
+ window.removeEventListener('resize', this.onWindowScrollHandler);
209
+ this.updateReadStatus();
210
+ if (this.messages.length > 0) {
211
+ this.list.removeEventListener('scroll', this.markVisibleItemsAsRead);
212
+ }
213
+ this.widget.classList.remove('pw-open', 'pw-top', 'pw-bottom', 'pw-right', 'pw-left');
214
+ this.widget.removeAttribute('style');
215
+ }
216
+
217
+ private positionWidget() {
218
+ if (!this.isOpened) {
219
+ return;
220
+ }
221
+ if (this.widgetParent === document.body) {
222
+ this.defaultPlaceWidget();
223
+ } else {
224
+ this.customPlaceWidget();
225
+ }
226
+ }
227
+
228
+ private customPlaceWidget() {
229
+ const { position } = this.config;
230
+ this.widgetParent.style.position = 'relative';
231
+ this.widget.classList.add('pw-inbox-widget--inset');
232
+ this.widget.classList.add(`pw-${position}`);
233
+ }
234
+
235
+ private defaultPlaceWidget() {
236
+ const position = this.pw.initParams.inboxWidget!.position ? this.config.position : this.getDefaultPosition();
237
+
238
+ const widgetRect = this.widget.getBoundingClientRect();
239
+ if (!document.documentElement) {
240
+ return;
241
+ }
242
+ const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
243
+ const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
244
+
245
+ if (widgetRect.width + (this.defaultMargin * 2) > windowWidth) {
246
+ this.widget.style.width = `${windowWidth - (this.defaultMargin * 2)}px`;
247
+ }
248
+ if (widgetRect.height + 24 > windowHeight) {
249
+ this.widget.style.height = `${windowHeight - (this.defaultMargin * 2)}px`;
250
+ }
251
+
252
+ switch (position) {
253
+ case 'top': {
254
+ this.alignWidgetTop();
255
+ break;
256
+ }
257
+ case 'right': {
258
+ this.alignWidgetRight();
259
+ break;
260
+ }
261
+ case 'left': {
262
+ this.alignWidgetLeft();
263
+ break;
264
+ }
265
+ case 'bottom': {
266
+ this.alignWidgetBottom();
267
+ break;
268
+ }
269
+ }
270
+ }
271
+
272
+ private alignWidgetTop() {
273
+ const triggerRect = this.trigger.getBoundingClientRect();
274
+ const widgetRect = this.widget.getBoundingClientRect();
275
+ if (!document.documentElement) {
276
+ return;
277
+ }
278
+ const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
279
+ const arrow: HTMLElement = this.widget.querySelector('.pw-inbox__arrow') || document.createElement('div');
280
+
281
+ this.widget.classList.add('pw-top');
282
+
283
+ let left = pageXOffset + triggerRect.left + Math.floor(triggerRect.width / 2) - Math.floor(widgetRect.width / 2);
284
+
285
+ const isUnderLeft = left < pageXOffset;
286
+ const isUnderRight = left + widgetRect.width > pageXOffset + windowWidth;
287
+
288
+ if (isUnderLeft) {
289
+ left = pageXOffset + this.defaultMargin;
290
+ }
291
+ if (isUnderRight) {
292
+ left = pageXOffset + windowWidth - widgetRect.width - this.defaultMargin;
293
+ }
294
+
295
+ const top = pageYOffset + triggerRect.top - widgetRect.height;
296
+
297
+ this.alignWidgetElement(left, top);
298
+ arrow.style.left = `${triggerRect.left + Math.floor(triggerRect.width / 2) - left}px`;
299
+
300
+ const topMargin = this.widget.getBoundingClientRect().top;
301
+ const isUnderTop = topMargin < 0;
302
+ if (isUnderTop) {
303
+ const newHeight = this.widget.getBoundingClientRect().height + topMargin - this.defaultMargin;
304
+ const newTop = this.widget.getBoundingClientRect().top - topMargin + this.defaultMargin;
305
+ this.widget.style.height = `${newHeight}px`;
306
+ this.widget.style.top = `${newTop}px`;
307
+ }
308
+ }
309
+
310
+ private alignWidgetRight() {
311
+ const triggerRect = this.trigger.getBoundingClientRect();
312
+ const widgetRect = this.widget.getBoundingClientRect();
313
+ if (!document.documentElement) {
314
+ return;
315
+ }
316
+ const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
317
+ const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
318
+ const arrow: HTMLElement = this.widget.querySelector('.pw-inbox__arrow') || document.createElement('div');
319
+
320
+ this.widget.classList.add('pw-right');
321
+
322
+ let top = pageYOffset + triggerRect.top + Math.floor(triggerRect.height / 2) - Math.floor(widgetRect.height / 2);
323
+
324
+ const isUnderTop = top < pageYOffset;
325
+ const isUnderBottom = pageYOffset + windowHeight < top + widgetRect.height;
326
+
327
+ if (isUnderTop) {
328
+ top = pageYOffset + this.defaultMargin;
329
+ }
330
+
331
+ if (isUnderBottom) {
332
+ top = pageYOffset + windowHeight - widgetRect.height - this.defaultMargin;
333
+ }
334
+
335
+ const left = pageXOffset + triggerRect.left + triggerRect.width;
336
+
337
+ this.alignWidgetElement(left, top);
338
+ arrow.style.top = `${triggerRect.top + Math.floor(triggerRect.height / 2) - top}px`;
339
+
340
+ const rightMargin = windowWidth - this.widget.getBoundingClientRect().right;
341
+ const isUnderRight = rightMargin < this.defaultMargin;
342
+ if (isUnderRight) {
343
+ const newWidth = this.widget.getBoundingClientRect().width + rightMargin - this.defaultMargin;
344
+ this.widget.style.width = `${newWidth}px`;
345
+ }
346
+ }
347
+
348
+ private alignWidgetLeft() {
349
+ const triggerRect = this.trigger.getBoundingClientRect();
350
+ const widgetRect = this.widget.getBoundingClientRect();
351
+ if (!document.documentElement) {
352
+ return;
353
+ }
354
+ const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
355
+ const arrow: HTMLElement = this.widget.querySelector('.pw-inbox__arrow') || document.createElement('div');
356
+
357
+ this.widget.classList.add('pw-left');
358
+
359
+ let top = pageYOffset + triggerRect.top + Math.floor(triggerRect.height / 2) - Math.floor(widgetRect.height / 2);
360
+
361
+ const isUnderTop = top < pageYOffset;
362
+ const isUnderBottom = pageYOffset + windowHeight < top + widgetRect.height;
363
+
364
+ if (isUnderTop) {
365
+ top = pageYOffset + this.defaultMargin;
366
+ }
367
+
368
+ if (isUnderBottom) {
369
+ top = pageYOffset + windowHeight - widgetRect.height - this.defaultMargin;
370
+ }
371
+
372
+ const left = pageXOffset + triggerRect.left - widgetRect.width;
373
+
374
+ this.alignWidgetElement(left, top);
375
+ arrow.style.top = `${triggerRect.top + Math.floor(triggerRect.height / 2) - top}px`;
376
+
377
+ const leftMargin = this.widget.getBoundingClientRect().left;
378
+ const isUnderLeft = leftMargin < 0;
379
+ if (isUnderLeft) {
380
+ const newWidth = this.widget.getBoundingClientRect().width + leftMargin - this.defaultMargin;
381
+ const newLeft = this.widget.getBoundingClientRect().left - leftMargin;
382
+ this.widget.style.width = `${newWidth}px`;
383
+ this.widget.style.left = `${newLeft}px`;
384
+ }
385
+ }
386
+
387
+ private alignWidgetBottom() {
388
+ const triggerRect = this.trigger.getBoundingClientRect();
389
+ const widgetRect = this.widget.getBoundingClientRect();
390
+ if (!document.documentElement) {
391
+ return;
392
+ }
393
+ const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
394
+ const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
395
+ const arrow: HTMLElement = this.widget.querySelector('.pw-inbox__arrow') || document.createElement('div');
396
+
397
+ this.widget.classList.add('pw-bottom');
398
+
399
+ let left = pageXOffset + triggerRect.left + Math.floor(triggerRect.width / 2) - Math.floor(widgetRect.width / 2);
400
+
401
+ const isUnderLeft = left < pageXOffset;
402
+ const isUnderRight = left + widgetRect.width > pageXOffset + windowWidth;
403
+
404
+ if (isUnderLeft) {
405
+ left = pageXOffset + this.defaultMargin;
406
+ }
407
+ if (isUnderRight) {
408
+ left = pageXOffset + windowWidth - widgetRect.width - 12;
409
+ }
410
+
411
+ const top = pageYOffset + triggerRect.top + triggerRect.height;
412
+
413
+ this.alignWidgetElement(left, top);
414
+ arrow.style.left = `${triggerRect.left + Math.floor(triggerRect.width / 2) - left}px`;
415
+
416
+ const bottomRange = windowHeight - this.widget.getBoundingClientRect().bottom;
417
+ const isUnderBottom = bottomRange < this.defaultMargin;
418
+ if (isUnderBottom) {
419
+ const newHeight = this.widget.getBoundingClientRect().height + bottomRange - this.defaultMargin;
420
+ this.widget.style.height = `${newHeight}px`;
421
+ }
422
+ }
423
+
424
+ private alignWidgetElement(left: number, top: number) {
425
+ this.widget.style.left = `${left}px`;
426
+ this.widget.style.top = `${top}px`;
427
+ }
428
+
429
+ private getDefaultPosition(): string {
430
+ const { left, top, width, height } = this.trigger.getBoundingClientRect();
431
+ if (!document.documentElement) {
432
+ return '';
433
+ }
434
+ const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
435
+ const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
436
+
437
+ const antiMargins: { [key: string]: number } = {
438
+ right: left,
439
+ bottom: top,
440
+ left: windowWidth - (left + width),
441
+ top: windowHeight - (top + height),
442
+ };
443
+
444
+ const leastMargin = Math.min(left, top, antiMargins.left, antiMargins.top);
445
+
446
+ let position = 'bottom';
447
+
448
+ Object.keys(antiMargins).forEach((key) => {
449
+ if (antiMargins[key] === leastMargin) {
450
+ position = key;
451
+ }
452
+ });
453
+
454
+ return position;
455
+ }
456
+
457
+ private addListeners() {
458
+ this.trigger.addEventListener('click', this.onTriggerClickHandler);
459
+
460
+ this.pw.push(['onPutNewMessageToInboxStore', () => {
461
+ this.updateInbox();
462
+ }]);
463
+ this.pw.push(['onUpdateInboxMessages', () => {
464
+ this.updateInbox();
465
+ }]);
466
+ }
467
+
468
+ private markVisibleItemsAsRead() {
469
+ if (this.messages.length === 0) {
470
+ return;
471
+ }
472
+ const scrollTop = (this.list.clientHeight + this.list.scrollTop) - 50;
473
+ Object.keys(this.messagesElements).forEach((code: string) => {
474
+ // check message exists and user saw the message in viewport
475
+ if (!this.messagesElements[code] || this.messagesElements[code].offsetTop > scrollTop) {
476
+ return;
477
+ }
478
+
479
+ const message = this.messages.find((message: IInboxMessagePublic): boolean => message.code === code);
480
+
481
+ // mark messages as read
482
+ if (!!message && !message.isRead && !(this.readItems.indexOf(code) + 1)) {
483
+ this.readItems.push(code);
484
+ }
485
+ });
486
+ }
487
+
488
+ private updateReadStatus() {
489
+ this.pw.pwinbox.readMessagesWithCodes(this.readItems).then(this.updateInbox);
490
+ }
491
+
492
+ private updateInbox() {
493
+ this.pw.pwinbox.loadMessages().then((messages: Array<IInboxMessagePublic>) => {
494
+ this.updateInboxMessages(messages);
495
+ });
496
+ this.pw.pwinbox.unreadMessagesCount().then((count: number) => {
497
+ this.updateCounter(count);
498
+ });
499
+ }
500
+
501
+ private performMessageAction(code: string) {
502
+ this.pw.pwinbox.performActionForMessageWithCode(code)
503
+ .then(() => {
504
+ this.updateInbox();
505
+ });
506
+ }
507
+
508
+ private removeMessages(messages: Array<string>) {
509
+ messages.forEach((code: string) => {
510
+ this.readItems = this.readItems.slice(this.readItems.indexOf(code), 1);
511
+ });
512
+ this.pw.pwinbox.deleteMessagesWithCodes(messages)
513
+ .then(() => {
514
+ this.updateInbox();
515
+ });
516
+ }
517
+
518
+ // handlers
519
+
520
+ private onTriggerClickHandler(event: MouseEvent) {
521
+ event.stopPropagation();
522
+ if (!event.target) {
523
+ return;
524
+ }
525
+
526
+ this.toggle();
527
+ }
528
+
529
+ private onWidgetClickHandler(event: MouseEvent) {
530
+ if (!event.target) {
531
+ return;
532
+ }
533
+
534
+ const itemElement = (<HTMLElement>event.target).closest('.pw-inbox_item');
535
+
536
+ if (!itemElement) {
537
+ this.toggle();
538
+ return;
539
+ }
540
+
541
+ const messageCode = itemElement.getAttribute('data-pw-inbox-message-id');
542
+
543
+ if (!messageCode) {
544
+ return;
545
+ }
546
+
547
+ const removeIconClick = (<HTMLElement>event.target).closest('.pw-inbox_item-remove');
548
+
549
+ if (removeIconClick) {
550
+ this.removeMessages([messageCode]);
551
+ return;
552
+ }
553
+
554
+ this.performMessageAction(messageCode);
555
+ }
556
+
557
+ private onWindowScrollHandler() {
558
+ if (this.isFixed && this.isOpened) {
559
+ this.toggle();
560
+ return;
561
+ }
562
+ this.positionWidget();
563
+ }
564
+ }
@@ -0,0 +1,49 @@
1
+ import { type IConfigStyles, type IInboxWidgetConfig } from './inbox_widget.types';
2
+
3
+ export const MILLISECONDS_IN_DAY = 60 * 60 * 24 * 1000;
4
+ export const MILLISECONDS_IN_HOUR = 60 * 60 * 1000;
5
+ export const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
6
+
7
+ export const CONFIG_STYLES: Array<IConfigStyles> = [
8
+ { name: 'widgetWidth', type: 'size' },
9
+ { name: 'borderRadius', type: 'size' },
10
+ { name: 'zIndex', type: 'number' },
11
+ { name: 'fontFamily', type: 'string' },
12
+ { name: 'bgColor', type: 'color' },
13
+ { name: 'textColor', type: 'color' },
14
+ { name: 'arrowBorderColor', type: 'color' },
15
+ { name: 'borderColor', type: 'color' },
16
+ { name: 'badgeBgColor', type: 'color' },
17
+ { name: 'badgeTextColor', type: 'color' },
18
+ { name: 'timeTextColor', type: 'color' },
19
+ { name: 'messageTitleColor', type: 'color' },
20
+ { name: 'emptyInboxTitleColor', type: 'color' },
21
+ { name: 'emptyInboxTextColor', type: 'color' },
22
+ ];
23
+
24
+ export const DEFAULT_CONFIG: IInboxWidgetConfig = {
25
+ enable: false,
26
+ triggerId: 'pwInbox',
27
+ position: 'bottom',
28
+ appendTo: 'body',
29
+ title: 'Inbox',
30
+ bgColor: '#ffffff',
31
+ textColor: '#333333',
32
+ fontFamily: 'inherit',
33
+ borderRadius: 4,
34
+ borderColor: 'transparent',
35
+ badgeBgColor: '#ff4c00',
36
+ badgeTextColor: '#ffffff',
37
+ widgetWidth: 350,
38
+ zIndex: 100,
39
+ messageTitleColor: '#7a7a7a',
40
+ timeTextColor: '#c4c4c4',
41
+ emptyInboxTitle: 'You\'re all caught up',
42
+ emptyInboxTitleColor: '#333333',
43
+ emptyInboxText: 'There are no new messages. Stay tuned!',
44
+ emptyInboxTextColor: '#7a7a7a',
45
+ emptyInboxIconUrl: 'https://pushon.pushwoosh.com/static/icon-empty-inbox.png',
46
+ arrowBorderColor: 'rgba(0,0,0,.1)',
47
+ };
48
+
49
+ export const COLOR_TEST_REGEXP = /^(#([\da-f]{3}){1,2}$|(rgb|hsl)a\((\d{1,3}%?,\s?){3}(1|0?\.\d+)\)$|(rgb|hsl)\(\d{1,3}%?(,\s?\d{1,3}%?){2}\)$)/;