quikchat 1.1.17 → 1.2.4

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 (43) hide show
  1. package/README.md +167 -276
  2. package/dist/build-manifest.json +157 -0
  3. package/dist/quikchat-md-full.cjs.js +2742 -0
  4. package/dist/quikchat-md-full.cjs.js.map +1 -0
  5. package/dist/quikchat-md-full.cjs.min.js +10 -0
  6. package/dist/quikchat-md-full.cjs.min.js.map +1 -0
  7. package/dist/quikchat-md-full.esm.js +2740 -0
  8. package/dist/quikchat-md-full.esm.js.map +1 -0
  9. package/dist/quikchat-md-full.esm.min.js +10 -0
  10. package/dist/quikchat-md-full.esm.min.js.map +1 -0
  11. package/dist/quikchat-md-full.umd.js +2748 -0
  12. package/dist/quikchat-md-full.umd.js.map +1 -0
  13. package/dist/quikchat-md-full.umd.min.js +10 -0
  14. package/dist/quikchat-md-full.umd.min.js.map +1 -0
  15. package/dist/quikchat-md.cjs.js +1641 -0
  16. package/dist/quikchat-md.cjs.js.map +1 -0
  17. package/dist/quikchat-md.cjs.min.js +8 -0
  18. package/dist/quikchat-md.cjs.min.js.map +1 -0
  19. package/dist/quikchat-md.esm.js +1639 -0
  20. package/dist/quikchat-md.esm.js.map +1 -0
  21. package/dist/quikchat-md.esm.min.js +8 -0
  22. package/dist/quikchat-md.esm.min.js.map +1 -0
  23. package/dist/quikchat-md.umd.js +1647 -0
  24. package/dist/quikchat-md.umd.js.map +1 -0
  25. package/dist/quikchat-md.umd.min.js +8 -0
  26. package/dist/quikchat-md.umd.min.js.map +1 -0
  27. package/dist/quikchat.cjs.js +454 -1729
  28. package/dist/quikchat.cjs.js.map +1 -1
  29. package/dist/quikchat.cjs.min.js +1 -1
  30. package/dist/quikchat.cjs.min.js.map +1 -1
  31. package/dist/quikchat.css +753 -226
  32. package/dist/quikchat.esm.js +454 -1729
  33. package/dist/quikchat.esm.js.map +1 -1
  34. package/dist/quikchat.esm.min.js +1 -1
  35. package/dist/quikchat.esm.min.js.map +1 -1
  36. package/dist/quikchat.min.css +1 -1
  37. package/dist/quikchat.react.js +63 -0
  38. package/dist/quikchat.umd.js +454 -1729
  39. package/dist/quikchat.umd.js.map +1 -1
  40. package/dist/quikchat.umd.min.js +1 -1
  41. package/dist/quikchat.umd.min.js.map +1 -1
  42. package/package.json +59 -39
  43. package/dist/quikchat.d.ts +0 -194
@@ -9,37 +9,76 @@
9
9
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
10
10
  return n;
11
11
  }
12
- function _arrayWithoutHoles(r) {
13
- if (Array.isArray(r)) return _arrayLikeToArray(r);
14
- }
15
12
  function _classCallCheck(a, n) {
16
13
  if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
17
14
  }
18
15
  function _defineProperties(e, r) {
19
16
  for (var t = 0; t < r.length; t++) {
20
17
  var o = r[t];
21
- o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
18
+ o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o);
22
19
  }
23
20
  }
24
21
  function _createClass(e, r, t) {
25
22
  return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
26
- writable: !1
23
+ writable: false
27
24
  }), e;
28
25
  }
26
+ function _createForOfIteratorHelper(r, e) {
27
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
28
+ if (!t) {
29
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
30
+ t && (r = t);
31
+ var n = 0,
32
+ F = function () {};
33
+ return {
34
+ s: F,
35
+ n: function () {
36
+ return n >= r.length ? {
37
+ done: true
38
+ } : {
39
+ done: false,
40
+ value: r[n++]
41
+ };
42
+ },
43
+ e: function (r) {
44
+ throw r;
45
+ },
46
+ f: F
47
+ };
48
+ }
49
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
50
+ }
51
+ var o,
52
+ a = true,
53
+ u = false;
54
+ return {
55
+ s: function () {
56
+ t = t.call(r);
57
+ },
58
+ n: function () {
59
+ var r = t.next();
60
+ return a = r.done, r;
61
+ },
62
+ e: function (r) {
63
+ u = true, o = r;
64
+ },
65
+ f: function () {
66
+ try {
67
+ a || null == t.return || t.return();
68
+ } finally {
69
+ if (u) throw o;
70
+ }
71
+ }
72
+ };
73
+ }
29
74
  function _defineProperty(e, r, t) {
30
75
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
31
76
  value: t,
32
- enumerable: !0,
33
- configurable: !0,
34
- writable: !0
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true
35
80
  }) : e[r] = t, e;
36
81
  }
37
- function _iterableToArray(r) {
38
- if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
39
- }
40
- function _nonIterableSpread() {
41
- throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
42
- }
43
82
  function ownKeys(e, r) {
44
83
  var t = Object.keys(e);
45
84
  if (Object.getOwnPropertySymbols) {
@@ -53,7 +92,7 @@
53
92
  function _objectSpread2(e) {
54
93
  for (var r = 1; r < arguments.length; r++) {
55
94
  var t = null != arguments[r] ? arguments[r] : {};
56
- r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
95
+ r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
57
96
  _defineProperty(e, r, t[r]);
58
97
  }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
59
98
  Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
@@ -61,18 +100,15 @@
61
100
  }
62
101
  return e;
63
102
  }
64
- function _toConsumableArray(r) {
65
- return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
66
- }
67
103
  function _toPrimitive(t, r) {
68
104
  if ("object" != typeof t || !t) return t;
69
105
  var e = t[Symbol.toPrimitive];
70
106
  if (void 0 !== e) {
71
- var i = e.call(t, r || "default");
107
+ var i = e.call(t, r);
72
108
  if ("object" != typeof i) return i;
73
109
  throw new TypeError("@@toPrimitive must return a primitive value.");
74
110
  }
75
- return ("string" === r ? String : Number)(t);
111
+ return (String )(t);
76
112
  }
77
113
  function _toPropertyKey(t) {
78
114
  var i = _toPrimitive(t, "string");
@@ -86,378 +122,11 @@
86
122
  }
87
123
  }
88
124
 
89
- // Auto-generated version file - DO NOT EDIT MANUALLY
90
- // This file is automatically updated by tools/updateVersion.js
91
-
92
- var quikchatVersion = {
93
- version: "1.1.17",
94
- license: "BSD-2",
95
- url: "https://github/deftio/quikchat",
96
- fun: true
97
- };
98
-
99
- /**
100
- * Simplified virtual scrolling implementation for QuikChat
101
- * @private
102
- */
103
- var SimpleVirtualScroller = /*#__PURE__*/function () {
104
- function SimpleVirtualScroller(container) {
105
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
106
- _classCallCheck(this, SimpleVirtualScroller);
107
- this.container = container;
108
- this.items = [];
109
- this.itemHeight = options.itemHeight || 80; // Default/estimate height
110
- this.buffer = options.buffer || 5;
111
- this.visibleRange = {
112
- start: 0,
113
- end: 0
114
- };
115
- this.renderedElements = new Map();
116
- this.itemHeights = new Map(); // Cache actual heights
117
- this.itemPositions = new Map(); // Cache positions
118
- this.totalHeight = 0;
119
- this.onRenderItem = options.onRenderItem || function () {};
120
- this.sanitizer = options.sanitizer || null; // Content sanitizer
121
-
122
- this._initStructure();
123
- this._attachScrollListener();
124
- }
125
- return _createClass(SimpleVirtualScroller, [{
126
- key: "_initStructure",
127
- value: function _initStructure() {
128
- var _this = this;
129
- var existingClasses = this.container.className;
130
- this.container.innerHTML = '';
131
- this.container.className = existingClasses;
132
- this.container.style.position = 'relative';
133
- this.container.style.overflow = 'auto';
134
-
135
- // Ensure container has height
136
- if (this.container.offsetHeight === 0) {
137
- // Try to get height from computed style or parent
138
- var computedHeight = window.getComputedStyle(this.container).height;
139
- if (computedHeight === '0px' || computedHeight === 'auto') {
140
- // If still no height, use a reasonable default
141
- this.container.style.height = '400px';
142
- console.warn('QuikChat Virtual Scrolling: Container has no height, setting to 400px');
143
- }
144
- }
145
- this.spacer = document.createElement('div');
146
- this.spacer.style.cssText = 'position: absolute; top: 0; left: 0; width: 1px; pointer-events: none; z-index: -1;';
147
- this.content = document.createElement('div');
148
- this.content.style.cssText = 'position: relative; width: 100%;';
149
- this.container.appendChild(this.spacer);
150
- this.container.appendChild(this.content);
151
-
152
- // Initial render after structure is set up
153
- setTimeout(function () {
154
- _this._updateVisibleRange();
155
- _this._renderVisibleItems();
156
- }, 0);
157
- }
158
- }, {
159
- key: "_attachScrollListener",
160
- value: function _attachScrollListener() {
161
- var _this2 = this;
162
- var ticking = false;
163
- this.container.addEventListener('scroll', function () {
164
- if (!ticking) {
165
- requestAnimationFrame(function () {
166
- _this2._updateVisibleRange();
167
- _this2._renderVisibleItems();
168
- ticking = false;
169
- });
170
- ticking = true;
171
- }
172
- });
173
- }
174
- }, {
175
- key: "_getItemHeight",
176
- value: function _getItemHeight(index) {
177
- // Return cached height or estimate
178
- return this.itemHeights.get(index) || this.itemHeight;
179
- }
180
- }, {
181
- key: "_getItemPosition",
182
- value: function _getItemPosition(index) {
183
- // Return cached position or calculate
184
- if (this.itemPositions.has(index)) {
185
- return this.itemPositions.get(index);
186
- }
187
-
188
- // Calculate position based on previous items
189
- var position = 0;
190
- for (var i = 0; i < index; i++) {
191
- position += this._getItemHeight(i);
192
- }
193
- this.itemPositions.set(index, position);
194
- return position;
195
- }
196
- }, {
197
- key: "_recalculatePositions",
198
- value: function _recalculatePositions(fromIndex) {
199
- // Recalculate positions from a specific index onwards
200
- var position = fromIndex > 0 ? this._getItemPosition(fromIndex) : 0;
201
- for (var i = fromIndex; i < this.items.length; i++) {
202
- this.itemPositions.set(i, position);
203
- position += this._getItemHeight(i);
204
-
205
- // Update position of rendered elements
206
- var element = this.renderedElements.get(i);
207
- if (element) {
208
- element.style.top = "".concat(this.itemPositions.get(i), "px");
209
- }
210
- }
211
-
212
- // Update total height
213
- this.totalHeight = position;
214
- this.spacer.style.height = "".concat(this.totalHeight, "px");
215
- }
216
- }, {
217
- key: "_updateVisibleRange",
218
- value: function _updateVisibleRange() {
219
- var scrollTop = this.container.scrollTop;
220
- var viewportHeight = this.container.clientHeight;
221
-
222
- // If container has no height, don't render anything (avoid infinite loop)
223
- if (viewportHeight === 0) {
224
- this.visibleRange = {
225
- start: 0,
226
- end: 0
227
- };
228
- return;
229
- }
230
-
231
- // Find first visible item based on positions
232
- var startIndex = 0;
233
- var endIndex = this.items.length;
234
-
235
- // Use cached positions when available
236
- if (this.itemPositions.size > 0) {
237
- // Find start index
238
- for (var i = 0; i < this.items.length; i++) {
239
- var pos = this._getItemPosition(i);
240
- if (pos + this._getItemHeight(i) > scrollTop) {
241
- startIndex = Math.max(0, i - this.buffer);
242
- break;
243
- }
244
- }
245
-
246
- // Find end index
247
- for (var _i = startIndex; _i < this.items.length; _i++) {
248
- var _pos = this._getItemPosition(_i);
249
- if (_pos > scrollTop + viewportHeight) {
250
- endIndex = Math.min(this.items.length, _i + this.buffer);
251
- break;
252
- }
253
- }
254
- } else {
255
- // Fallback to estimate if no positions cached yet
256
- startIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer);
257
- endIndex = Math.min(this.items.length, Math.ceil((scrollTop + viewportHeight) / this.itemHeight) + this.buffer);
258
- }
259
- this.visibleRange = {
260
- start: startIndex,
261
- end: endIndex
262
- };
263
- }
264
- }, {
265
- key: "_renderVisibleItems",
266
- value: function _renderVisibleItems() {
267
- var _this3 = this;
268
- var _this$visibleRange = this.visibleRange,
269
- start = _this$visibleRange.start,
270
- end = _this$visibleRange.end;
271
-
272
- // Remove elements outside range
273
- this.renderedElements.forEach(function (element, index) {
274
- if (index < start || index >= end) {
275
- element.remove();
276
- _this3.renderedElements["delete"](index);
277
- // Don't clear height cache - we might need it again
278
- }
279
- });
280
-
281
- // Add new visible elements
282
- var _loop = function _loop(i) {
283
- var item = _this3.items[i];
284
- if (!item || _this3.renderedElements.has(i)) return 1; // continue
285
- var element = _this3._createItemElement(item, i);
286
- element.style.position = 'absolute';
287
- element.style.top = "".concat(_this3._getItemPosition(i), "px");
288
- element.style.left = '0';
289
- element.style.right = '0';
290
- _this3.renderedElements.set(i, element);
291
- _this3.content.appendChild(element);
292
-
293
- // Measure actual height after rendering
294
- requestAnimationFrame(function () {
295
- if (_this3.renderedElements.has(i)) {
296
- var actualHeight = element.offsetHeight;
297
- if (actualHeight && actualHeight !== _this3._getItemHeight(i)) {
298
- _this3.itemHeights.set(i, actualHeight);
299
- _this3._recalculatePositions(i);
300
- }
301
- }
302
- });
303
- };
304
- for (var i = start; i < end; i++) {
305
- if (_loop(i)) continue;
306
- }
307
- this.onRenderItem(this.content);
308
- }
309
- }, {
310
- key: "_createItemElement",
311
- value: function _createItemElement(item, index) {
312
- var messageDiv = document.createElement('div');
313
- messageDiv.className = "quikchat-message quikchat-message-".concat(item.align || 'left', " quikchat-msgid-").concat(String(item.msgid).padStart(10, '0'));
314
- messageDiv.setAttribute('data-index', index);
315
- messageDiv.setAttribute('data-msgid', item.msgid);
316
- messageDiv.setAttribute('role', 'article');
317
- messageDiv.setAttribute('aria-label', "Message from ".concat(item.userString || 'user'));
318
- if (item.tags && item.tags.length > 0) {
319
- item.tags.forEach(function (tag) {
320
- return messageDiv.classList.add("quikchat-tag-".concat(tag));
321
- });
322
- }
323
- var userDiv = document.createElement('div');
324
- userDiv.className = 'quikchat-message-user';
325
- userDiv.innerHTML = this.sanitizer ? this.sanitizer(item.userString || '') : item.userString || '';
326
- userDiv.setAttribute('aria-label', 'User');
327
- var contentDiv = document.createElement('div');
328
- contentDiv.className = 'quikchat-message-content';
329
- contentDiv.innerHTML = this.sanitizer ? this.sanitizer(item.content || '') : item.content || '';
330
- contentDiv.setAttribute('aria-label', 'Message content');
331
- messageDiv.appendChild(userDiv);
332
- messageDiv.appendChild(contentDiv);
333
- if (!item.visible) {
334
- messageDiv.style.display = 'none';
335
- }
336
- return messageDiv;
337
- }
338
- }, {
339
- key: "addItem",
340
- value: function addItem(item) {
341
- this.items.push(item);
342
- var index = this.items.length - 1;
343
-
344
- // Calculate position for new item
345
- var position = index > 0 ? this._getItemPosition(index - 1) + this._getItemHeight(index - 1) : 0;
346
- this.itemPositions.set(index, position);
347
-
348
- // Update total height (using estimate for new item)
349
- this.totalHeight = position + this.itemHeight;
350
- this.spacer.style.height = "".concat(this.totalHeight, "px");
351
- if (index >= this.visibleRange.start && index < this.visibleRange.end) {
352
- this._renderVisibleItems();
353
- }
354
- if (item.scrollIntoView) {
355
- this.container.scrollTop = this.container.scrollHeight;
356
- }
357
- return index;
358
- }
359
- }, {
360
- key: "addItems",
361
- value: function addItems(items) {
362
- var _this$items;
363
- // Batch add items for better performance
364
- var startLength = this.items.length;
365
- (_this$items = this.items).push.apply(_this$items, _toConsumableArray(items));
366
-
367
- // Calculate positions for new items
368
- var position = startLength > 0 ? this._getItemPosition(startLength - 1) + this._getItemHeight(startLength - 1) : 0;
369
- for (var i = startLength; i < this.items.length; i++) {
370
- this.itemPositions.set(i, position);
371
- position += this.itemHeight; // Use estimate for new items
372
- }
373
- this.totalHeight = position;
374
- this.spacer.style.height = "".concat(this.totalHeight, "px");
375
-
376
- // Update visible range and render
377
- this._updateVisibleRange();
378
- this._renderVisibleItems();
379
-
380
- // Handle scrollIntoView for last item if needed
381
- if (items.length > 0 && items[items.length - 1].scrollIntoView) {
382
- this.container.scrollTop = this.container.scrollHeight;
383
- }
384
- }
385
- }, {
386
- key: "clear",
387
- value: function clear() {
388
- this.items = [];
389
- this.renderedElements.clear();
390
- this.itemHeights.clear();
391
- this.itemPositions.clear();
392
- this.totalHeight = 0;
393
- this.content.innerHTML = '';
394
- this.spacer.style.height = '0px';
395
- this.visibleRange = {
396
- start: 0,
397
- end: 0
398
- };
399
-
400
- // Force update after clear
401
- this._updateVisibleRange();
402
- this._renderVisibleItems();
403
- }
404
- }, {
405
- key: "updateItem",
406
- value: function updateItem(index, updates) {
407
- if (index >= 0 && index < this.items.length) {
408
- this.items[index] = _objectSpread2(_objectSpread2({}, this.items[index]), updates);
409
- if (this.renderedElements.has(index)) {
410
- this._renderVisibleItems();
411
- }
412
- }
413
- }
414
- }, {
415
- key: "destroy",
416
- value: function destroy() {
417
- this.container.innerHTML = '';
418
- this.items = [];
419
- this.renderedElements.clear();
420
- }
421
- }]);
422
- }();
423
- /**
424
- * QuikChat - A zero-dependency JavaScript chat widget for modern web applications
425
- * @class quikchat
426
- */
427
125
  var quikchat = /*#__PURE__*/function () {
428
126
  /**
429
- * Creates a new QuikChat instance
430
- * @constructor
431
- * @param {string|HTMLElement} parentElement - CSS selector or DOM element to attach the chat widget to
432
- * @param {Function} [onSend] - Callback function triggered when user sends a message
433
- * @param {Object} [options] - Configuration options
434
- * @param {string} [options.theme='quikchat-theme-light'] - CSS theme class name
435
- * @param {boolean} [options.trackHistory=true] - Whether to track message history
436
- * @param {Object} [options.titleArea] - Title area configuration
437
- * @param {string} [options.titleArea.title='Chat'] - Title text/HTML
438
- * @param {boolean} [options.titleArea.show=false] - Whether to show title area initially
439
- * @param {'left'|'center'|'right'} [options.titleArea.align='center'] - Title alignment
440
- * @param {Object} [options.messagesArea] - Messages area configuration
441
- * @param {boolean} [options.messagesArea.alternating=true] - Alternate message colors
442
- * @param {Object} [options.inputArea] - Input area configuration
443
- * @param {boolean} [options.inputArea.show=true] - Whether to show input area initially
444
- * @param {boolean} [options.sendOnEnter=true] - Send message on Enter key
445
- * @param {boolean} [options.sendOnShiftEnter=false] - Send message on Shift+Enter
446
- * @param {string} [options.instanceClass=''] - Additional CSS class for the widget instance
447
- * @example
448
- * // Basic usage
449
- * const chat = new quikchat('#chat-container', (instance, message) => {
450
- * console.log('User sent:', message);
451
- * });
452
- *
453
- * @example
454
- * // With options
455
- * const chat = new quikchat('#chat', handleMessage, {
456
- * theme: 'quikchat-theme-dark',
457
- * titleArea: { title: 'Support Chat', show: true },
458
- * sendOnEnter: false,
459
- * sendOnShiftEnter: true
460
- * });
127
+ *
128
+ * @param string or DOM element parentElement
129
+ * @param {*} meta
461
130
  */
462
131
  function quikchat(parentElement) {
463
132
  var onSend = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
@@ -466,6 +135,7 @@
466
135
  var defaultOpts = {
467
136
  theme: 'quikchat-theme-light',
468
137
  trackHistory: true,
138
+ showTimestamps: false,
469
139
  titleArea: {
470
140
  title: "Chat",
471
141
  show: false,
@@ -473,55 +143,19 @@
473
143
  },
474
144
  messagesArea: {
475
145
  alternating: true
476
- },
477
- inputArea: {
478
- show: true
479
- },
480
- sendOnEnter: true,
481
- sendOnShiftEnter: false,
482
- instanceClass: '',
483
- virtualScrolling: true,
484
- // Default to true for better performance
485
- virtualScrollingThreshold: 500,
486
- // Lower threshold since it performs so well
487
- // i18n support
488
- lang: 'en',
489
- dir: 'ltr',
490
- // 'ltr' or 'rtl'
491
- translations: {
492
- 'en': {
493
- sendButton: 'Send',
494
- inputPlaceholder: 'Type a message...',
495
- titleDefault: 'Chat'
496
- }
497
- },
498
- // Security: content sanitizer callback
499
- sanitizer: null // null = no sanitization (backward compatible)
146
+ }
500
147
  };
501
148
  var meta = _objectSpread2(_objectSpread2({}, defaultOpts), options); // merge options with defaults
502
149
 
503
- // Merge user translations with defaults
504
- if (options.translations) {
505
- meta.translations = _objectSpread2(_objectSpread2({}, defaultOpts.translations), options.translations);
506
- }
507
150
  if (typeof parentElement === 'string') {
508
151
  parentElement = document.querySelector(parentElement);
509
152
  }
510
- //console.log(parentElement, meta);
511
153
  this._parentElement = parentElement;
512
154
  this._theme = meta.theme;
513
155
  this._onSend = onSend ? onSend : function () {}; // call back function for onSend
514
-
515
- // i18n settings
516
- this.lang = meta.lang;
517
- this.dir = meta.dir;
518
- this.translations = meta.translations;
519
- this.currentTranslations = this.translations[this.lang] || this.translations['en'];
156
+ this._messageFormatter = meta.messageFormatter || null;
157
+ this._sanitize = meta.sanitize || false;
520
158
  this._createWidget();
521
- if (meta.instanceClass) {
522
- this._chatWidget.classList.add(meta.instanceClass);
523
- }
524
-
525
159
  // title area
526
160
  if (meta.titleArea) {
527
161
  this.titleAreaSetContents(meta.titleArea.title, meta.titleArea.align);
@@ -535,176 +169,34 @@
535
169
  if (meta.messagesArea) {
536
170
  this.messagesAreaAlternateColors(meta.messagesArea.alternating);
537
171
  }
538
-
539
- // input area
540
- if (meta.inputArea) {
541
- if (meta.inputArea.show === true) this.inputAreaShow();else this.inputAreaHide();
172
+ // timestamps
173
+ if (meta.showTimestamps) {
174
+ this.messagesAreaShowTimestamps(true);
175
+ }
176
+ // direction (ltr/rtl)
177
+ if (meta.direction) {
178
+ this.setDirection(meta.direction);
542
179
  }
543
180
  // plumbing
544
181
  this._attachEventListeners();
545
- this.trackHistory = meta.trackHistory || true;
182
+ this.trackHistory = meta.trackHistory !== false;
546
183
  this._historyLimit = 10000000;
547
184
  this._history = [];
548
- this._activeTags = new Set();
549
-
550
- // send on enter / shift enter
551
- this.sendOnEnter = meta.sendOnEnter;
552
- this.sendOnShiftEnter = meta.sendOnShiftEnter;
553
-
554
- // Virtual scrolling setup
555
- this.virtualScrollingEnabled = meta.virtualScrolling;
556
- this.virtualScrollingThreshold = meta.virtualScrollingThreshold;
557
- this.virtualScroller = null;
558
-
559
- // Sanitizer setup
560
- this._sanitizer = meta.sanitizer || null;
561
-
562
- // Don't initialize virtual scrolling immediately - wait for threshold
563
- // Virtual scrolling will be initialized when message count exceeds threshold
564
185
  }
565
-
566
- /**
567
- * Initialize virtual scrolling
568
- * @private
569
- */
570
186
  return _createClass(quikchat, [{
571
- key: "_initVirtualScrolling",
572
- value: function _initVirtualScrolling() {
573
- var _this4 = this;
574
- if (this.virtualScrollingEnabled && this._messagesArea && !this.virtualScroller) {
575
- // Check if we've hit the threshold (or about to with the next message)
576
- if (this._history.length >= this.virtualScrollingThreshold - 1) {
577
- this.virtualScroller = new SimpleVirtualScroller(this._messagesArea, {
578
- itemHeight: 80,
579
- buffer: 5,
580
- sanitizer: this._sanitizer,
581
- // Pass sanitizer to virtual scroller
582
- onRenderItem: function onRenderItem(content) {
583
- // Apply alternating colors if enabled
584
- if (_this4._messagesArea.classList.contains('quikchat-messages-area-alt')) {
585
- _this4._updateMessageStyles();
586
- }
587
- }
588
- });
589
-
590
- // Migrate existing messages to virtual scroller
591
- this._migrateToVirtualScrolling();
592
- }
593
- }
594
- }
595
-
596
- /**
597
- * Migrate existing messages to virtual scrolling
598
- * @private
599
- */
600
- }, {
601
- key: "_migrateToVirtualScrolling",
602
- value: function _migrateToVirtualScrolling() {
603
- var _this5 = this;
604
- if (!this.virtualScroller) return;
605
-
606
- // Don't clear DOM here - virtual scroller will do it in _initStructure
607
-
608
- // Add all messages to virtual scroller
609
- var items = this._history.map(function (msg) {
610
- return {
611
- msgid: msg.msgid,
612
- content: msg.content,
613
- userString: msg.userString,
614
- align: msg.align,
615
- role: msg.role,
616
- visible: msg.visible,
617
- tags: msg.tags || [],
618
- scrollIntoView: false
619
- };
620
- });
621
- this.virtualScroller.addItems(items);
622
-
623
- // Force a render in case container needs time to get dimensions
624
- setTimeout(function () {
625
- if (_this5.virtualScroller) {
626
- _this5.virtualScroller._updateVisibleRange();
627
- _this5.virtualScroller._renderVisibleItems();
628
- }
629
- }, 10);
630
- }
631
- }, {
632
187
  key: "_createWidget",
633
188
  value: function _createWidget() {
634
- var sendButtonText = this.currentTranslations.sendButton || 'Send';
635
- var inputPlaceholder = this.currentTranslations.inputPlaceholder || 'Type a message...';
636
- var widgetHTML = "\n <div class=\"quikchat-base ".concat(this._theme, "\" dir=\"").concat(this.dir, "\" lang=\"").concat(this.lang, "\" role=\"region\" aria-label=\"Chat widget\">\n <div class=\"quikchat-title-area\" role=\"heading\" aria-level=\"2\">\n <span style=\"font-size: 1.5em; font-weight: 600;\">Title Area</span>\n </div>\n <div class=\"quikchat-messages-area\" role=\"log\" aria-live=\"polite\" aria-label=\"Chat messages\"></div>\n <div class=\"quikchat-input-area\" role=\"form\" aria-label=\"Message input\">\n <textarea class=\"quikchat-input-textbox\" \n placeholder=\"").concat(inputPlaceholder, "\"\n aria-label=\"Type your message\"\n autocomplete=\"off\"\n autocapitalize=\"sentences\"></textarea>\n <button class=\"quikchat-input-send-btn\" \n aria-label=\"Send message\"\n type=\"button\">").concat(sendButtonText, "</button>\n </div>\n </div>\n ");
189
+ var widgetHTML = "\n <div class=\"quikchat-base ".concat(this.theme, "\">\n <div class=\"quikchat-title-area\"></div>\n <div class=\"quikchat-messages-wrapper\"><div class=\"quikchat-messages-area\" role=\"log\" aria-live=\"polite\" aria-label=\"Chat messages\"></div><button class=\"quikchat-scroll-bottom\" aria-label=\"Scroll to bottom\"></button></div>\n <div class=\"quikchat-input-area\">\n <textarea class=\"quikchat-input-textbox\" rows=\"1\" aria-label=\"Type a message\"></textarea>\n <button class=\"quikchat-input-send-btn\">Send</button>\n </div>\n </div>\n ");
637
190
  this._parentElement.innerHTML = widgetHTML;
638
191
  this._chatWidget = this._parentElement.querySelector('.quikchat-base');
639
192
  this._titleArea = this._chatWidget.querySelector('.quikchat-title-area');
193
+ this._messagesWrapper = this._chatWidget.querySelector('.quikchat-messages-wrapper');
640
194
  this._messagesArea = this._chatWidget.querySelector('.quikchat-messages-area');
195
+ this._scrollBottomBtn = this._messagesWrapper.querySelector('.quikchat-scroll-bottom');
641
196
  this._inputArea = this._chatWidget.querySelector('.quikchat-input-area');
642
197
  this._textEntry = this._inputArea.querySelector('.quikchat-input-textbox');
643
198
  this._sendButton = this._inputArea.querySelector('.quikchat-input-send-btn');
644
199
  this.msgid = 0;
645
-
646
- // Add mobile viewport handling
647
- this._setupMobileSupport();
648
- }
649
-
650
- /**
651
- * Setup mobile support - prevent zoom on input focus and handle virtual keyboard
652
- * @private
653
- */
654
- }, {
655
- key: "_setupMobileSupport",
656
- value: function _setupMobileSupport() {
657
- var _this6 = this;
658
- // Prevent zoom on input focus for mobile
659
- var meta = document.querySelector('meta[name="viewport"]');
660
- if (!meta) {
661
- meta = document.createElement('meta');
662
- meta.name = 'viewport';
663
- document.head.appendChild(meta);
664
- }
665
-
666
- // Store original content
667
- this._originalViewport = meta.content;
668
-
669
- // Prevent zoom on focus
670
- this._textEntry.addEventListener('focus', function () {
671
- if (window.innerWidth <= 768) {
672
- // Mobile device width
673
- meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
674
- }
675
- });
676
- this._textEntry.addEventListener('blur', function () {
677
- if (_this6._originalViewport) {
678
- meta.content = _this6._originalViewport;
679
- } else {
680
- meta.content = 'width=device-width, initial-scale=1.0';
681
- }
682
- });
683
-
684
- // Handle virtual keyboard resize
685
- if ('visualViewport' in window) {
686
- window.visualViewport.addEventListener('resize', function () {
687
- _this6._handleVirtualKeyboard();
688
- });
689
- }
690
- }
691
-
692
- /**
693
- * Handle virtual keyboard appearance/disappearance
694
- * @private
695
- */
696
- }, {
697
- key: "_handleVirtualKeyboard",
698
- value: function _handleVirtualKeyboard() {
699
- // Adjust layout when virtual keyboard appears
700
- var keyboardHeight = window.innerHeight - window.visualViewport.height;
701
- if (keyboardHeight > 0) {
702
- // Keyboard is visible - adjust chat widget height
703
- this._chatWidget.style.paddingBottom = "".concat(keyboardHeight, "px");
704
- } else {
705
- // Keyboard hidden - restore original padding
706
- this._chatWidget.style.paddingBottom = '';
707
- }
708
200
  }
709
201
 
710
202
  /**
@@ -713,40 +205,55 @@
713
205
  }, {
714
206
  key: "_attachEventListeners",
715
207
  value: function _attachEventListeners() {
716
- var _this7 = this;
717
- this._sendButton.addEventListener('click', function (event) {
718
- event.preventDefault();
719
- _this7._onSend(_this7, _this7._textEntry.value.trim());
720
- });
721
- window.addEventListener('resize', function () {
722
- return _this7._handleContainerResize();
723
- });
724
- this._chatWidget.addEventListener('resize', function () {
725
- return _this7._handleContainerResize();
208
+ var _this = this;
209
+ this._sendButton.addEventListener('click', function () {
210
+ var text = _this._textEntry.value.trim();
211
+ if (text === '') return;
212
+ _this._onSend(_this, text);
726
213
  });
727
214
  this._textEntry.addEventListener('keydown', function (event) {
728
- // Check if Shift + Enter is pressed then we just do carraige
215
+ // Check if Shift + Enter is pressed
729
216
  if (event.shiftKey && event.keyCode === 13) {
730
- // Prevent default behavior (adding new line)
731
- if (_this7.sendOnShiftEnter) {
732
- event.preventDefault();
733
- _this7._onSend(_this7, _this7._textEntry.value.trim());
734
- }
735
- } else if (event.keyCode === 13) {
736
- // Enter but not Shift + Enter
737
- if (_this7.sendOnEnter) {
738
- event.preventDefault();
739
- _this7._onSend(_this7, _this7._textEntry.value.trim());
740
- }
217
+ event.preventDefault();
218
+ var text = _this._textEntry.value.trim();
219
+ if (text === '') return;
220
+ _this._onSend(_this, text);
741
221
  }
742
222
  });
223
+
224
+ // Auto-grow textarea
225
+ this._textEntry.addEventListener('input', function () {
226
+ return _this._autoGrowTextarea();
227
+ });
743
228
  this._messagesArea.addEventListener('scroll', function () {
744
- var _this7$_messagesArea = _this7._messagesArea,
745
- scrollTop = _this7$_messagesArea.scrollTop,
746
- scrollHeight = _this7$_messagesArea.scrollHeight,
747
- clientHeight = _this7$_messagesArea.clientHeight;
748
- _this7.userScrolledUp = scrollTop + clientHeight < scrollHeight;
229
+ var _this$_messagesArea = _this._messagesArea,
230
+ scrollTop = _this$_messagesArea.scrollTop,
231
+ scrollHeight = _this$_messagesArea.scrollHeight,
232
+ clientHeight = _this$_messagesArea.clientHeight;
233
+ _this.userScrolledUp = scrollTop + clientHeight < scrollHeight - 1;
234
+ _this._updateScrollBottomBtn();
235
+ });
236
+
237
+ // Scroll-to-bottom button
238
+ this._scrollBottomBtn.addEventListener('click', function () {
239
+ return _this.scrollToBottom();
240
+ });
241
+
242
+ // Ctrl+End to scroll to bottom
243
+ this._chatWidget.addEventListener('keydown', function (event) {
244
+ if (event.ctrlKey && event.key === 'End') {
245
+ event.preventDefault();
246
+ _this.scrollToBottom();
247
+ }
749
248
  });
249
+
250
+ // Use ResizeObserver to detect parent container resize
251
+ if (typeof ResizeObserver !== 'undefined') {
252
+ this._resizeObserver = new ResizeObserver(function () {
253
+ return _this._handleContainerResize();
254
+ });
255
+ this._resizeObserver.observe(this._parentElement);
256
+ }
750
257
  }
751
258
 
752
259
  // set the onSend function callback.
@@ -761,54 +268,16 @@
761
268
  value: function setCallbackonMessageAdded(callback) {
762
269
  this._onMessageAdded = callback;
763
270
  }
764
-
765
- /**
766
- * Sets the callback function for when content is appended to a message
767
- * @param {Function} callback - Function to call when content is appended
768
- * @param {quikchat} callback.instance - The QuikChat instance
769
- * @param {number} callback.msgId - The ID of the message being appended to
770
- * @param {string} callback.content - The content being appended
771
- * @since 1.1.15
772
- * @example
773
- * chat.setCallbackonMessageAppend((instance, msgId, content) => {
774
- * console.log(`Appended "${content}" to message ${msgId}`);
775
- * });
776
- */
777
271
  }, {
778
272
  key: "setCallbackonMessageAppend",
779
273
  value: function setCallbackonMessageAppend(callback) {
780
274
  this._onMessageAppend = callback;
781
275
  }
782
-
783
- /**
784
- * Sets the callback function for when a message's content is replaced
785
- * @param {Function} callback - Function to call when content is replaced
786
- * @param {quikchat} callback.instance - The QuikChat instance
787
- * @param {number} callback.msgId - The ID of the message being replaced
788
- * @param {string} callback.content - The new content
789
- * @since 1.1.15
790
- * @example
791
- * chat.setCallbackonMessageReplace((instance, msgId, content) => {
792
- * console.log(`Message ${msgId} replaced with: ${content}`);
793
- * });
794
- */
795
276
  }, {
796
277
  key: "setCallbackonMessageReplace",
797
278
  value: function setCallbackonMessageReplace(callback) {
798
279
  this._onMessageReplace = callback;
799
280
  }
800
-
801
- /**
802
- * Sets the callback function for when a message is deleted
803
- * @param {Function} callback - Function to call when a message is deleted
804
- * @param {quikchat} callback.instance - The QuikChat instance
805
- * @param {number} callback.msgId - The ID of the deleted message
806
- * @since 1.1.15
807
- * @example
808
- * chat.setCallbackonMessageDelete((instance, msgId) => {
809
- * console.log(`Message ${msgId} was deleted`);
810
- * });
811
- */
812
281
  }, {
813
282
  key: "setCallbackonMessageDelete",
814
283
  value: function setCallbackonMessageDelete(callback) {
@@ -816,58 +285,32 @@
816
285
  }
817
286
 
818
287
  // Public methods
819
- /**
820
- * Toggles the visibility of the title area
821
- * @returns {void}
822
- */
823
288
  }, {
824
289
  key: "titleAreaToggle",
825
290
  value: function titleAreaToggle() {
826
- this._titleArea.style.display === 'none' ? this.titleAreaShow() : this.titleAreaHide();
291
+ if (this._titleArea.style.display === 'none') {
292
+ this.titleAreaShow();
293
+ } else {
294
+ this.titleAreaHide();
295
+ }
827
296
  }
828
-
829
- /**
830
- * Shows the title area
831
- * @returns {void}
832
- */
833
297
  }, {
834
298
  key: "titleAreaShow",
835
299
  value: function titleAreaShow() {
836
300
  this._titleArea.style.display = '';
837
- this._adjustMessagesAreaHeight();
838
301
  }
839
-
840
- /**
841
- * Hides the title area
842
- * @returns {void}
843
- */
844
302
  }, {
845
303
  key: "titleAreaHide",
846
304
  value: function titleAreaHide() {
847
305
  this._titleArea.style.display = 'none';
848
- this._adjustMessagesAreaHeight();
849
306
  }
850
-
851
- /**
852
- * Sets the contents of the title area
853
- * @param {string} title - HTML content to display in the title area
854
- * @param {'left'|'center'|'right'} [align='center'] - Text alignment
855
- * @returns {void}
856
- * @example
857
- * chat.titleAreaSetContents('<h2>Support Chat</h2>', 'center');
858
- */
859
307
  }, {
860
308
  key: "titleAreaSetContents",
861
309
  value: function titleAreaSetContents(title) {
862
310
  var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'center';
863
- this._titleArea.innerHTML = this._sanitizeContent(title);
311
+ this._titleArea.innerHTML = title;
864
312
  this._titleArea.style.textAlign = align;
865
313
  }
866
-
867
- /**
868
- * Gets the current contents of the title area
869
- * @returns {string} The HTML content of the title area
870
- */
871
314
  }, {
872
315
  key: "titleAreaGetContents",
873
316
  value: function titleAreaGetContents() {
@@ -876,48 +319,133 @@
876
319
  }, {
877
320
  key: "inputAreaToggle",
878
321
  value: function inputAreaToggle() {
879
- this._inputArea.classList.toggle('hidden');
880
- this._inputArea.style.display === 'none' ? this.inputAreaShow() : this.inputAreaHide();
322
+ if (this._inputArea.style.display === 'none') {
323
+ this.inputAreaShow();
324
+ } else {
325
+ this.inputAreaHide();
326
+ }
881
327
  }
882
328
  }, {
883
329
  key: "inputAreaShow",
884
330
  value: function inputAreaShow() {
885
331
  this._inputArea.style.display = '';
886
- this._adjustMessagesAreaHeight();
887
332
  }
888
333
  }, {
889
334
  key: "inputAreaHide",
890
335
  value: function inputAreaHide() {
891
336
  this._inputArea.style.display = 'none';
892
- this._adjustMessagesAreaHeight();
893
337
  }
894
338
  }, {
895
- key: "_adjustMessagesAreaHeight",
896
- value: function _adjustMessagesAreaHeight() {
897
- var hiddenElements = _toConsumableArray(this._chatWidget.children).filter(function (child) {
898
- return child.classList.contains('hidden');
899
- });
900
- var totalHiddenHeight = hiddenElements.reduce(function (sum, child) {
901
- return sum + child.offsetHeight;
902
- }, 0);
903
- var containerHeight = this._chatWidget.offsetHeight;
904
- this._messagesArea.style.height = "calc(100% - ".concat(containerHeight - totalHiddenHeight, "px)");
339
+ key: "inputAreaSetEnabled",
340
+ value: function inputAreaSetEnabled(enabled) {
341
+ this._textEntry.disabled = !enabled;
342
+ this._sendButton.disabled = !enabled;
343
+ }
344
+ }, {
345
+ key: "inputAreaSetButtonText",
346
+ value: function inputAreaSetButtonText(text) {
347
+ this._sendButton.textContent = text;
348
+ }
349
+ }, {
350
+ key: "inputAreaGetButtonText",
351
+ value: function inputAreaGetButtonText() {
352
+ return this._sendButton.textContent;
353
+ }
354
+ }, {
355
+ key: "setDirection",
356
+ value: function setDirection(dir) {
357
+ var d = dir === 'rtl' ? 'rtl' : 'ltr';
358
+ this._chatWidget.setAttribute('dir', d);
359
+ if (d === 'rtl') {
360
+ this._chatWidget.classList.add('quikchat-rtl');
361
+ } else {
362
+ this._chatWidget.classList.remove('quikchat-rtl');
363
+ }
364
+ }
365
+ }, {
366
+ key: "getDirection",
367
+ value: function getDirection() {
368
+ return this._chatWidget.getAttribute('dir') || 'ltr';
905
369
  }
906
370
  }, {
907
371
  key: "_handleContainerResize",
908
372
  value: function _handleContainerResize() {
909
- this._adjustMessagesAreaHeight();
910
- this._adjustSendButtonWidth();
911
- return true;
373
+ // Layout is handled by CSS flexbox — no JS height calculation needed.
374
+ // This hook exists for future use or custom resize callbacks.
912
375
  }
913
376
  }, {
914
- key: "_adjustSendButtonWidth",
915
- value: function _adjustSendButtonWidth() {
916
- var sendButtonText = this._sendButton.textContent.trim();
917
- var fontSize = parseFloat(getComputedStyle(this._sendButton).fontSize);
918
- var minWidth = fontSize * sendButtonText.length + 16;
919
- this._sendButton.style.minWidth = "".concat(minWidth, "px");
920
- return true;
377
+ key: "scrollToBottom",
378
+ value: function scrollToBottom() {
379
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
380
+ this.userScrolledUp = false;
381
+ this._updateScrollBottomBtn();
382
+ }
383
+ }, {
384
+ key: "_updateScrollBottomBtn",
385
+ value: function _updateScrollBottomBtn() {
386
+ if (this.userScrolledUp) {
387
+ this._scrollBottomBtn.classList.add('quikchat-scroll-bottom-visible');
388
+ } else {
389
+ this._scrollBottomBtn.classList.remove('quikchat-scroll-bottom-visible');
390
+ }
391
+ }
392
+ }, {
393
+ key: "_autoGrowTextarea",
394
+ value: function _autoGrowTextarea() {
395
+ var el = this._textEntry;
396
+ el.style.height = 'auto';
397
+ var maxHeight = parseInt(getComputedStyle(el).getPropertyValue('--quikchat-input-max-height')) || 120;
398
+ el.style.height = Math.min(el.scrollHeight, maxHeight) + 'px';
399
+ el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';
400
+ }
401
+ }, {
402
+ key: "_formatTimestamp",
403
+ value: function _formatTimestamp(isoString) {
404
+ var d = new Date(isoString);
405
+ var h = d.getHours();
406
+ var m = String(d.getMinutes()).padStart(2, '0');
407
+ var ampm = h >= 12 ? 'PM' : 'AM';
408
+ var h12 = h % 12 || 12;
409
+ return h12 + ':' + m + ' ' + ampm;
410
+ }
411
+ }, {
412
+ key: "messagesAreaShowTimestamps",
413
+ value: function messagesAreaShowTimestamps(show) {
414
+ if (show) {
415
+ this._messagesArea.classList.add('quikchat-show-timestamps');
416
+ } else {
417
+ this._messagesArea.classList.remove('quikchat-show-timestamps');
418
+ }
419
+ }
420
+ }, {
421
+ key: "messagesAreaShowTimestampsGet",
422
+ value: function messagesAreaShowTimestampsGet() {
423
+ return this._messagesArea.classList.contains('quikchat-show-timestamps');
424
+ }
425
+ }, {
426
+ key: "messagesAreaShowTimestampsToggle",
427
+ value: function messagesAreaShowTimestampsToggle() {
428
+ this._messagesArea.classList.toggle('quikchat-show-timestamps');
429
+ }
430
+ }, {
431
+ key: "_escapeHTML",
432
+ value: function _escapeHTML(str) {
433
+ var div = document.createElement('div');
434
+ div.textContent = str;
435
+ return div.innerHTML;
436
+ }
437
+ }, {
438
+ key: "_processContent",
439
+ value: function _processContent(content) {
440
+ if (this._sanitize === true) {
441
+ content = this._escapeHTML(content);
442
+ } else if (typeof this._sanitize === 'function') {
443
+ content = this._sanitize(content);
444
+ }
445
+ if (this._messageFormatter) {
446
+ content = this._messageFormatter(content);
447
+ }
448
+ return content;
921
449
  }
922
450
 
923
451
  //messagesArea functions
@@ -942,151 +470,67 @@
942
470
  value: function messagesAreaAlternateColorsGet() {
943
471
  return this._messagesArea.classList.contains('quikchat-messages-area-alt');
944
472
  }
945
- /**
946
- * Adds a new message to the chat with full configuration options
947
- * @param {Object} input - Message configuration object
948
- * @param {string} [input.content=''] - Message content (HTML allowed)
949
- * @param {string} [input.userString='user'] - Display name for the message sender
950
- * @param {'left'|'right'|'center'} [input.align='right'] - Message alignment
951
- * @param {string} [input.role='user'] - Role identifier (user, assistant, system)
952
- * @param {number} [input.userID=-1] - User ID for the message
953
- * @param {string|false} [input.timestamp=false] - ISO timestamp or false for auto
954
- * @param {string|false} [input.updatedtime=false] - Last updated timestamp
955
- * @param {boolean|'smart'} [input.scrollIntoView=true] - Scroll behavior (true/false/'smart')
956
- * @param {boolean} [input.visible=true] - Whether message is initially visible
957
- * @param {string[]} [input.tags=[]] - Tags for message categorization
958
- * @returns {number} Message ID for the newly added message
959
- * @example
960
- * const msgId = chat.messageAddFull({
961
- * content: 'Hello!',
962
- * userString: 'Bot',
963
- * align: 'left',
964
- * scrollIntoView: 'smart',
965
- * tags: ['greeting']
966
- * });
967
- */
473
+ // message functions
968
474
  }, {
969
475
  key: "messageAddFull",
970
476
  value: function messageAddFull() {
971
- var _this8 = this;
972
477
  var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
973
478
  content: "",
974
479
  userString: "user",
975
480
  align: "right",
976
481
  role: "user",
977
- userID: -1,
978
- timestamp: false,
979
- updatedtime: false,
980
- scrollIntoView: true,
981
- visible: true,
982
- tags: []
482
+ userID: -1
983
483
  };
984
484
  var msgid = this.msgid;
485
+ var messageDiv = document.createElement('div');
486
+ var msgidClass = 'quikchat-msgid-' + String(msgid).padStart(10, '0');
487
+ messageDiv.classList.add('quikchat-message', msgidClass);
488
+ messageDiv.classList.add('quikchat-role-' + (input.role || 'user'));
489
+ messageDiv.classList.add('quikchat-align-' + (input.align || 'right'));
985
490
  this.msgid++;
986
- var messageDiv = null; // Initialize messageDiv to null
491
+ messageDiv.classList.add(this._messagesArea.children.length % 2 === 1 ? 'quikchat-message-1' : 'quikchat-message-2');
987
492
 
988
- // Check if we should initialize virtual scrolling
989
- if (this.virtualScrollingEnabled && !this.virtualScroller && this._history.length >= this.virtualScrollingThreshold - 1) {
990
- this._initVirtualScrolling();
493
+ // Visibility: default true, hidden messages get display:none
494
+ var visible = input.visible !== false;
495
+ if (!visible) {
496
+ messageDiv.style.display = 'none';
991
497
  }
992
498
 
993
- // If virtual scrolling is enabled, use the virtual scroller
994
- if (this.virtualScroller) {
995
- var messageData = {
996
- msgid: msgid,
997
- content: input.content,
998
- userString: input.userString,
999
- align: input.align,
1000
- role: input.role,
1001
- visible: input.visible !== undefined ? input.visible : true,
1002
- tags: input.tags || [],
1003
- scrollIntoView: input.scrollIntoView
1004
- };
1005
-
1006
- // Add tags to active tags set
1007
- if (Array.isArray(input.tags)) {
1008
- input.tags.forEach(function (tag) {
1009
- if (typeof tag === 'string' && /^[a-zA-Z0-9-]+$/.test(tag)) {
1010
- _this8._activeTags.add(tag);
1011
- }
1012
- });
1013
- }
1014
-
1015
- // Add to virtual scroller
1016
- this.virtualScroller.addItem(messageData);
1017
-
1018
- // Clear text entry
1019
- this._textEntry.value = '';
1020
- this._adjustMessagesAreaHeight();
1021
- } else {
1022
- // Original DOM-based implementation
1023
- messageDiv = document.createElement('div');
1024
- var msgidClass = 'quikchat-msgid-' + String(msgid).padStart(10, '0');
1025
- 'quikchat-userid-' + String(input.userString).padStart(10, '0'); // hash this..
1026
- messageDiv.classList.add('quikchat-message', msgidClass, 'quikchat-structure');
1027
- messageDiv.setAttribute('role', 'article');
1028
- messageDiv.setAttribute('aria-label', "Message from ".concat(input.userString || 'user'));
1029
- if (Array.isArray(input.tags)) {
1030
- input.tags.forEach(function (tag) {
1031
- if (typeof tag === 'string' && /^[a-zA-Z0-9-]+$/.test(tag)) {
1032
- messageDiv.classList.add("quikchat-tag-".concat(tag));
1033
- _this8._activeTags.add(tag);
1034
- }
1035
- });
1036
- }
1037
- var userDiv = document.createElement('div');
1038
- userDiv.innerHTML = this._sanitizeContent(input.userString);
1039
- userDiv.classList.add('quikchat-user-label');
1040
- userDiv.style.textAlign = input.align;
1041
- var contentDiv = document.createElement('div');
1042
- contentDiv.classList.add('quikchat-message-content');
1043
-
1044
- // Determine text alignment for right-aligned messages
1045
- if (input.align === "right") {
1046
- var isMultiLine = input.content.includes("\n");
1047
- var isLong = input.content.length > 50; // Adjust length threshold
1048
-
1049
- if (isMultiLine || isLong) {
1050
- contentDiv.classList.add("quikchat-right-multiline");
1051
- } else {
1052
- contentDiv.classList.add("quikchat-right-singleline");
1053
- }
1054
- }
1055
- contentDiv.innerHTML = this._sanitizeContent(input.content);
1056
- messageDiv.appendChild(userDiv);
1057
- messageDiv.appendChild(contentDiv);
1058
- this._messagesArea.appendChild(messageDiv);
1059
- if (input.visible === false) {
1060
- messageDiv.style.display = 'none';
1061
- }
1062
-
1063
- // Handle scroll behavior based on scrollIntoView parameter
1064
- // 'smart' = only scroll if near bottom, true = always scroll, false = never scroll
1065
- if (input.scrollIntoView === true) {
1066
- this.messageScrollToBottom();
1067
- } else if (input.scrollIntoView === 'smart' && !this.userScrolledUp) {
1068
- this.messageScrollToBottom();
1069
- }
1070
- // If scrollIntoView is false, don't scroll at all
499
+ // Tags: array of strings for group-based visibility control
500
+ var tags = Array.isArray(input.tags) ? input.tags.slice() : [];
501
+ var userDiv = document.createElement('div');
502
+ userDiv.classList.add('quikchat-user-label');
503
+ userDiv.style.textAlign = input.align;
504
+ userDiv.innerHTML = input.userString;
505
+ var contentDiv = document.createElement('div');
506
+ contentDiv.classList.add('quikchat-message-content');
507
+ contentDiv.style.textAlign = input.align;
508
+ contentDiv.innerHTML = this._processContent(input.content);
509
+ var timestamp = new Date().toISOString();
510
+ var timestampSpan = document.createElement('span');
511
+ timestampSpan.classList.add('quikchat-timestamp');
512
+ timestampSpan.textContent = this._formatTimestamp(timestamp);
513
+ messageDiv.appendChild(userDiv);
514
+ messageDiv.appendChild(contentDiv);
515
+ messageDiv.appendChild(timestampSpan);
516
+ this._messagesArea.appendChild(messageDiv);
1071
517
 
1072
- this._textEntry.value = '';
1073
- this._adjustMessagesAreaHeight();
1074
- this._handleShortLongMessageCSS(messageDiv, input.align); // Handle CSS for short/long messages
1075
- this._updateMessageStyles();
518
+ // Scroll to the last message only if the user is not actively scrolling up
519
+ if (!this.userScrolledUp) {
520
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
1076
521
  }
1077
-
1078
- // Add timestamp now, unless it is passed in
1079
- var timestamp = input.timestamp ? input.timestamp : new Date().toISOString();
1080
- var updatedtime = input.updatedtime ? input.updatedtime : timestamp;
1081
- var visible = input.visible !== undefined ? input.visible : true;
522
+ this._textEntry.value = '';
523
+ this._autoGrowTextarea();
524
+ var updatedtime = timestamp;
1082
525
  if (this.trackHistory) {
1083
526
  this._history.push(_objectSpread2(_objectSpread2({
1084
527
  msgid: msgid
1085
528
  }, input), {}, {
1086
529
  visible: visible,
530
+ tags: tags,
1087
531
  timestamp: timestamp,
1088
532
  updatedtime: updatedtime,
1089
- messageDiv: messageDiv || null
533
+ messageDiv: messageDiv
1090
534
  }));
1091
535
  if (this._history.length > this._historyLimit) {
1092
536
  this._history.shift();
@@ -1097,24 +541,6 @@
1097
541
  }
1098
542
  return msgid;
1099
543
  }
1100
-
1101
- /**
1102
- * Adds a new message to the chat (simplified version of messageAddFull)
1103
- * @param {string} [content=''] - Message content (HTML allowed)
1104
- * @param {string} [userString='user'] - Display name for the message sender
1105
- * @param {'left'|'right'|'center'} [align='right'] - Message alignment
1106
- * @param {string} [role='user'] - Role identifier (user, assistant, system)
1107
- * @param {boolean|'smart'} [scrollIntoView=true] - Scroll behavior
1108
- * @param {boolean} [visible=true] - Whether message is initially visible
1109
- * @param {string[]} [tags=[]] - Tags for message categorization
1110
- * @returns {number} Message ID for the newly added message
1111
- * @example
1112
- * // Simple message
1113
- * chat.messageAddNew('Hello!', 'User', 'right');
1114
- *
1115
- * // Bot message with smart scroll
1116
- * chat.messageAddNew('Hi there!', 'Bot', 'left', 'assistant', 'smart');
1117
- */
1118
544
  }, {
1119
545
  key: "messageAddNew",
1120
546
  value: function messageAddNew() {
@@ -1122,115 +548,125 @@
1122
548
  var userString = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "user";
1123
549
  var align = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "right";
1124
550
  var role = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "user";
1125
- var scrollIntoView = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
1126
- var visible = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true;
1127
- var tags = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : [];
1128
- var retvalue = this.messageAddFull({
551
+ return this.messageAddFull({
1129
552
  content: content,
1130
553
  userString: userString,
1131
554
  align: align,
1132
- role: role,
1133
- scrollIntoView: scrollIntoView,
1134
- visible: visible,
1135
- tags: tags
555
+ role: role
1136
556
  });
1137
- // this.messageScrollToBottom();
1138
- return retvalue;
1139
557
  }
1140
-
1141
- /**
1142
- * Removes a message from the chat by its ID
1143
- * @param {number} n - Message ID to remove
1144
- * @returns {boolean} True if message was removed, false if not found
1145
- * @example
1146
- * const success = chat.messageRemove(5);
1147
- */
1148
558
  }, {
1149
559
  key: "messageRemove",
1150
560
  value: function messageRemove(n) {
1151
- // use css selector to remove the message
1152
- var sucess = false;
561
+ var success = false;
1153
562
  try {
1154
563
  this._messagesArea.removeChild(this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0'))));
1155
- sucess = true;
1156
- } catch (error) {
1157
- console.log("{String(n)} : Message ID not found");
564
+ success = true;
565
+ } catch (_error) {
566
+ // Message ID not found
1158
567
  }
1159
- if (sucess) {
1160
- // slow way to remove from history
1161
- //this._history = this._history.filter((item) => item.msgid !== n); // todo make this more efficient
1162
-
1163
- // better way to delete this from history
568
+ if (success) {
1164
569
  this._history.splice(this._history.findIndex(function (item) {
1165
570
  return item.msgid === n;
1166
571
  }), 1);
1167
-
1168
- // Call the onMessageDelete callback if it exists
1169
572
  if (this._onMessageDelete) {
1170
573
  this._onMessageDelete(this, n);
1171
574
  }
1172
575
  }
1173
- return sucess;
576
+ return success;
1174
577
  }
1175
578
  /* returns the message html object from the DOM
1176
579
  */
1177
- /**
1178
- * Gets the DOM element for a message by its ID
1179
- * @param {number} n - Message ID
1180
- * @returns {HTMLElement|null} The message DOM element or null if not found
1181
- */
1182
580
  }, {
1183
581
  key: "messageGetDOMObject",
1184
582
  value: function messageGetDOMObject(n) {
1185
583
  var msg = null;
1186
- // now use css selector to get the message
1187
584
  try {
1188
585
  msg = this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0')));
1189
- } catch (error) {
1190
- console.log("{String(n)} : Message ID not found");
586
+ } catch (_error) {
587
+ // Message ID not found
1191
588
  }
1192
589
  return msg;
1193
590
  }
1194
591
  /* returns the message content only
1195
592
  */
1196
- /**
1197
- * Gets the content of a message by its ID
1198
- * @param {number} n - Message ID
1199
- * @returns {string} The message content or empty string if not found
1200
- */
1201
593
  }, {
1202
594
  key: "messageGetContent",
1203
595
  value: function messageGetContent(n) {
1204
596
  var content = "";
1205
- // now use css selector to get the message
1206
597
  try {
1207
- // get from history..
1208
598
  content = this._history.filter(function (item) {
1209
599
  return item.msgid === n;
1210
600
  })[0].content;
1211
- //content = this._messagesArea.querySelector(`.quikchat-msgid-${String(n).padStart(10, '0')}`).lastChild.textContent;
1212
- } catch (error) {
1213
- console.log("{String(n)} : Message ID not found");
601
+ } catch (_error) {
602
+ // Message ID not found
1214
603
  }
1215
604
  return content;
1216
605
  }
1217
-
1218
- /* returns the DOM Content element of a given message
1219
- */
1220
606
  }, {
1221
- key: "messageGetContentDOMElement",
1222
- value: function messageGetContentDOMElement(n) {
1223
- var contentElement = null;
1224
- // now use css selector to get the message
607
+ key: "messageSetVisible",
608
+ value: function messageSetVisible(n, visible) {
609
+ var msgEl = this.messageGetDOMObject(n);
610
+ if (!msgEl) return false;
611
+ msgEl.style.display = visible ? '' : 'none';
612
+ var item = this._history.find(function (entry) {
613
+ return entry.msgid === n;
614
+ });
615
+ if (item) item.visible = visible;
616
+ return true;
617
+ }
618
+ }, {
619
+ key: "messageGetVisible",
620
+ value: function messageGetVisible(n) {
621
+ var item = this._history.find(function (entry) {
622
+ return entry.msgid === n;
623
+ });
624
+ return item ? item.visible !== false : false;
625
+ }
626
+ }, {
627
+ key: "messageToggleVisible",
628
+ value: function messageToggleVisible(n) {
629
+ var current = this.messageGetVisible(n);
630
+ return this.messageSetVisible(n, !current);
631
+ }
632
+ }, {
633
+ key: "messageSetVisibleByTag",
634
+ value: function messageSetVisibleByTag(tag, visible) {
635
+ var count = 0;
636
+ var _iterator = _createForOfIteratorHelper(this._history),
637
+ _step;
1225
638
  try {
1226
- //contentElement = this._messagesArea.querySelector(`.quikchat-msgid-${String(n).padStart(10, '0')}`).lastChild;
1227
- contentElement = this._history.filter(function (item) {
1228
- return item.msgid === n;
1229
- })[0].messageDiv.lastChild;
1230
- } catch (error) {
1231
- console.log("{String(n)} : Message ID not found");
639
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
640
+ var item = _step.value;
641
+ if (item.tags && item.tags.includes(tag)) {
642
+ this.messageSetVisible(item.msgid, visible);
643
+ count++;
644
+ }
645
+ }
646
+ } catch (err) {
647
+ _iterator.e(err);
648
+ } finally {
649
+ _iterator.f();
1232
650
  }
1233
- return contentElement;
651
+ return count;
652
+ }
653
+ }, {
654
+ key: "messageGetTags",
655
+ value: function messageGetTags(n) {
656
+ var item = this._history.find(function (entry) {
657
+ return entry.msgid === n;
658
+ });
659
+ return item && item.tags ? item.tags.slice() : [];
660
+ }
661
+ }, {
662
+ key: "messageSetTags",
663
+ value: function messageSetTags(n, tags) {
664
+ var item = this._history.find(function (entry) {
665
+ return entry.msgid === n;
666
+ });
667
+ if (!item) return false;
668
+ item.tags = Array.isArray(tags) ? tags.slice() : [];
669
+ return true;
1234
670
  }
1235
671
 
1236
672
  /* append message to the message content
@@ -1240,41 +676,23 @@
1240
676
  value: function messageAppendContent(n, content) {
1241
677
  var success = false;
1242
678
  try {
1243
- // Update history first
1244
- var item = this._history.filter(function (item) {
1245
- return item.msgid === n;
679
+ var msgEl = this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0')));
680
+ var item = this._history.filter(function (entry) {
681
+ return entry.msgid === n;
1246
682
  })[0];
1247
683
  item.content += content;
1248
684
  item.updatedtime = new Date().toISOString();
1249
-
1250
- // If virtual scrolling is active, update the virtual scroller
1251
- if (this.virtualScroller) {
1252
- // Find the item index in virtual scroller
1253
- var index = this.virtualScroller.items.findIndex(function (item) {
1254
- return item.msgid === n;
1255
- });
1256
- if (index >= 0) {
1257
- this.virtualScroller.items[index].content += content; // Don't double-sanitize, it's done on render
1258
- // Re-render if the item is currently visible
1259
- this.virtualScroller.updateItem(index, {
1260
- content: this.virtualScroller.items[index].content
1261
- });
1262
- }
1263
- } else {
1264
- // Regular DOM manipulation
1265
- this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0'))).lastChild.innerHTML += this._sanitizeContent(content);
1266
- }
685
+ msgEl.querySelector('.quikchat-message-content').innerHTML = this._processContent(item.content);
686
+ msgEl.classList.remove('quikchat-typing');
1267
687
  success = true;
1268
-
1269
- // Call the onMessageAppend callback if it exists
688
+ if (!this.userScrolledUp) {
689
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
690
+ }
1270
691
  if (this._onMessageAppend) {
1271
692
  this._onMessageAppend(this, n, content);
1272
693
  }
1273
-
1274
- // Don't auto-scroll on append - let user control this
1275
- // Users can call messageScrollToBottom() if they want to scroll
1276
- } catch (error) {
1277
- console.log("".concat(String(n), " : Message ID not found"));
694
+ } catch (_error) {
695
+ // Message ID not found
1278
696
  }
1279
697
  return success;
1280
698
  }
@@ -1286,405 +704,80 @@
1286
704
  value: function messageReplaceContent(n, content) {
1287
705
  var success = false;
1288
706
  try {
1289
- // Update history first
1290
- var item = this._history.filter(function (item) {
1291
- return item.msgid === n;
707
+ var msgEl = this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0')));
708
+ var item = this._history.filter(function (entry) {
709
+ return entry.msgid === n;
1292
710
  })[0];
1293
711
  item.content = content;
1294
712
  item.updatedtime = new Date().toISOString();
1295
-
1296
- // If virtual scrolling is active, update the virtual scroller
1297
- if (this.virtualScroller) {
1298
- // Find the item index in virtual scroller
1299
- var index = this.virtualScroller.items.findIndex(function (item) {
1300
- return item.msgid === n;
1301
- });
1302
- if (index >= 0) {
1303
- this.virtualScroller.items[index].content = content; // Don't double-sanitize, it's done on render
1304
- // Re-render if the item is currently visible
1305
- this.virtualScroller.updateItem(index, {
1306
- content: content
1307
- });
1308
- }
1309
- } else {
1310
- // Regular DOM manipulation
1311
- this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0'))).lastChild.innerHTML = this._sanitizeContent(content);
1312
- }
713
+ msgEl.querySelector('.quikchat-message-content').innerHTML = this._processContent(content);
714
+ msgEl.classList.remove('quikchat-typing');
1313
715
  success = true;
1314
-
1315
- // Call the onMessageReplace callback if it exists
716
+ if (!this.userScrolledUp) {
717
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
718
+ }
1316
719
  if (this._onMessageReplace) {
1317
720
  this._onMessageReplace(this, n, content);
1318
721
  }
1319
-
1320
- // Don't auto-scroll on append - let user control this
1321
- // Users can call messageScrollToBottom() if they want to scroll
1322
- } catch (error) {
1323
- console.log("".concat(String(n), " : Message ID not found"));
722
+ } catch (_error) {
723
+ // Message ID not found
1324
724
  }
1325
725
  return success;
1326
726
  }
1327
-
1328
- /**
1329
- * Scrolls the messages area to the bottom.
1330
- */
1331
- }, {
1332
- key: "messageScrollToBottom",
1333
- value: function messageScrollToBottom() {
1334
- // Always use scrollTop to avoid page jumping
1335
- // This ensures only the chat container scrolls, not the entire page
1336
- this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
1337
- }
1338
-
1339
- /**
1340
- * Removes the last message from the messages area.
1341
- */
1342
- }, {
1343
- key: "messageRemoveLast",
1344
- value: function messageRemoveLast() {
1345
- // find the last message by id:
1346
- if (this._history.length >= 0) {
1347
- var lastMsgId = this._history[this._history.length - 1].msgid;
1348
- return this.messageRemove(lastMsgId);
1349
- }
1350
- return false;
1351
- }
1352
727
  }, {
1353
- key: "messageSetVisibility",
1354
- value: function messageSetVisibility(msgid, isVisible) {
1355
- var message = this._history.find(function (item) {
1356
- return item.msgid === msgid;
728
+ key: "messageAddTypingIndicator",
729
+ value: function messageAddTypingIndicator() {
730
+ var userString = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
731
+ var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'left';
732
+ var msgid = this.messageAddFull({
733
+ content: '',
734
+ userString: userString,
735
+ align: align,
736
+ role: 'assistant'
1357
737
  });
1358
- if (message && message.messageDiv) {
1359
- message.messageDiv.style.display = isVisible ? '' : 'none';
1360
- message.visible = isVisible;
1361
- this._updateMessageStyles();
1362
- return true;
1363
- }
1364
- return false;
738
+ var msgEl = this.messageGetDOMObject(msgid);
739
+ msgEl.classList.add('quikchat-typing');
740
+ var contentDiv = msgEl.querySelector('.quikchat-message-content');
741
+ contentDiv.innerHTML = '<span class="quikchat-typing-dots"><span>.</span><span>.</span><span>.</span></span>';
742
+ return msgid;
1365
743
  }
1366
744
  }, {
1367
- key: "messageGetVisibility",
1368
- value: function messageGetVisibility(msgid) {
1369
- var message = this._history.find(function (item) {
1370
- return item.msgid === msgid;
1371
- });
1372
- if (message && message.messageDiv) {
1373
- return message.messageDiv.style.display !== 'none';
1374
- }
1375
- return false; // Return false if not found or no messageDiv
745
+ key: "setMessageFormatter",
746
+ value: function setMessageFormatter(formatter) {
747
+ this._messageFormatter = formatter;
1376
748
  }
1377
749
  }, {
1378
- key: "_updateMessageStyles",
1379
- value: function _updateMessageStyles() {
1380
- var visibleMessages = _toConsumableArray(this._messagesArea.children).filter(function (child) {
1381
- return child.style.display !== 'none';
1382
- });
1383
- visibleMessages.forEach(function (messageDiv, index) {
1384
- messageDiv.classList.remove('quikchat-message-1', 'quikchat-message-2');
1385
- messageDiv.classList.add(index % 2 === 0 ? 'quikchat-message-1' : 'quikchat-message-2');
1386
- });
750
+ key: "setSanitize",
751
+ value: function setSanitize(sanitize) {
752
+ this._sanitize = sanitize;
1387
753
  }
1388
754
 
1389
- /**
1390
- * For right sided or centered messages, we need to handle the CSS for short and long messages.
1391
- * for short messages we use simple justifying, for long messages we need to wrap and perform multiline
1392
- * formatting.
1393
- *
1394
- * @param {*} messageElement
1395
- * @returns nothing
1396
- */
1397
- }, {
1398
- key: "_handleShortLongMessageCSS",
1399
- value: function _handleShortLongMessageCSS(messageElement, align) {
1400
- // console.log(messageElement);
1401
- // Reset classes
1402
- messageElement.classList.remove('left-singleline', 'left-multiline', 'center-singleline', 'center-multiline', 'right-singleline', 'right-multiline');
1403
- var contentDiv = messageElement.lastChild;
1404
- window.lastDiv = contentDiv; // for debugging
1405
- // Determine if the message is short or long
1406
-
1407
- var computedStyle = window.getComputedStyle(contentDiv);
1408
-
1409
- // Get the element's height
1410
- var elementHeight = contentDiv.offsetHeight;
1411
-
1412
- // Calculate or estimate line height
1413
- var lineHeight;
1414
- if (computedStyle.lineHeight === "normal") {
1415
- var fontSize = parseFloat(computedStyle.fontSize);
1416
- lineHeight = fontSize * 1.2; // approximate "normal" as 1.2 times font-size
1417
- } else {
1418
- lineHeight = parseFloat(computedStyle.lineHeight);
1419
- }
1420
-
1421
- // Check if the element height is more than one line-height
1422
- var isMultiLine = elementHeight > lineHeight;
1423
-
1424
- // Using scrollHeight and clientHeight to check for overflow (multi-line)
1425
- switch (align) {
1426
- case 'center':
1427
- if (isMultiLine) {
1428
- messageElement.classList.add('center-multiline');
1429
- } else {
1430
- messageElement.classList.add('center-singleline');
1431
- }
1432
- break;
1433
- case 'right':
1434
- if (isMultiLine) {
1435
- messageElement.classList.add('right-multiline');
1436
- } else {
1437
- messageElement.classList.add('right-singleline');
1438
- }
1439
- break;
1440
- case 'left':
1441
- default:
1442
- if (isMultiLine) {
1443
- messageElement.classList.add('left-multiline');
1444
- } else {
1445
- messageElement.classList.add('left-singleline');
1446
- }
1447
- break;
1448
- }
1449
- }
1450
755
  // history functions
1451
756
  /**
1452
- *
1453
- * @param {*} n
1454
- * @param {*} m
757
+ *
758
+ * @param {*} n
759
+ * @param {*} m
1455
760
  * @returns array of history messages
1456
761
  */
1457
- /**
1458
- * Gets a slice of message history
1459
- * @param {number} [n] - Start index (defaults to 0)
1460
- * @param {number} [m] - End index (defaults to history length)
1461
- * @returns {Array} Array of message objects
1462
- * @example
1463
- * // Get first 10 messages
1464
- * const messages = chat.historyGet(0, 10);
1465
- *
1466
- * // Get all messages
1467
- * const allMessages = chat.historyGet();
1468
- */
1469
762
  }, {
1470
763
  key: "historyGet",
1471
764
  value: function historyGet(n, m) {
1472
- if (n == undefined) {
1473
- n = 0;
1474
- m = this._history.length;
765
+ if (n === undefined) {
766
+ return this._history.slice();
1475
767
  }
1476
768
  if (m === undefined) {
1477
- m = n < 0 ? m : n + 1;
1478
- }
1479
- // remember that entries could be deleted. TODO: So we need to return the actual history entries
1480
- // so now we need to find the array index that correspondes to messageIds n (start) and m (end)
1481
-
1482
- return this._history.slice(n, m);
1483
- }
1484
-
1485
- /**
1486
- * Gets a copy of the entire message history
1487
- * @returns {Array} Complete array of all message objects
1488
- * @example
1489
- * const history = chat.historyGetAllCopy();
1490
- * console.log(`Total messages: ${history.length}`);
1491
- */
1492
- }, {
1493
- key: "historyGetAllCopy",
1494
- value: function historyGetAllCopy() {
1495
- return this._history.slice();
1496
- }
1497
-
1498
- /**
1499
- * Get a page of history messages with pagination support
1500
- * @param {number} page - Page number (1-based)
1501
- * @param {number} pageSize - Number of messages per page (default 50)
1502
- * @param {string} order - 'asc' for oldest first, 'desc' for newest first (default 'asc')
1503
- * @returns {object} Object with messages array, pagination info
1504
- */
1505
- }, {
1506
- key: "historyGetPage",
1507
- value: function historyGetPage() {
1508
- var page = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
1509
- var pageSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 50;
1510
- var order = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'asc';
1511
- var totalMessages = this._history.length;
1512
- var totalPages = Math.ceil(totalMessages / pageSize);
1513
- var currentPage = Math.max(1, Math.min(page, totalPages || 1));
1514
- var start, end;
1515
- if (order === 'desc') {
1516
- // For descending order, page 1 shows the newest messages
1517
- start = Math.max(0, totalMessages - currentPage * pageSize);
1518
- end = totalMessages - (currentPage - 1) * pageSize;
1519
- } else {
1520
- // For ascending order, page 1 shows the oldest messages
1521
- start = (currentPage - 1) * pageSize;
1522
- end = Math.min(start + pageSize, totalMessages);
1523
- }
1524
- var messages = this._history.slice(start, end);
1525
-
1526
- // Reverse messages array if descending order requested
1527
- if (order === 'desc') {
1528
- messages.reverse();
1529
- }
1530
- return {
1531
- messages: messages,
1532
- pagination: {
1533
- currentPage: currentPage,
1534
- pageSize: pageSize,
1535
- totalPages: totalPages,
1536
- totalMessages: totalMessages,
1537
- hasNext: currentPage < totalPages,
1538
- hasPrevious: currentPage > 1,
1539
- order: order
769
+ if (n < 0) {
770
+ return this._history.slice(n);
1540
771
  }
1541
- };
1542
- }
1543
-
1544
- /**
1545
- * Get information about history size and pagination
1546
- * @param {number} pageSize - Size to calculate pages for (default 50)
1547
- * @returns {object} History metadata
1548
- */
1549
- /**
1550
- * Search history for messages matching criteria
1551
- * @param {object} criteria - Search criteria object
1552
- * @param {string} criteria.text - Text to search for in message content
1553
- * @param {string} criteria.userString - Filter by user name
1554
- * @param {string} criteria.role - Filter by role
1555
- * @param {array} criteria.tags - Filter by tags (messages with any of these tags)
1556
- * @param {number} criteria.limit - Maximum results to return (default 100)
1557
- * @returns {array} Array of matching messages
1558
- */
1559
- /**
1560
- * Searches through message history with various filters
1561
- * @param {Object} [criteria={}] - Search criteria
1562
- * @param {string} [criteria.text] - Text to search for in message content
1563
- * @param {string} [criteria.userString] - Filter by specific user
1564
- * @param {string} [criteria.role] - Filter by role (user, assistant, system)
1565
- * @param {string[]} [criteria.tags] - Filter by tags (messages must have at least one)
1566
- * @param {number} [criteria.limit=100] - Maximum number of results
1567
- * @returns {Array} Array of matching messages
1568
- * @since 1.1.15
1569
- * @example
1570
- * // Search for messages containing 'error'
1571
- * const errors = chat.historySearch({ text: 'error' });
1572
- *
1573
- * // Find all bot messages
1574
- * const botMessages = chat.historySearch({ role: 'assistant' });
1575
- *
1576
- * // Complex search
1577
- * const results = chat.historySearch({
1578
- * text: 'help',
1579
- * userString: 'Support',
1580
- * tags: ['urgent'],
1581
- * limit: 20
1582
- * });
1583
- */
1584
- }, {
1585
- key: "historySearch",
1586
- value: function historySearch() {
1587
- var criteria = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1588
- var results = this._history;
1589
-
1590
- // Filter by text content (case-insensitive)
1591
- if (criteria.text) {
1592
- var searchText = criteria.text.toLowerCase();
1593
- results = results.filter(function (msg) {
1594
- return msg.content.toLowerCase().includes(searchText);
1595
- });
772
+ return this._history.slice(n, n + 1);
1596
773
  }
1597
-
1598
- // Filter by user
1599
- if (criteria.userString) {
1600
- results = results.filter(function (msg) {
1601
- return msg.userString === criteria.userString;
1602
- });
1603
- }
1604
-
1605
- // Filter by role
1606
- if (criteria.role) {
1607
- results = results.filter(function (msg) {
1608
- return msg.role === criteria.role;
1609
- });
1610
- }
1611
-
1612
- // Filter by tags (match any tag)
1613
- if (criteria.tags && criteria.tags.length > 0) {
1614
- results = results.filter(function (msg) {
1615
- return msg.tags && msg.tags.some(function (tag) {
1616
- return criteria.tags.includes(tag);
1617
- });
1618
- });
1619
- }
1620
-
1621
- // Limit results
1622
- var limit = criteria.limit || 100;
1623
- if (results.length > limit) {
1624
- results = results.slice(0, limit);
1625
- }
1626
- return results;
1627
- }
1628
-
1629
- /**
1630
- * Gets metadata and statistics about the message history
1631
- * @param {number} [pageSize=50] - Page size for calculating total pages
1632
- * @returns {Object} History information object
1633
- * @returns {number} returns.totalMessages - Total number of messages
1634
- * @returns {number} returns.totalPages - Total pages based on page size
1635
- * @returns {Object|null} returns.oldestMessage - First message info
1636
- * @returns {Object|null} returns.newestMessage - Last message info
1637
- * @returns {Object} returns.memoryUsage - Memory usage statistics
1638
- * @since 1.1.15
1639
- * @example
1640
- * const info = chat.historyGetInfo();
1641
- * console.log(`Messages: ${info.totalMessages}`);
1642
- * console.log(`Memory: ${info.memoryUsage.estimatedSize} bytes`);
1643
- */
1644
- }, {
1645
- key: "historyGetInfo",
1646
- value: function historyGetInfo() {
1647
- var pageSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 50;
1648
- var totalMessages = this._history.length;
1649
- return {
1650
- totalMessages: totalMessages,
1651
- totalPages: Math.ceil(totalMessages / pageSize),
1652
- oldestMessage: totalMessages > 0 ? {
1653
- msgid: this._history[0].msgid,
1654
- timestamp: this._history[0].timestamp,
1655
- userString: this._history[0].userString
1656
- } : null,
1657
- newestMessage: totalMessages > 0 ? {
1658
- msgid: this._history[totalMessages - 1].msgid,
1659
- timestamp: this._history[totalMessages - 1].timestamp,
1660
- userString: this._history[totalMessages - 1].userString
1661
- } : null,
1662
- memoryUsage: {
1663
- estimatedSize: JSON.stringify(this._history).length,
1664
- averageMessageSize: totalMessages > 0 ? Math.round(JSON.stringify(this._history).length / totalMessages) : 0
1665
- }
1666
- };
774
+ return this._history.slice(n, m);
1667
775
  }
1668
-
1669
- /**
1670
- * Clears all messages and resets the chat
1671
- * @returns {void}
1672
- * @example
1673
- * chat.historyClear(); // Removes all messages
1674
- */
1675
776
  }, {
1676
777
  key: "historyClear",
1677
778
  value: function historyClear() {
1678
779
  this.msgid = 0;
1679
-
1680
- // Handle virtual scroller
1681
- if (this.virtualScroller) {
1682
- this.virtualScroller.clear();
1683
- } else {
1684
- this._messagesArea.innerHTML = "";
1685
- }
1686
780
  this._history = [];
1687
- this._activeTags.clear();
1688
781
  }
1689
782
  }, {
1690
783
  key: "historyGetLength",
@@ -1702,29 +795,57 @@
1702
795
  }, {
1703
796
  key: "historyGetMessageContent",
1704
797
  value: function historyGetMessageContent(n) {
1705
- if (n >= 0 && n < this._history.length) return this._history[n].content;else return "";
798
+ if (n >= 0 && n < this._history.length) {
799
+ return this._history[n].content;
800
+ }
801
+ return "";
1706
802
  }
1707
-
1708
- // expects an array of messages to be in the format of the history object
1709
803
  }, {
1710
- key: "historyRestoreAll",
1711
- value: function historyRestoreAll(messageList) {
1712
- var _this9 = this;
1713
- // clear all messages and history
1714
- this.historyClear();
1715
-
1716
- // clear the messages div
1717
- this._messagesArea.innerHTML = "";
1718
-
1719
- // add all messages
1720
- messageList.forEach(function (message) {
1721
- _this9.messageAddFull(message);
804
+ key: "historyExport",
805
+ value: function historyExport() {
806
+ return this._history.map(function (item) {
807
+ return {
808
+ msgid: item.msgid,
809
+ content: item.content,
810
+ userString: item.userString,
811
+ align: item.align,
812
+ role: item.role,
813
+ userID: item.userID,
814
+ visible: item.visible,
815
+ tags: item.tags ? item.tags.slice() : [],
816
+ timestamp: item.timestamp,
817
+ updatedtime: item.updatedtime
818
+ };
1722
819
  });
1723
820
  }
1724
- /**
1725
- *
1726
- * @param {string} newTheme
1727
- */
821
+ }, {
822
+ key: "historyImport",
823
+ value: function historyImport(data) {
824
+ // Clear existing messages from DOM and history
825
+ this._messagesArea.innerHTML = '';
826
+ this._history = [];
827
+ this.msgid = 0;
828
+ var _iterator2 = _createForOfIteratorHelper(data),
829
+ _step2;
830
+ try {
831
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
832
+ var entry = _step2.value;
833
+ this.messageAddFull({
834
+ content: entry.content || '',
835
+ userString: entry.userString || 'user',
836
+ align: entry.align || 'right',
837
+ role: entry.role || 'user',
838
+ userID: entry.userID,
839
+ visible: entry.visible,
840
+ tags: entry.tags
841
+ });
842
+ }
843
+ } catch (err) {
844
+ _iterator2.e(err);
845
+ } finally {
846
+ _iterator2.f();
847
+ }
848
+ }
1728
849
  }, {
1729
850
  key: "changeTheme",
1730
851
  value: function changeTheme(newTheme) {
@@ -1732,323 +853,46 @@
1732
853
  this._chatWidget.classList.add(newTheme);
1733
854
  this._theme = newTheme;
1734
855
  }
1735
-
1736
- /**
1737
- * Get the current theme
1738
- * @returns {string} - The current theme
1739
- */
1740
856
  }, {
1741
857
  key: "theme",
1742
858
  get: function get() {
1743
859
  return this._theme;
1744
860
  }
1745
-
1746
- /**
1747
- *
1748
- * @returns {object} - Returns the version and license information for the library.
1749
- */
1750
- /**
1751
- * Gets the QuikChat version information
1752
- * @static
1753
- * @returns {Object} Version information object
1754
- * @returns {string} returns.version - Version number (e.g., '1.1.15')
1755
- * @returns {string} returns.license - License type (e.g., 'BSD-2')
1756
- * @returns {string} returns.url - Project URL
1757
- * @returns {boolean} returns.fun - Easter egg flag
1758
- * @example
1759
- * const version = quikchat.version();
1760
- * console.log(`QuikChat v${version.version}`);
1761
- */
1762
- }, {
1763
- key: "setTagVisibility",
1764
- value:
1765
- /**
1766
- * Sets visibility for all messages with a specific tag
1767
- * @param {string} tagName - Tag name to control
1768
- * @param {boolean} isVisible - Whether to show or hide messages with this tag
1769
- * @returns {boolean} True if successful, false if invalid tag name
1770
- * @since 1.1.14
1771
- * @example
1772
- * // Hide all system messages
1773
- * chat.setTagVisibility('system', false);
1774
- *
1775
- * // Show urgent messages
1776
- * chat.setTagVisibility('urgent', true);
1777
- */
1778
- function setTagVisibility(tagName, isVisible) {
1779
- if (typeof tagName !== 'string' || !/^[a-zA-Z0-9-]+$/.test(tagName)) {
1780
- return false;
1781
- }
1782
- var className = "quikchat-show-tag-".concat(tagName);
1783
- if (isVisible) {
1784
- this._chatWidget.classList.add(className);
1785
- } else {
1786
- this._chatWidget.classList.remove(className);
1787
- }
1788
- this._updateMessageStyles();
1789
- return true;
1790
- }
1791
-
1792
- /**
1793
- * Gets the visibility state of a tag
1794
- * @param {string} tagName - Tag name to check
1795
- * @returns {boolean} True if tag is visible, false otherwise
1796
- * @since 1.1.14
1797
- * @example
1798
- * const isVisible = chat.getTagVisibility('system');
1799
- */
1800
- }, {
1801
- key: "getTagVisibility",
1802
- value: function getTagVisibility(tagName) {
1803
- if (typeof tagName !== 'string' || !/^[a-zA-Z0-9-]+$/.test(tagName)) {
1804
- return false;
1805
- }
1806
- return this._chatWidget.classList.contains("quikchat-show-tag-".concat(tagName));
1807
- }
1808
-
1809
- /**
1810
- * Gets all active tags in the chat
1811
- * @returns {string[]} Array of all tags currently in use
1812
- * @since 1.1.14
1813
- * @example
1814
- * const tags = chat.getActiveTags();
1815
- * console.log('Active tags:', tags);
1816
- */
1817
- }, {
1818
- key: "getActiveTags",
1819
- value: function getActiveTags() {
1820
- return Array.from(this._activeTags);
1821
- }
1822
-
1823
- /**
1824
- * Checks if virtual scrolling is currently enabled
1825
- * @returns {boolean} True if virtual scrolling is enabled, false otherwise
1826
- * @since 1.1.16
1827
- * @example
1828
- * if (chat.isVirtualScrollingEnabled()) {
1829
- * console.log('Virtual scrolling is active');
1830
- * }
1831
- */
1832
- }, {
1833
- key: "isVirtualScrollingEnabled",
1834
- value: function isVirtualScrollingEnabled() {
1835
- return this.virtualScrollingEnabled && this.virtualScroller !== null;
1836
- }
1837
-
1838
- /**
1839
- * Gets the virtual scrolling configuration
1840
- * @returns {Object} Virtual scrolling configuration with enabled status and threshold
1841
- * @since 1.1.16
1842
- * @example
1843
- * const config = chat.getVirtualScrollingConfig();
1844
- * console.log(`Virtual scrolling: ${config.enabled}, threshold: ${config.threshold}`);
1845
- */
1846
- }, {
1847
- key: "getVirtualScrollingConfig",
1848
- value: function getVirtualScrollingConfig() {
1849
- return {
1850
- enabled: this.virtualScrollingEnabled,
1851
- active: this.virtualScroller !== null,
1852
- threshold: this.virtualScrollingThreshold
1853
- };
1854
- }
1855
-
1856
- /**
1857
- * Set the language for the widget
1858
- * @param {string} lang - Language code (e.g., 'en', 'es', 'fr')
1859
- * @param {Object} [translations] - Optional translations object for the language
1860
- * @example
1861
- * chat.setLanguage('es', {
1862
- * sendButton: 'Enviar',
1863
- * inputPlaceholder: 'Escribe un mensaje...',
1864
- * titleDefault: 'Chat'
1865
- * });
1866
- */
1867
- }, {
1868
- key: "setLanguage",
1869
- value: function setLanguage(lang, translations) {
1870
- this.lang = lang;
1871
-
1872
- // Add translations if provided
1873
- if (translations) {
1874
- this.translations[lang] = _objectSpread2(_objectSpread2({}, this.translations[lang]), translations);
1875
- }
1876
-
1877
- // Update current translations
1878
- this.currentTranslations = this.translations[lang] || this.translations['en'];
1879
-
1880
- // Update UI elements
1881
- this._updateUITranslations();
1882
- }
1883
-
1884
- /**
1885
- * Get current language
1886
- * @returns {string} Current language code
1887
- */
1888
- }, {
1889
- key: "getLanguage",
1890
- value: function getLanguage() {
1891
- return this.lang;
1892
- }
1893
-
1894
- /**
1895
- * Set text direction (LTR or RTL)
1896
- * @param {string} dir - Direction ('ltr' or 'rtl')
1897
- */
1898
- }, {
1899
- key: "setDirection",
1900
- value: function setDirection(dir) {
1901
- if (dir === 'ltr' || dir === 'rtl') {
1902
- this.dir = dir;
1903
- this._chatWidget.setAttribute('dir', dir);
1904
- }
1905
- }
1906
-
1907
- /**
1908
- * Get current text direction
1909
- * @returns {string} Current direction ('ltr' or 'rtl')
1910
- */
1911
- }, {
1912
- key: "getDirection",
1913
- value: function getDirection() {
1914
- return this.dir;
1915
- }
1916
-
1917
- /**
1918
- * Update UI elements with current translations
1919
- * @private
1920
- */
1921
- }, {
1922
- key: "_updateUITranslations",
1923
- value: function _updateUITranslations() {
1924
- // Update send button text
1925
- if (this._sendButton) {
1926
- this._sendButton.textContent = this.currentTranslations.sendButton || 'Send';
1927
- }
1928
-
1929
- // Update input placeholder
1930
- if (this._textEntry) {
1931
- this._textEntry.placeholder = this.currentTranslations.inputPlaceholder || 'Type a message...';
1932
- }
1933
-
1934
- // Update widget language attribute
1935
- if (this._chatWidget) {
1936
- this._chatWidget.setAttribute('lang', this.lang);
1937
- }
1938
- }
1939
-
1940
- /**
1941
- * Sets the content sanitizer function
1942
- * @param {Function|null} sanitizer - Function to sanitize content or null to disable
1943
- * @returns {void}
1944
- * @example
1945
- * // Use built-in HTML escaper
1946
- * chat.setSanitizer(quikchat.sanitizers.escapeHTML);
1947
- *
1948
- * // Use custom sanitizer (e.g., DOMPurify)
1949
- * chat.setSanitizer((content) => DOMPurify.sanitize(content));
1950
- *
1951
- * // Disable sanitization
1952
- * chat.setSanitizer(null);
1953
- */
1954
- }, {
1955
- key: "setSanitizer",
1956
- value: function setSanitizer(sanitizer) {
1957
- if (sanitizer === null || typeof sanitizer === 'function') {
1958
- this._sanitizer = sanitizer;
1959
- } else {
1960
- console.warn('Sanitizer must be a function or null');
1961
- }
1962
- }
1963
-
1964
- /**
1965
- * Gets the current content sanitizer function
1966
- * @returns {Function|null} The current sanitizer function or null
1967
- * @example
1968
- * const sanitizer = chat.getSanitizer();
1969
- * if (sanitizer) {
1970
- * console.log('Sanitization is enabled');
1971
- * }
1972
- */
1973
- }, {
1974
- key: "getSanitizer",
1975
- value: function getSanitizer() {
1976
- return this._sanitizer;
1977
- }
1978
-
1979
- /**
1980
- * Internal method to apply sanitizer to content
1981
- * @private
1982
- * @param {string} content - Content to sanitize
1983
- * @returns {string} Sanitized content
1984
- */
1985
- }, {
1986
- key: "_sanitizeContent",
1987
- value: function _sanitizeContent(content) {
1988
- if (this._sanitizer && typeof this._sanitizer === 'function') {
1989
- return this._sanitizer(content);
1990
- }
1991
- return content;
1992
- }
1993
861
  }], [{
1994
862
  key: "version",
1995
863
  value: function version() {
1996
- return quikchatVersion;
864
+ return {
865
+ "version": "1.2.4",
866
+ "license": "BSD-2",
867
+ "url": "https://github/deftio/quikchat"
868
+ };
1997
869
  }
1998
870
 
1999
- /**
2000
- * Built-in content sanitizers for XSS protection
2001
- * @static
2002
- * @type {Object}
2003
- * @property {Function} escapeHTML - Escapes HTML entities
2004
- * @property {Function} stripHTML - Removes all HTML tags
2005
- * @example
2006
- * // Use built-in HTML escaper
2007
- * const chat = new quikchat('#chat', onSend, {
2008
- * sanitizer: quikchat.sanitizers.escapeHTML
2009
- * });
2010
- */
2011
- }, {
2012
- key: "loremIpsum",
2013
- value:
2014
871
  /**
2015
872
  * quikchat.loremIpsum() - Generate a simple string of Lorem Ipsum text (sample typographer's text) of numChars in length.
2016
873
  * borrowed from github.com/deftio/bitwrench.js
2017
- * @param {number} numChars - The number of characters to generate (random btw 25 and 150 if undefined).
874
+ * @param {number} numChars - The number of characters to generate (random btw 25 and 150 if undefined).
2018
875
  * @param {number} [startSpot=0] - The starting index in the Lorem Ipsum text. If undefined, a random startSpot will be generated.
2019
876
  * @param {boolean} [startWithCapitalLetter=true] - If true, capitalize the first character or inject a capital letter if the first character isn't a capital letter.
2020
- *
877
+ *
2021
878
  * @returns {string} A string of Lorem Ipsum text.
2022
- *
2023
- * @example
879
+ *
880
+ * @example
2024
881
  * // Returns 200 characters of Lorem Ipsum starting from index 50
2025
882
  * loremIpsum(200, 50);
2026
- *
2027
- * @example
883
+ *
884
+ * @example
2028
885
  * //Returns a 200 Lorem Ipsum characters starting from a random index
2029
886
  * loremIpsum(200);
2030
887
  */
2031
-
2032
- /**
2033
- * Generates Lorem Ipsum placeholder text
2034
- * @static
2035
- * @param {number} [numChars] - Length of text to generate (random if not specified)
2036
- * @param {number} [startSpot] - Starting offset in Lorem text (random if not specified)
2037
- * @param {boolean} [startWithCapitalLetter=true] - Whether to capitalize first letter
2038
- * @returns {string} Generated Lorem Ipsum text
2039
- * @example
2040
- * // Generate 100 characters
2041
- * const text = quikchat.loremIpsum(100);
2042
- *
2043
- * // Generate random length
2044
- * const randomText = quikchat.loremIpsum();
2045
- */
2046
- function loremIpsum(numChars) {
888
+ }, {
889
+ key: "loremIpsum",
890
+ value: function loremIpsum(numChars) {
2047
891
  var startSpot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
2048
892
  var startWithCapitalLetter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
2049
893
  var loremText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
2050
894
  if (typeof numChars !== "number") {
2051
- numChars = Math.floor(Math.random() * 150) + 25;
895
+ numChars = Math.floor(Math.random() * 126) + 25;
2052
896
  }
2053
897
  if (startSpot === undefined) {
2054
898
  startSpot = Math.floor(Math.random() * loremText.length);
@@ -2060,9 +904,6 @@
2060
904
  startSpot = (startSpot + 1) % loremText.length;
2061
905
  }
2062
906
  var l = loremText.substring(startSpot) + loremText.substring(0, startSpot);
2063
- if (typeof numChars !== "number") {
2064
- numChars = l.length;
2065
- }
2066
907
  var s = "";
2067
908
  while (numChars > 0) {
2068
909
  s += numChars < l.length ? l.substring(0, numChars) : l;
@@ -2072,128 +913,12 @@
2072
913
  s = s.substring(0, s.length - 1) + "."; // always end on non-whitespace. "." was chosen arbitrarily.
2073
914
  }
2074
915
  if (startWithCapitalLetter) {
2075
- var c = s[0].toUpperCase();
2076
- c = /[A-Z]/.test(c) ? c : "M";
2077
- s = c + s.substring(1);
916
+ s = s[0].toUpperCase() + s.substring(1);
2078
917
  }
2079
918
  return s;
2080
919
  }
2081
- }, {
2082
- key: "tempMessageGenerator",
2083
- value:
2084
- /**
2085
- * Creates a temporary message that updates periodically
2086
- * @static
2087
- * @param {string|HTMLElement} domElement - Element selector or DOM element
2088
- * @param {string} content - Initial message content
2089
- * @param {number} interval - Update interval in milliseconds (min 100ms)
2090
- * @param {Function} [cb=null] - Callback to generate new content
2091
- * @param {string} cb.message - Current message
2092
- * @param {number} cb.count - Update count
2093
- * @returns {void}
2094
- * @example
2095
- * // Simple loading indicator
2096
- * quikchat.tempMessageGenerator('#loading', 'Loading', 500);
2097
- *
2098
- * // Custom update function
2099
- * quikchat.tempMessageGenerator('#status', 'Processing', 1000, (msg, count) => {
2100
- * return `Processing... ${count}%`;
2101
- * });
2102
- */
2103
- function tempMessageGenerator(domElement, content, interval) {
2104
- var cb = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
2105
- interval = Math.max(interval, 100); // Ensure at least 100ms interval
2106
-
2107
- var count = 0;
2108
- var defaultCB = function defaultCB(msg, count) {
2109
- msg += ".";
2110
- return msg;
2111
- };
2112
- if (cb && typeof cb !== 'function') {
2113
- cb = null;
2114
- }
2115
- cb = cb || defaultCB;
2116
-
2117
- // if its a string, then get the element (css sel) or its an DOM element already
2118
- var el = domElement;
2119
- if (typeof el === 'string') {
2120
- el = document.querySelector(el);
2121
- }
2122
- var element = el;
2123
-
2124
- // Ensure the element exists
2125
- if (!element) return;
2126
- element.innerHTML = content;
2127
- var currentMsg = content;
2128
- var intervalId = setInterval(function () {
2129
- if (element.innerHTML !== currentMsg) {
2130
- clearInterval(intervalId); // Stop updating if content is changed externally
2131
- return;
2132
- }
2133
- currentMsg = String(cb(currentMsg, count)); // Use callback return value if provided
2134
-
2135
- count++;
2136
- element.innerHTML = currentMsg;
2137
- }, interval);
2138
- }
2139
- }, {
2140
- key: "createTempMessageDOMStr",
2141
- value: function createTempMessageDOMStr(initialContent) {
2142
- var updateInterval = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1000;
2143
- var callback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
2144
- var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
2145
- // Make sure the interval is at least 100ms
2146
- updateInterval = Math.max(updateInterval, 100);
2147
-
2148
- // Validate callback; if not a function, ignore it.
2149
- if (callback && typeof callback !== 'function') {
2150
- callback = null;
2151
- }
2152
- // Default callback simply appends a dot.
2153
- callback = callback || function (msg, count) {
2154
- return msg + ".";
2155
- };
2156
-
2157
- // Allow an optional CSS class for the container element
2158
- var containerClass = options.containerClass ? options.containerClass : '';
2159
-
2160
- // Generate a unique id so that the inline script can reliably find the container.
2161
- var uniqueId = "tempMsg_" + Date.now() + "_" + Math.floor(Math.random() * 1000000);
2162
-
2163
- // Build and return the HTML string.
2164
- // Note the use of <\/script> (with a backslash) so that the inline script is not terminated early.
2165
- return "\n <span id=\"".concat(uniqueId, "\" ").concat(containerClass ? "class=\"".concat(containerClass, "\"") : '', ">\n ").concat(initialContent, "\n </span>\n <script>\n (function(){\n // Get our container element by its unique id.\n var container = document.getElementById(\"").concat(uniqueId, "\");\n if (!container) return;\n var count = 0;\n var currentMsg = container.innerHTML;\n var interval = ").concat(updateInterval, ";\n // Convert the callback function into its string representation.\n var cb = ").concat(callback.toString(), ";\n var intervalId = setInterval(function(){\n // If the content has been replaced, stop updating.\n if(container.innerHTML !== currentMsg){\n clearInterval(intervalId);\n return;\n }\n // Use the callback to generate the new message.\n currentMsg = String(cb(currentMsg, count));\n count++;\n container.innerHTML = currentMsg;\n }, interval);\n })();\n </script>\n ");
2166
- }
2167
920
  }]);
2168
921
  }();
2169
- _defineProperty(quikchat, "sanitizers", {
2170
- /**
2171
- * Escapes HTML entities to prevent XSS
2172
- * @param {string} str - String to escape
2173
- * @returns {string} Escaped string
2174
- */
2175
- escapeHTML: function escapeHTML(str) {
2176
- if (typeof str !== 'string') return str;
2177
- return str.replace(/[&<>"']/g, function (m) {
2178
- return {
2179
- '&': '&amp;',
2180
- '<': '&lt;',
2181
- '>': '&gt;',
2182
- '"': '&quot;',
2183
- "'": '&#39;'
2184
- }[m];
2185
- });
2186
- },
2187
- /**
2188
- * Strips all HTML tags but keeps text content
2189
- * @param {string} str - String to strip
2190
- * @returns {string} Text without HTML tags
2191
- */
2192
- stripHTML: function stripHTML(str) {
2193
- if (typeof str !== 'string') return str;
2194
- return str.replace(/<[^>]*>/g, '');
2195
- }
2196
- });
2197
922
 
2198
923
  return quikchat;
2199
924