quikchat 1.1.16 → 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 (42) hide show
  1. package/README.md +160 -354
  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 +449 -1421
  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 +449 -1421
  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.umd.js +449 -1421
  38. package/dist/quikchat.umd.js.map +1 -1
  39. package/dist/quikchat.umd.min.js +1 -1
  40. package/dist/quikchat.umd.min.js.map +1 -1
  41. package/package.json +59 -34
  42. package/dist/quikchat.d.ts +0 -194
@@ -0,0 +1,2748 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.quikchat = factory());
5
+ })(this, (function () { 'use strict';
6
+
7
+ function _arrayLikeToArray(r, a) {
8
+ (null == a || a > r.length) && (a = r.length);
9
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
10
+ return n;
11
+ }
12
+ function _arrayWithoutHoles(r) {
13
+ if (Array.isArray(r)) return _arrayLikeToArray(r);
14
+ }
15
+ function _assertThisInitialized(e) {
16
+ if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
17
+ return e;
18
+ }
19
+ function asyncGeneratorStep(n, t, e, r, o, a, c) {
20
+ try {
21
+ var i = n[a](c),
22
+ u = i.value;
23
+ } catch (n) {
24
+ return void e(n);
25
+ }
26
+ i.done ? t(u) : Promise.resolve(u).then(r, o);
27
+ }
28
+ function _asyncToGenerator(n) {
29
+ return function () {
30
+ var t = this,
31
+ e = arguments;
32
+ return new Promise(function (r, o) {
33
+ var a = n.apply(t, e);
34
+ function _next(n) {
35
+ asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
36
+ }
37
+ function _throw(n) {
38
+ asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
39
+ }
40
+ _next(void 0);
41
+ });
42
+ };
43
+ }
44
+ function _callSuper(t, o, e) {
45
+ return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));
46
+ }
47
+ function _classCallCheck(a, n) {
48
+ if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
49
+ }
50
+ function _defineProperties(e, r) {
51
+ for (var t = 0; t < r.length; t++) {
52
+ var o = r[t];
53
+ o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o);
54
+ }
55
+ }
56
+ function _createClass(e, r, t) {
57
+ return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
58
+ writable: false
59
+ }), e;
60
+ }
61
+ function _createForOfIteratorHelper(r, e) {
62
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
63
+ if (!t) {
64
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
65
+ t && (r = t);
66
+ var n = 0,
67
+ F = function () {};
68
+ return {
69
+ s: F,
70
+ n: function () {
71
+ return n >= r.length ? {
72
+ done: true
73
+ } : {
74
+ done: false,
75
+ value: r[n++]
76
+ };
77
+ },
78
+ e: function (r) {
79
+ throw r;
80
+ },
81
+ f: F
82
+ };
83
+ }
84
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
85
+ }
86
+ var o,
87
+ a = true,
88
+ u = false;
89
+ return {
90
+ s: function () {
91
+ t = t.call(r);
92
+ },
93
+ n: function () {
94
+ var r = t.next();
95
+ return a = r.done, r;
96
+ },
97
+ e: function (r) {
98
+ u = true, o = r;
99
+ },
100
+ f: function () {
101
+ try {
102
+ a || null == t.return || t.return();
103
+ } finally {
104
+ if (u) throw o;
105
+ }
106
+ }
107
+ };
108
+ }
109
+ function _defineProperty(e, r, t) {
110
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
111
+ value: t,
112
+ enumerable: true,
113
+ configurable: true,
114
+ writable: true
115
+ }) : e[r] = t, e;
116
+ }
117
+ function _get() {
118
+ return _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) {
119
+ var p = _superPropBase(e, t);
120
+ if (p) {
121
+ var n = Object.getOwnPropertyDescriptor(p, t);
122
+ return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value;
123
+ }
124
+ }, _get.apply(null, arguments);
125
+ }
126
+ function _getPrototypeOf(t) {
127
+ return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) {
128
+ return t.__proto__ || Object.getPrototypeOf(t);
129
+ }, _getPrototypeOf(t);
130
+ }
131
+ function _inherits(t, e) {
132
+ if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function");
133
+ t.prototype = Object.create(e && e.prototype, {
134
+ constructor: {
135
+ value: t,
136
+ writable: true,
137
+ configurable: true
138
+ }
139
+ }), Object.defineProperty(t, "prototype", {
140
+ writable: false
141
+ }), e && _setPrototypeOf(t, e);
142
+ }
143
+ function _isNativeReflectConstruct() {
144
+ try {
145
+ var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
146
+ } catch (t) {}
147
+ return (_isNativeReflectConstruct = function () {
148
+ return !!t;
149
+ })();
150
+ }
151
+ function _iterableToArray(r) {
152
+ if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
153
+ }
154
+ function _nonIterableSpread() {
155
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
156
+ }
157
+ function ownKeys(e, r) {
158
+ var t = Object.keys(e);
159
+ if (Object.getOwnPropertySymbols) {
160
+ var o = Object.getOwnPropertySymbols(e);
161
+ r && (o = o.filter(function (r) {
162
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
163
+ })), t.push.apply(t, o);
164
+ }
165
+ return t;
166
+ }
167
+ function _objectSpread2(e) {
168
+ for (var r = 1; r < arguments.length; r++) {
169
+ var t = null != arguments[r] ? arguments[r] : {};
170
+ r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
171
+ _defineProperty(e, r, t[r]);
172
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
173
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
174
+ });
175
+ }
176
+ return e;
177
+ }
178
+ function _possibleConstructorReturn(t, e) {
179
+ if (e && ("object" == typeof e || "function" == typeof e)) return e;
180
+ if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined");
181
+ return _assertThisInitialized(t);
182
+ }
183
+ function _regenerator() {
184
+ /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
185
+ var e,
186
+ t,
187
+ r = "function" == typeof Symbol ? Symbol : {},
188
+ n = r.iterator || "@@iterator",
189
+ o = r.toStringTag || "@@toStringTag";
190
+ function i(r, n, o, i) {
191
+ var c = n && n.prototype instanceof Generator ? n : Generator,
192
+ u = Object.create(c.prototype);
193
+ return _regeneratorDefine(u, "_invoke", function (r, n, o) {
194
+ var i,
195
+ c,
196
+ u,
197
+ f = 0,
198
+ p = o || [],
199
+ y = false,
200
+ G = {
201
+ p: 0,
202
+ n: 0,
203
+ v: e,
204
+ a: d,
205
+ f: d.bind(e, 4),
206
+ d: function (t, r) {
207
+ return i = t, c = 0, u = e, G.n = r, a;
208
+ }
209
+ };
210
+ function d(r, n) {
211
+ for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) {
212
+ var o,
213
+ i = p[t],
214
+ d = G.p,
215
+ l = i[2];
216
+ r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0));
217
+ }
218
+ if (o || r > 1) return a;
219
+ throw y = true, n;
220
+ }
221
+ return function (o, p, l) {
222
+ if (f > 1) throw TypeError("Generator is already running");
223
+ for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) {
224
+ i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u);
225
+ try {
226
+ if (f = 2, i) {
227
+ if (c || (o = "next"), t = i[o]) {
228
+ if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object");
229
+ if (!t.done) return t;
230
+ u = t.value, c < 2 && (c = 0);
231
+ } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1);
232
+ i = e;
233
+ } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break;
234
+ } catch (t) {
235
+ i = e, c = 1, u = t;
236
+ } finally {
237
+ f = 1;
238
+ }
239
+ }
240
+ return {
241
+ value: t,
242
+ done: y
243
+ };
244
+ };
245
+ }(r, o, i), true), u;
246
+ }
247
+ var a = {};
248
+ function Generator() {}
249
+ function GeneratorFunction() {}
250
+ function GeneratorFunctionPrototype() {}
251
+ t = Object.getPrototypeOf;
252
+ var c = [][n] ? t(t([][n]())) : (_regeneratorDefine(t = {}, n, function () {
253
+ return this;
254
+ }), t),
255
+ u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c);
256
+ function f(e) {
257
+ return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e;
258
+ }
259
+ return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine(u), _regeneratorDefine(u, o, "Generator"), _regeneratorDefine(u, n, function () {
260
+ return this;
261
+ }), _regeneratorDefine(u, "toString", function () {
262
+ return "[object Generator]";
263
+ }), (_regenerator = function () {
264
+ return {
265
+ w: i,
266
+ m: f
267
+ };
268
+ })();
269
+ }
270
+ function _regeneratorDefine(e, r, n, t) {
271
+ var i = Object.defineProperty;
272
+ try {
273
+ i({}, "", {});
274
+ } catch (e) {
275
+ i = 0;
276
+ }
277
+ _regeneratorDefine = function (e, r, n, t) {
278
+ function o(r, n) {
279
+ _regeneratorDefine(e, r, function (e) {
280
+ return this._invoke(r, n, e);
281
+ });
282
+ }
283
+ r ? i ? i(e, r, {
284
+ value: n,
285
+ enumerable: !t,
286
+ configurable: !t,
287
+ writable: !t
288
+ }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2));
289
+ }, _regeneratorDefine(e, r, n, t);
290
+ }
291
+ function _setPrototypeOf(t, e) {
292
+ return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
293
+ return t.__proto__ = e, t;
294
+ }, _setPrototypeOf(t, e);
295
+ }
296
+ function _superPropBase(t, o) {
297
+ for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
298
+ return t;
299
+ }
300
+ function _superPropGet(t, o, e, r) {
301
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
302
+ return 2 & r && "function" == typeof p ? function (t) {
303
+ return p.apply(e, t);
304
+ } : p;
305
+ }
306
+ function _toConsumableArray(r) {
307
+ return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
308
+ }
309
+ function _toPrimitive(t, r) {
310
+ if ("object" != typeof t || !t) return t;
311
+ var e = t[Symbol.toPrimitive];
312
+ if (void 0 !== e) {
313
+ var i = e.call(t, r);
314
+ if ("object" != typeof i) return i;
315
+ throw new TypeError("@@toPrimitive must return a primitive value.");
316
+ }
317
+ return (String )(t);
318
+ }
319
+ function _toPropertyKey(t) {
320
+ var i = _toPrimitive(t, "string");
321
+ return "symbol" == typeof i ? i : i + "";
322
+ }
323
+ function _unsupportedIterableToArray(r, a) {
324
+ if (r) {
325
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
326
+ var t = {}.toString.call(r).slice(8, -1);
327
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
328
+ }
329
+ }
330
+
331
+ var quikchat = /*#__PURE__*/function () {
332
+ /**
333
+ *
334
+ * @param string or DOM element parentElement
335
+ * @param {*} meta
336
+ */
337
+ function quikchat(parentElement) {
338
+ var onSend = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
339
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
340
+ _classCallCheck(this, quikchat);
341
+ var defaultOpts = {
342
+ theme: 'quikchat-theme-light',
343
+ trackHistory: true,
344
+ showTimestamps: false,
345
+ titleArea: {
346
+ title: "Chat",
347
+ show: false,
348
+ align: "center"
349
+ },
350
+ messagesArea: {
351
+ alternating: true
352
+ }
353
+ };
354
+ var meta = _objectSpread2(_objectSpread2({}, defaultOpts), options); // merge options with defaults
355
+
356
+ if (typeof parentElement === 'string') {
357
+ parentElement = document.querySelector(parentElement);
358
+ }
359
+ this._parentElement = parentElement;
360
+ this._theme = meta.theme;
361
+ this._onSend = onSend ? onSend : function () {}; // call back function for onSend
362
+ this._messageFormatter = meta.messageFormatter || null;
363
+ this._sanitize = meta.sanitize || false;
364
+ this._createWidget();
365
+ // title area
366
+ if (meta.titleArea) {
367
+ this.titleAreaSetContents(meta.titleArea.title, meta.titleArea.align);
368
+ if (meta.titleArea.show === true) {
369
+ this.titleAreaShow();
370
+ } else {
371
+ this.titleAreaHide();
372
+ }
373
+ }
374
+ // messages area
375
+ if (meta.messagesArea) {
376
+ this.messagesAreaAlternateColors(meta.messagesArea.alternating);
377
+ }
378
+ // timestamps
379
+ if (meta.showTimestamps) {
380
+ this.messagesAreaShowTimestamps(true);
381
+ }
382
+ // direction (ltr/rtl)
383
+ if (meta.direction) {
384
+ this.setDirection(meta.direction);
385
+ }
386
+ // plumbing
387
+ this._attachEventListeners();
388
+ this.trackHistory = meta.trackHistory !== false;
389
+ this._historyLimit = 10000000;
390
+ this._history = [];
391
+ }
392
+ return _createClass(quikchat, [{
393
+ key: "_createWidget",
394
+ value: function _createWidget() {
395
+ 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 ");
396
+ this._parentElement.innerHTML = widgetHTML;
397
+ this._chatWidget = this._parentElement.querySelector('.quikchat-base');
398
+ this._titleArea = this._chatWidget.querySelector('.quikchat-title-area');
399
+ this._messagesWrapper = this._chatWidget.querySelector('.quikchat-messages-wrapper');
400
+ this._messagesArea = this._chatWidget.querySelector('.quikchat-messages-area');
401
+ this._scrollBottomBtn = this._messagesWrapper.querySelector('.quikchat-scroll-bottom');
402
+ this._inputArea = this._chatWidget.querySelector('.quikchat-input-area');
403
+ this._textEntry = this._inputArea.querySelector('.quikchat-input-textbox');
404
+ this._sendButton = this._inputArea.querySelector('.quikchat-input-send-btn');
405
+ this.msgid = 0;
406
+ }
407
+
408
+ /**
409
+ * Attach event listeners to the widget
410
+ */
411
+ }, {
412
+ key: "_attachEventListeners",
413
+ value: function _attachEventListeners() {
414
+ var _this = this;
415
+ this._sendButton.addEventListener('click', function () {
416
+ var text = _this._textEntry.value.trim();
417
+ if (text === '') return;
418
+ _this._onSend(_this, text);
419
+ });
420
+ this._textEntry.addEventListener('keydown', function (event) {
421
+ // Check if Shift + Enter is pressed
422
+ if (event.shiftKey && event.keyCode === 13) {
423
+ event.preventDefault();
424
+ var text = _this._textEntry.value.trim();
425
+ if (text === '') return;
426
+ _this._onSend(_this, text);
427
+ }
428
+ });
429
+
430
+ // Auto-grow textarea
431
+ this._textEntry.addEventListener('input', function () {
432
+ return _this._autoGrowTextarea();
433
+ });
434
+ this._messagesArea.addEventListener('scroll', function () {
435
+ var _this$_messagesArea = _this._messagesArea,
436
+ scrollTop = _this$_messagesArea.scrollTop,
437
+ scrollHeight = _this$_messagesArea.scrollHeight,
438
+ clientHeight = _this$_messagesArea.clientHeight;
439
+ _this.userScrolledUp = scrollTop + clientHeight < scrollHeight - 1;
440
+ _this._updateScrollBottomBtn();
441
+ });
442
+
443
+ // Scroll-to-bottom button
444
+ this._scrollBottomBtn.addEventListener('click', function () {
445
+ return _this.scrollToBottom();
446
+ });
447
+
448
+ // Ctrl+End to scroll to bottom
449
+ this._chatWidget.addEventListener('keydown', function (event) {
450
+ if (event.ctrlKey && event.key === 'End') {
451
+ event.preventDefault();
452
+ _this.scrollToBottom();
453
+ }
454
+ });
455
+
456
+ // Use ResizeObserver to detect parent container resize
457
+ if (typeof ResizeObserver !== 'undefined') {
458
+ this._resizeObserver = new ResizeObserver(function () {
459
+ return _this._handleContainerResize();
460
+ });
461
+ this._resizeObserver.observe(this._parentElement);
462
+ }
463
+ }
464
+
465
+ // set the onSend function callback.
466
+ }, {
467
+ key: "setCallbackOnSend",
468
+ value: function setCallbackOnSend(callback) {
469
+ this._onSend = callback;
470
+ }
471
+ // set a callback for everytime a message is added (listener)
472
+ }, {
473
+ key: "setCallbackonMessageAdded",
474
+ value: function setCallbackonMessageAdded(callback) {
475
+ this._onMessageAdded = callback;
476
+ }
477
+ }, {
478
+ key: "setCallbackonMessageAppend",
479
+ value: function setCallbackonMessageAppend(callback) {
480
+ this._onMessageAppend = callback;
481
+ }
482
+ }, {
483
+ key: "setCallbackonMessageReplace",
484
+ value: function setCallbackonMessageReplace(callback) {
485
+ this._onMessageReplace = callback;
486
+ }
487
+ }, {
488
+ key: "setCallbackonMessageDelete",
489
+ value: function setCallbackonMessageDelete(callback) {
490
+ this._onMessageDelete = callback;
491
+ }
492
+
493
+ // Public methods
494
+ }, {
495
+ key: "titleAreaToggle",
496
+ value: function titleAreaToggle() {
497
+ if (this._titleArea.style.display === 'none') {
498
+ this.titleAreaShow();
499
+ } else {
500
+ this.titleAreaHide();
501
+ }
502
+ }
503
+ }, {
504
+ key: "titleAreaShow",
505
+ value: function titleAreaShow() {
506
+ this._titleArea.style.display = '';
507
+ }
508
+ }, {
509
+ key: "titleAreaHide",
510
+ value: function titleAreaHide() {
511
+ this._titleArea.style.display = 'none';
512
+ }
513
+ }, {
514
+ key: "titleAreaSetContents",
515
+ value: function titleAreaSetContents(title) {
516
+ var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'center';
517
+ this._titleArea.innerHTML = title;
518
+ this._titleArea.style.textAlign = align;
519
+ }
520
+ }, {
521
+ key: "titleAreaGetContents",
522
+ value: function titleAreaGetContents() {
523
+ return this._titleArea.innerHTML;
524
+ }
525
+ }, {
526
+ key: "inputAreaToggle",
527
+ value: function inputAreaToggle() {
528
+ if (this._inputArea.style.display === 'none') {
529
+ this.inputAreaShow();
530
+ } else {
531
+ this.inputAreaHide();
532
+ }
533
+ }
534
+ }, {
535
+ key: "inputAreaShow",
536
+ value: function inputAreaShow() {
537
+ this._inputArea.style.display = '';
538
+ }
539
+ }, {
540
+ key: "inputAreaHide",
541
+ value: function inputAreaHide() {
542
+ this._inputArea.style.display = 'none';
543
+ }
544
+ }, {
545
+ key: "inputAreaSetEnabled",
546
+ value: function inputAreaSetEnabled(enabled) {
547
+ this._textEntry.disabled = !enabled;
548
+ this._sendButton.disabled = !enabled;
549
+ }
550
+ }, {
551
+ key: "inputAreaSetButtonText",
552
+ value: function inputAreaSetButtonText(text) {
553
+ this._sendButton.textContent = text;
554
+ }
555
+ }, {
556
+ key: "inputAreaGetButtonText",
557
+ value: function inputAreaGetButtonText() {
558
+ return this._sendButton.textContent;
559
+ }
560
+ }, {
561
+ key: "setDirection",
562
+ value: function setDirection(dir) {
563
+ var d = dir === 'rtl' ? 'rtl' : 'ltr';
564
+ this._chatWidget.setAttribute('dir', d);
565
+ if (d === 'rtl') {
566
+ this._chatWidget.classList.add('quikchat-rtl');
567
+ } else {
568
+ this._chatWidget.classList.remove('quikchat-rtl');
569
+ }
570
+ }
571
+ }, {
572
+ key: "getDirection",
573
+ value: function getDirection() {
574
+ return this._chatWidget.getAttribute('dir') || 'ltr';
575
+ }
576
+ }, {
577
+ key: "_handleContainerResize",
578
+ value: function _handleContainerResize() {
579
+ // Layout is handled by CSS flexbox — no JS height calculation needed.
580
+ // This hook exists for future use or custom resize callbacks.
581
+ }
582
+ }, {
583
+ key: "scrollToBottom",
584
+ value: function scrollToBottom() {
585
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
586
+ this.userScrolledUp = false;
587
+ this._updateScrollBottomBtn();
588
+ }
589
+ }, {
590
+ key: "_updateScrollBottomBtn",
591
+ value: function _updateScrollBottomBtn() {
592
+ if (this.userScrolledUp) {
593
+ this._scrollBottomBtn.classList.add('quikchat-scroll-bottom-visible');
594
+ } else {
595
+ this._scrollBottomBtn.classList.remove('quikchat-scroll-bottom-visible');
596
+ }
597
+ }
598
+ }, {
599
+ key: "_autoGrowTextarea",
600
+ value: function _autoGrowTextarea() {
601
+ var el = this._textEntry;
602
+ el.style.height = 'auto';
603
+ var maxHeight = parseInt(getComputedStyle(el).getPropertyValue('--quikchat-input-max-height')) || 120;
604
+ el.style.height = Math.min(el.scrollHeight, maxHeight) + 'px';
605
+ el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';
606
+ }
607
+ }, {
608
+ key: "_formatTimestamp",
609
+ value: function _formatTimestamp(isoString) {
610
+ var d = new Date(isoString);
611
+ var h = d.getHours();
612
+ var m = String(d.getMinutes()).padStart(2, '0');
613
+ var ampm = h >= 12 ? 'PM' : 'AM';
614
+ var h12 = h % 12 || 12;
615
+ return h12 + ':' + m + ' ' + ampm;
616
+ }
617
+ }, {
618
+ key: "messagesAreaShowTimestamps",
619
+ value: function messagesAreaShowTimestamps(show) {
620
+ if (show) {
621
+ this._messagesArea.classList.add('quikchat-show-timestamps');
622
+ } else {
623
+ this._messagesArea.classList.remove('quikchat-show-timestamps');
624
+ }
625
+ }
626
+ }, {
627
+ key: "messagesAreaShowTimestampsGet",
628
+ value: function messagesAreaShowTimestampsGet() {
629
+ return this._messagesArea.classList.contains('quikchat-show-timestamps');
630
+ }
631
+ }, {
632
+ key: "messagesAreaShowTimestampsToggle",
633
+ value: function messagesAreaShowTimestampsToggle() {
634
+ this._messagesArea.classList.toggle('quikchat-show-timestamps');
635
+ }
636
+ }, {
637
+ key: "_escapeHTML",
638
+ value: function _escapeHTML(str) {
639
+ var div = document.createElement('div');
640
+ div.textContent = str;
641
+ return div.innerHTML;
642
+ }
643
+ }, {
644
+ key: "_processContent",
645
+ value: function _processContent(content) {
646
+ if (this._sanitize === true) {
647
+ content = this._escapeHTML(content);
648
+ } else if (typeof this._sanitize === 'function') {
649
+ content = this._sanitize(content);
650
+ }
651
+ if (this._messageFormatter) {
652
+ content = this._messageFormatter(content);
653
+ }
654
+ return content;
655
+ }
656
+
657
+ //messagesArea functions
658
+ }, {
659
+ key: "messagesAreaAlternateColors",
660
+ value: function messagesAreaAlternateColors() {
661
+ var alt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
662
+ if (alt) {
663
+ this._messagesArea.classList.add('quikchat-messages-area-alt');
664
+ } else {
665
+ this._messagesArea.classList.remove('quikchat-messages-area-alt');
666
+ }
667
+ return alt === true;
668
+ }
669
+ }, {
670
+ key: "messagesAreaAlternateColorsToggle",
671
+ value: function messagesAreaAlternateColorsToggle() {
672
+ this._messagesArea.classList.toggle('quikchat-messages-area-alt');
673
+ }
674
+ }, {
675
+ key: "messagesAreaAlternateColorsGet",
676
+ value: function messagesAreaAlternateColorsGet() {
677
+ return this._messagesArea.classList.contains('quikchat-messages-area-alt');
678
+ }
679
+ // message functions
680
+ }, {
681
+ key: "messageAddFull",
682
+ value: function messageAddFull() {
683
+ var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
684
+ content: "",
685
+ userString: "user",
686
+ align: "right",
687
+ role: "user",
688
+ userID: -1
689
+ };
690
+ var msgid = this.msgid;
691
+ var messageDiv = document.createElement('div');
692
+ var msgidClass = 'quikchat-msgid-' + String(msgid).padStart(10, '0');
693
+ messageDiv.classList.add('quikchat-message', msgidClass);
694
+ messageDiv.classList.add('quikchat-role-' + (input.role || 'user'));
695
+ messageDiv.classList.add('quikchat-align-' + (input.align || 'right'));
696
+ this.msgid++;
697
+ messageDiv.classList.add(this._messagesArea.children.length % 2 === 1 ? 'quikchat-message-1' : 'quikchat-message-2');
698
+
699
+ // Visibility: default true, hidden messages get display:none
700
+ var visible = input.visible !== false;
701
+ if (!visible) {
702
+ messageDiv.style.display = 'none';
703
+ }
704
+
705
+ // Tags: array of strings for group-based visibility control
706
+ var tags = Array.isArray(input.tags) ? input.tags.slice() : [];
707
+ var userDiv = document.createElement('div');
708
+ userDiv.classList.add('quikchat-user-label');
709
+ userDiv.style.textAlign = input.align;
710
+ userDiv.innerHTML = input.userString;
711
+ var contentDiv = document.createElement('div');
712
+ contentDiv.classList.add('quikchat-message-content');
713
+ contentDiv.style.textAlign = input.align;
714
+ contentDiv.innerHTML = this._processContent(input.content);
715
+ var timestamp = new Date().toISOString();
716
+ var timestampSpan = document.createElement('span');
717
+ timestampSpan.classList.add('quikchat-timestamp');
718
+ timestampSpan.textContent = this._formatTimestamp(timestamp);
719
+ messageDiv.appendChild(userDiv);
720
+ messageDiv.appendChild(contentDiv);
721
+ messageDiv.appendChild(timestampSpan);
722
+ this._messagesArea.appendChild(messageDiv);
723
+
724
+ // Scroll to the last message only if the user is not actively scrolling up
725
+ if (!this.userScrolledUp) {
726
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
727
+ }
728
+ this._textEntry.value = '';
729
+ this._autoGrowTextarea();
730
+ var updatedtime = timestamp;
731
+ if (this.trackHistory) {
732
+ this._history.push(_objectSpread2(_objectSpread2({
733
+ msgid: msgid
734
+ }, input), {}, {
735
+ visible: visible,
736
+ tags: tags,
737
+ timestamp: timestamp,
738
+ updatedtime: updatedtime,
739
+ messageDiv: messageDiv
740
+ }));
741
+ if (this._history.length > this._historyLimit) {
742
+ this._history.shift();
743
+ }
744
+ }
745
+ if (this._onMessageAdded) {
746
+ this._onMessageAdded(this, msgid);
747
+ }
748
+ return msgid;
749
+ }
750
+ }, {
751
+ key: "messageAddNew",
752
+ value: function messageAddNew() {
753
+ var content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
754
+ var userString = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "user";
755
+ var align = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "right";
756
+ var role = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "user";
757
+ return this.messageAddFull({
758
+ content: content,
759
+ userString: userString,
760
+ align: align,
761
+ role: role
762
+ });
763
+ }
764
+ }, {
765
+ key: "messageRemove",
766
+ value: function messageRemove(n) {
767
+ var success = false;
768
+ try {
769
+ this._messagesArea.removeChild(this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0'))));
770
+ success = true;
771
+ } catch (_error) {
772
+ // Message ID not found
773
+ }
774
+ if (success) {
775
+ this._history.splice(this._history.findIndex(function (item) {
776
+ return item.msgid === n;
777
+ }), 1);
778
+ if (this._onMessageDelete) {
779
+ this._onMessageDelete(this, n);
780
+ }
781
+ }
782
+ return success;
783
+ }
784
+ /* returns the message html object from the DOM
785
+ */
786
+ }, {
787
+ key: "messageGetDOMObject",
788
+ value: function messageGetDOMObject(n) {
789
+ var msg = null;
790
+ try {
791
+ msg = this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0')));
792
+ } catch (_error) {
793
+ // Message ID not found
794
+ }
795
+ return msg;
796
+ }
797
+ /* returns the message content only
798
+ */
799
+ }, {
800
+ key: "messageGetContent",
801
+ value: function messageGetContent(n) {
802
+ var content = "";
803
+ try {
804
+ content = this._history.filter(function (item) {
805
+ return item.msgid === n;
806
+ })[0].content;
807
+ } catch (_error) {
808
+ // Message ID not found
809
+ }
810
+ return content;
811
+ }
812
+ }, {
813
+ key: "messageSetVisible",
814
+ value: function messageSetVisible(n, visible) {
815
+ var msgEl = this.messageGetDOMObject(n);
816
+ if (!msgEl) return false;
817
+ msgEl.style.display = visible ? '' : 'none';
818
+ var item = this._history.find(function (entry) {
819
+ return entry.msgid === n;
820
+ });
821
+ if (item) item.visible = visible;
822
+ return true;
823
+ }
824
+ }, {
825
+ key: "messageGetVisible",
826
+ value: function messageGetVisible(n) {
827
+ var item = this._history.find(function (entry) {
828
+ return entry.msgid === n;
829
+ });
830
+ return item ? item.visible !== false : false;
831
+ }
832
+ }, {
833
+ key: "messageToggleVisible",
834
+ value: function messageToggleVisible(n) {
835
+ var current = this.messageGetVisible(n);
836
+ return this.messageSetVisible(n, !current);
837
+ }
838
+ }, {
839
+ key: "messageSetVisibleByTag",
840
+ value: function messageSetVisibleByTag(tag, visible) {
841
+ var count = 0;
842
+ var _iterator = _createForOfIteratorHelper(this._history),
843
+ _step;
844
+ try {
845
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
846
+ var item = _step.value;
847
+ if (item.tags && item.tags.includes(tag)) {
848
+ this.messageSetVisible(item.msgid, visible);
849
+ count++;
850
+ }
851
+ }
852
+ } catch (err) {
853
+ _iterator.e(err);
854
+ } finally {
855
+ _iterator.f();
856
+ }
857
+ return count;
858
+ }
859
+ }, {
860
+ key: "messageGetTags",
861
+ value: function messageGetTags(n) {
862
+ var item = this._history.find(function (entry) {
863
+ return entry.msgid === n;
864
+ });
865
+ return item && item.tags ? item.tags.slice() : [];
866
+ }
867
+ }, {
868
+ key: "messageSetTags",
869
+ value: function messageSetTags(n, tags) {
870
+ var item = this._history.find(function (entry) {
871
+ return entry.msgid === n;
872
+ });
873
+ if (!item) return false;
874
+ item.tags = Array.isArray(tags) ? tags.slice() : [];
875
+ return true;
876
+ }
877
+
878
+ /* append message to the message content
879
+ */
880
+ }, {
881
+ key: "messageAppendContent",
882
+ value: function messageAppendContent(n, content) {
883
+ var success = false;
884
+ try {
885
+ var msgEl = this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0')));
886
+ var item = this._history.filter(function (entry) {
887
+ return entry.msgid === n;
888
+ })[0];
889
+ item.content += content;
890
+ item.updatedtime = new Date().toISOString();
891
+ msgEl.querySelector('.quikchat-message-content').innerHTML = this._processContent(item.content);
892
+ msgEl.classList.remove('quikchat-typing');
893
+ success = true;
894
+ if (!this.userScrolledUp) {
895
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
896
+ }
897
+ if (this._onMessageAppend) {
898
+ this._onMessageAppend(this, n, content);
899
+ }
900
+ } catch (_error) {
901
+ // Message ID not found
902
+ }
903
+ return success;
904
+ }
905
+
906
+ /* replace message content
907
+ */
908
+ }, {
909
+ key: "messageReplaceContent",
910
+ value: function messageReplaceContent(n, content) {
911
+ var success = false;
912
+ try {
913
+ var msgEl = this._messagesArea.querySelector(".quikchat-msgid-".concat(String(n).padStart(10, '0')));
914
+ var item = this._history.filter(function (entry) {
915
+ return entry.msgid === n;
916
+ })[0];
917
+ item.content = content;
918
+ item.updatedtime = new Date().toISOString();
919
+ msgEl.querySelector('.quikchat-message-content').innerHTML = this._processContent(content);
920
+ msgEl.classList.remove('quikchat-typing');
921
+ success = true;
922
+ if (!this.userScrolledUp) {
923
+ this._messagesArea.scrollTop = this._messagesArea.scrollHeight;
924
+ }
925
+ if (this._onMessageReplace) {
926
+ this._onMessageReplace(this, n, content);
927
+ }
928
+ } catch (_error) {
929
+ // Message ID not found
930
+ }
931
+ return success;
932
+ }
933
+ }, {
934
+ key: "messageAddTypingIndicator",
935
+ value: function messageAddTypingIndicator() {
936
+ var userString = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
937
+ var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'left';
938
+ var msgid = this.messageAddFull({
939
+ content: '',
940
+ userString: userString,
941
+ align: align,
942
+ role: 'assistant'
943
+ });
944
+ var msgEl = this.messageGetDOMObject(msgid);
945
+ msgEl.classList.add('quikchat-typing');
946
+ var contentDiv = msgEl.querySelector('.quikchat-message-content');
947
+ contentDiv.innerHTML = '<span class="quikchat-typing-dots"><span>.</span><span>.</span><span>.</span></span>';
948
+ return msgid;
949
+ }
950
+ }, {
951
+ key: "setMessageFormatter",
952
+ value: function setMessageFormatter(formatter) {
953
+ this._messageFormatter = formatter;
954
+ }
955
+ }, {
956
+ key: "setSanitize",
957
+ value: function setSanitize(sanitize) {
958
+ this._sanitize = sanitize;
959
+ }
960
+
961
+ // history functions
962
+ /**
963
+ *
964
+ * @param {*} n
965
+ * @param {*} m
966
+ * @returns array of history messages
967
+ */
968
+ }, {
969
+ key: "historyGet",
970
+ value: function historyGet(n, m) {
971
+ if (n === undefined) {
972
+ return this._history.slice();
973
+ }
974
+ if (m === undefined) {
975
+ if (n < 0) {
976
+ return this._history.slice(n);
977
+ }
978
+ return this._history.slice(n, n + 1);
979
+ }
980
+ return this._history.slice(n, m);
981
+ }
982
+ }, {
983
+ key: "historyClear",
984
+ value: function historyClear() {
985
+ this.msgid = 0;
986
+ this._history = [];
987
+ }
988
+ }, {
989
+ key: "historyGetLength",
990
+ value: function historyGetLength() {
991
+ return this._history.length;
992
+ }
993
+ }, {
994
+ key: "historyGetMessage",
995
+ value: function historyGetMessage(n) {
996
+ if (n >= 0 && n < this._history.length) {
997
+ return this._history[n];
998
+ }
999
+ return {};
1000
+ }
1001
+ }, {
1002
+ key: "historyGetMessageContent",
1003
+ value: function historyGetMessageContent(n) {
1004
+ if (n >= 0 && n < this._history.length) {
1005
+ return this._history[n].content;
1006
+ }
1007
+ return "";
1008
+ }
1009
+ }, {
1010
+ key: "historyExport",
1011
+ value: function historyExport() {
1012
+ return this._history.map(function (item) {
1013
+ return {
1014
+ msgid: item.msgid,
1015
+ content: item.content,
1016
+ userString: item.userString,
1017
+ align: item.align,
1018
+ role: item.role,
1019
+ userID: item.userID,
1020
+ visible: item.visible,
1021
+ tags: item.tags ? item.tags.slice() : [],
1022
+ timestamp: item.timestamp,
1023
+ updatedtime: item.updatedtime
1024
+ };
1025
+ });
1026
+ }
1027
+ }, {
1028
+ key: "historyImport",
1029
+ value: function historyImport(data) {
1030
+ // Clear existing messages from DOM and history
1031
+ this._messagesArea.innerHTML = '';
1032
+ this._history = [];
1033
+ this.msgid = 0;
1034
+ var _iterator2 = _createForOfIteratorHelper(data),
1035
+ _step2;
1036
+ try {
1037
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1038
+ var entry = _step2.value;
1039
+ this.messageAddFull({
1040
+ content: entry.content || '',
1041
+ userString: entry.userString || 'user',
1042
+ align: entry.align || 'right',
1043
+ role: entry.role || 'user',
1044
+ userID: entry.userID,
1045
+ visible: entry.visible,
1046
+ tags: entry.tags
1047
+ });
1048
+ }
1049
+ } catch (err) {
1050
+ _iterator2.e(err);
1051
+ } finally {
1052
+ _iterator2.f();
1053
+ }
1054
+ }
1055
+ }, {
1056
+ key: "changeTheme",
1057
+ value: function changeTheme(newTheme) {
1058
+ this._chatWidget.classList.remove(this._theme);
1059
+ this._chatWidget.classList.add(newTheme);
1060
+ this._theme = newTheme;
1061
+ }
1062
+ }, {
1063
+ key: "theme",
1064
+ get: function get() {
1065
+ return this._theme;
1066
+ }
1067
+ }], [{
1068
+ key: "version",
1069
+ value: function version() {
1070
+ return {
1071
+ "version": "1.2.4",
1072
+ "license": "BSD-2",
1073
+ "url": "https://github/deftio/quikchat"
1074
+ };
1075
+ }
1076
+
1077
+ /**
1078
+ * quikchat.loremIpsum() - Generate a simple string of Lorem Ipsum text (sample typographer's text) of numChars in length.
1079
+ * borrowed from github.com/deftio/bitwrench.js
1080
+ * @param {number} numChars - The number of characters to generate (random btw 25 and 150 if undefined).
1081
+ * @param {number} [startSpot=0] - The starting index in the Lorem Ipsum text. If undefined, a random startSpot will be generated.
1082
+ * @param {boolean} [startWithCapitalLetter=true] - If true, capitalize the first character or inject a capital letter if the first character isn't a capital letter.
1083
+ *
1084
+ * @returns {string} A string of Lorem Ipsum text.
1085
+ *
1086
+ * @example
1087
+ * // Returns 200 characters of Lorem Ipsum starting from index 50
1088
+ * loremIpsum(200, 50);
1089
+ *
1090
+ * @example
1091
+ * //Returns a 200 Lorem Ipsum characters starting from a random index
1092
+ * loremIpsum(200);
1093
+ */
1094
+ }, {
1095
+ key: "loremIpsum",
1096
+ value: function loremIpsum(numChars) {
1097
+ var startSpot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
1098
+ var startWithCapitalLetter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
1099
+ 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. ";
1100
+ if (typeof numChars !== "number") {
1101
+ numChars = Math.floor(Math.random() * 126) + 25;
1102
+ }
1103
+ if (startSpot === undefined) {
1104
+ startSpot = Math.floor(Math.random() * loremText.length);
1105
+ }
1106
+ startSpot = startSpot % loremText.length;
1107
+
1108
+ // Move startSpot to the next non-whitespace and non-punctuation character
1109
+ while (loremText[startSpot] === ' ' || /[.,:;!?]/.test(loremText[startSpot])) {
1110
+ startSpot = (startSpot + 1) % loremText.length;
1111
+ }
1112
+ var l = loremText.substring(startSpot) + loremText.substring(0, startSpot);
1113
+ var s = "";
1114
+ while (numChars > 0) {
1115
+ s += numChars < l.length ? l.substring(0, numChars) : l;
1116
+ numChars -= l.length;
1117
+ }
1118
+ if (s[s.length - 1] === " ") {
1119
+ s = s.substring(0, s.length - 1) + "."; // always end on non-whitespace. "." was chosen arbitrarily.
1120
+ }
1121
+ if (startWithCapitalLetter) {
1122
+ s = s[0].toUpperCase() + s.substring(1);
1123
+ }
1124
+ return s;
1125
+ }
1126
+ }]);
1127
+ }();
1128
+
1129
+ /**
1130
+ * quikdown_bd - Bidirectional Markdown Parser
1131
+ * @version 1.1.1
1132
+ * @license BSD-2-Clause
1133
+ * @copyright DeftIO 2025
1134
+ */
1135
+ /**
1136
+ * quikdown - A minimal markdown parser optimized for chat/LLM output
1137
+ * Supports tables, code blocks, lists, and common formatting
1138
+ * @param {string} markdown - The markdown source text
1139
+ * @param {Object} options - Optional configuration object
1140
+ * @param {Function} options.fence_plugin - Custom renderer for fenced code blocks
1141
+ * (content, fence_string) => html string
1142
+ * @param {boolean} options.inline_styles - If true, uses inline styles instead of classes
1143
+ * @param {boolean} options.bidirectional - If true, adds data-qd attributes for source tracking
1144
+ * @param {boolean} options.lazy_linefeeds - If true, single newlines become <br> tags
1145
+ * @returns {string} - The rendered HTML
1146
+ */
1147
+
1148
+ // Version will be injected at build time
1149
+ const quikdownVersion = '1.1.1';
1150
+
1151
+ // Constants for reuse
1152
+ const CLASS_PREFIX = 'quikdown-';
1153
+ const PLACEHOLDER_CB = '§CB';
1154
+ const PLACEHOLDER_IC = '§IC';
1155
+
1156
+ // Escape map at module level
1157
+ const ESC_MAP = {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'};
1158
+
1159
+ // Single source of truth for all style definitions - optimized
1160
+ const QUIKDOWN_STYLES = {
1161
+ h1: 'font-size:2em;font-weight:600;margin:.67em 0;text-align:left',
1162
+ h2: 'font-size:1.5em;font-weight:600;margin:.83em 0',
1163
+ h3: 'font-size:1.25em;font-weight:600;margin:1em 0',
1164
+ h4: 'font-size:1em;font-weight:600;margin:1.33em 0',
1165
+ h5: 'font-size:.875em;font-weight:600;margin:1.67em 0',
1166
+ h6: 'font-size:.85em;font-weight:600;margin:2em 0',
1167
+ pre: 'background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0',
1168
+ code: 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace',
1169
+ blockquote: 'border-left:4px solid #ddd;margin-left:0;padding-left:1em',
1170
+ table: 'border-collapse:collapse;width:100%;margin:1em 0',
1171
+ th: 'border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left',
1172
+ td: 'border:1px solid #ddd;padding:8px;text-align:left',
1173
+ hr: 'border:none;border-top:1px solid #ddd;margin:1em 0',
1174
+ img: 'max-width:100%;height:auto',
1175
+ a: 'color:#06c;text-decoration:underline',
1176
+ strong: 'font-weight:bold',
1177
+ em: 'font-style:italic',
1178
+ del: 'text-decoration:line-through',
1179
+ ul: 'margin:.5em 0;padding-left:2em',
1180
+ ol: 'margin:.5em 0;padding-left:2em',
1181
+ li: 'margin:.25em 0',
1182
+ // Task list specific styles
1183
+ 'task-item': 'list-style:none',
1184
+ 'task-checkbox': 'margin-right:.5em'
1185
+ };
1186
+
1187
+ // Factory function to create getAttr for a given context
1188
+ function createGetAttr(inline_styles, styles) {
1189
+ return function(tag, additionalStyle = '') {
1190
+ if (inline_styles) {
1191
+ let style = styles[tag];
1192
+ if (!style && !additionalStyle) return '';
1193
+
1194
+ // Remove default text-align if we're adding a different alignment
1195
+ if (additionalStyle && additionalStyle.includes('text-align') && style && style.includes('text-align')) {
1196
+ style = style.replace(/text-align:[^;]+;?/, '').trim();
1197
+ if (style && !style.endsWith(';')) style += ';';
1198
+ }
1199
+
1200
+ /* istanbul ignore next - defensive: additionalStyle without style doesn't occur with current tags */
1201
+ const fullStyle = additionalStyle ? (style ? `${style}${additionalStyle}` : additionalStyle) : style;
1202
+ return ` style="${fullStyle}"`;
1203
+ } else {
1204
+ const classAttr = ` class="${CLASS_PREFIX}${tag}"`;
1205
+ // Apply inline styles for alignment even when using CSS classes
1206
+ if (additionalStyle) {
1207
+ return `${classAttr} style="${additionalStyle}"`;
1208
+ }
1209
+ return classAttr;
1210
+ }
1211
+ };
1212
+ }
1213
+
1214
+ function quikdown(markdown, options = {}) {
1215
+ if (!markdown || typeof markdown !== 'string') {
1216
+ return '';
1217
+ }
1218
+
1219
+ const { fence_plugin, inline_styles = false, bidirectional = false, lazy_linefeeds = false } = options;
1220
+ const styles = QUIKDOWN_STYLES; // Use module-level styles
1221
+ const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once
1222
+
1223
+ // Escape HTML entities to prevent XSS
1224
+ function escapeHtml(text) {
1225
+ return text.replace(/[&<>"']/g, m => ESC_MAP[m]);
1226
+ }
1227
+
1228
+ // Helper to add data-qd attributes for bidirectional support
1229
+ const dataQd = bidirectional ? (marker) => ` data-qd="${escapeHtml(marker)}"` : () => '';
1230
+
1231
+ // Sanitize URLs to prevent XSS attacks
1232
+ function sanitizeUrl(url, allowUnsafe = false) {
1233
+ /* istanbul ignore next - defensive programming, regex ensures url is never empty */
1234
+ if (!url) return '';
1235
+
1236
+ // If unsafe URLs are explicitly allowed, return as-is
1237
+ if (allowUnsafe) return url;
1238
+
1239
+ const trimmedUrl = url.trim();
1240
+ const lowerUrl = trimmedUrl.toLowerCase();
1241
+
1242
+ // Block dangerous protocols
1243
+ const dangerousProtocols = ['javascript:', 'vbscript:', 'data:'];
1244
+
1245
+ for (const protocol of dangerousProtocols) {
1246
+ if (lowerUrl.startsWith(protocol)) {
1247
+ // Exception: Allow data:image/* for images
1248
+ if (protocol === 'data:' && lowerUrl.startsWith('data:image/')) {
1249
+ return trimmedUrl;
1250
+ }
1251
+ // Return safe empty link for dangerous protocols
1252
+ return '#';
1253
+ }
1254
+ }
1255
+
1256
+ return trimmedUrl;
1257
+ }
1258
+
1259
+ // Process the markdown in phases
1260
+ let html = markdown;
1261
+
1262
+ // Phase 1: Extract and protect code blocks and inline code
1263
+ const codeBlocks = [];
1264
+ const inlineCodes = [];
1265
+
1266
+ // Extract fenced code blocks first (supports both ``` and ~~~)
1267
+ // Match paired fences - ``` with ``` and ~~~ with ~~~
1268
+ // Fence must be at start of line
1269
+ html = html.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm, (match, fence, lang, code) => {
1270
+ const placeholder = `${PLACEHOLDER_CB}${codeBlocks.length}§`;
1271
+
1272
+ // Trim the language specification
1273
+ const langTrimmed = lang ? lang.trim() : '';
1274
+
1275
+ // If custom fence plugin is provided, use it (v1.1.0: object format required)
1276
+ if (fence_plugin && fence_plugin.render && typeof fence_plugin.render === 'function') {
1277
+ codeBlocks.push({
1278
+ lang: langTrimmed,
1279
+ code: code.trimEnd(),
1280
+ custom: true,
1281
+ fence: fence,
1282
+ hasReverse: !!fence_plugin.reverse
1283
+ });
1284
+ } else {
1285
+ codeBlocks.push({
1286
+ lang: langTrimmed,
1287
+ code: escapeHtml(code.trimEnd()),
1288
+ custom: false,
1289
+ fence: fence
1290
+ });
1291
+ }
1292
+ return placeholder;
1293
+ });
1294
+
1295
+ // Extract inline code
1296
+ html = html.replace(/`([^`]+)`/g, (match, code) => {
1297
+ const placeholder = `${PLACEHOLDER_IC}${inlineCodes.length}§`;
1298
+ inlineCodes.push(escapeHtml(code));
1299
+ return placeholder;
1300
+ });
1301
+
1302
+ // Now escape HTML in the rest of the content
1303
+ html = escapeHtml(html);
1304
+
1305
+ // Phase 2: Process block elements
1306
+
1307
+ // Process tables
1308
+ html = processTable(html, getAttr);
1309
+
1310
+ // Process headings (supports optional trailing #'s)
1311
+ html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
1312
+ const level = hashes.length;
1313
+ return `<h${level}${getAttr('h' + level)}${dataQd(hashes)}>${content}</h${level}>`;
1314
+ });
1315
+
1316
+ // Process blockquotes (must handle escaped > since we already escaped HTML)
1317
+ html = html.replace(/^&gt;\s+(.+)$/gm, `<blockquote${getAttr('blockquote')}>$1</blockquote>`);
1318
+ // Merge consecutive blockquotes
1319
+ html = html.replace(/<\/blockquote>\n<blockquote>/g, '\n');
1320
+
1321
+ // Process horizontal rules (allow trailing spaces)
1322
+ html = html.replace(/^---+\s*$/gm, `<hr${getAttr('hr')}>`);
1323
+
1324
+ // Process lists
1325
+ html = processLists(html, getAttr, inline_styles, bidirectional);
1326
+
1327
+ // Phase 3: Process inline elements
1328
+
1329
+ // Images (must come before links, with URL sanitization)
1330
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
1331
+ const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);
1332
+ const altAttr = bidirectional && alt ? ` data-qd-alt="${escapeHtml(alt)}"` : '';
1333
+ const srcAttr = bidirectional ? ` data-qd-src="${escapeHtml(src)}"` : '';
1334
+ return `<img${getAttr('img')} src="${sanitizedSrc}" alt="${alt}"${altAttr}${srcAttr}${dataQd('!')}>`;
1335
+ });
1336
+
1337
+ // Links (with URL sanitization)
1338
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
1339
+ // Sanitize URL to prevent XSS
1340
+ const sanitizedHref = sanitizeUrl(href, options.allow_unsafe_urls);
1341
+ const isExternal = /^https?:\/\//i.test(sanitizedHref);
1342
+ const rel = isExternal ? ' rel="noopener noreferrer"' : '';
1343
+ const textAttr = bidirectional ? ` data-qd-text="${escapeHtml(text)}"` : '';
1344
+ return `<a${getAttr('a')} href="${sanitizedHref}"${rel}${textAttr}${dataQd('[')}>${text}</a>`;
1345
+ });
1346
+
1347
+ // Autolinks - convert bare URLs to clickable links
1348
+ html = html.replace(/(^|\s)(https?:\/\/[^\s<]+)/g, (match, prefix, url) => {
1349
+ const sanitizedUrl = sanitizeUrl(url, options.allow_unsafe_urls);
1350
+ return `${prefix}<a${getAttr('a')} href="${sanitizedUrl}" rel="noopener noreferrer">${url}</a>`;
1351
+ });
1352
+
1353
+ // Process inline formatting (bold, italic, strikethrough)
1354
+ const inlinePatterns = [
1355
+ [/\*\*(.+?)\*\*/g, 'strong', '**'],
1356
+ [/__(.+?)__/g, 'strong', '__'],
1357
+ [/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em', '*'],
1358
+ [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em', '_'],
1359
+ [/~~(.+?)~~/g, 'del', '~~']
1360
+ ];
1361
+
1362
+ inlinePatterns.forEach(([pattern, tag, marker]) => {
1363
+ html = html.replace(pattern, `<${tag}${getAttr(tag)}${dataQd(marker)}>$1</${tag}>`);
1364
+ });
1365
+
1366
+ // Line breaks
1367
+ if (lazy_linefeeds) {
1368
+ // Lazy linefeeds: single newline becomes <br> (except between paragraphs and after/before block elements)
1369
+ const blocks = [];
1370
+ let bi = 0;
1371
+
1372
+ // Protect tables and lists
1373
+ html = html.replace(/<(table|[uo]l)[^>]*>[\s\S]*?<\/\1>/g, m => {
1374
+ blocks[bi] = m;
1375
+ return `§B${bi++}§`;
1376
+ });
1377
+
1378
+ // Handle paragraphs and block elements
1379
+ html = html.replace(/\n\n+/g, '§P§')
1380
+ // After block elements
1381
+ .replace(/(<\/(?:h[1-6]|blockquote|pre)>)\n/g, '$1§N§')
1382
+ .replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\n/g, '$1§N§')
1383
+ // Before block elements
1384
+ .replace(/\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g, '§N§$1')
1385
+ .replace(/\n(§B\d+§)/g, '§N§$1')
1386
+ .replace(/(§B\d+§)\n/g, '$1§N§')
1387
+ // Convert remaining newlines
1388
+ .replace(/\n/g, `<br${getAttr('br')}>`)
1389
+ // Restore
1390
+ .replace(/§N§/g, '\n')
1391
+ .replace(/§P§/g, '</p><p>');
1392
+
1393
+ // Restore protected blocks
1394
+ blocks.forEach((b, i) => html = html.replace(`§B${i}§`, b));
1395
+
1396
+ html = '<p>' + html + '</p>';
1397
+ } else {
1398
+ // Standard: two spaces at end of line for line breaks
1399
+ html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
1400
+
1401
+ // Paragraphs (double newlines)
1402
+ // Don't add </p> after block elements (they're not in paragraphs)
1403
+ html = html.replace(/\n\n+/g, (match, offset) => {
1404
+ // Check if we're after a block element closing tag
1405
+ const before = html.substring(0, offset);
1406
+ if (before.match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)) {
1407
+ return '<p>'; // Just open a new paragraph
1408
+ }
1409
+ return '</p><p>'; // Normal paragraph break
1410
+ });
1411
+ html = '<p>' + html + '</p>';
1412
+ }
1413
+
1414
+ // Clean up empty paragraphs and unwrap block elements
1415
+ const cleanupPatterns = [
1416
+ [/<p><\/p>/g, ''],
1417
+ [/<p>(<h[1-6][^>]*>)/g, '$1'],
1418
+ [/(<\/h[1-6]>)<\/p>/g, '$1'],
1419
+ [/<p>(<blockquote[^>]*>)/g, '$1'],
1420
+ [/(<\/blockquote>)<\/p>/g, '$1'],
1421
+ [/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1'],
1422
+ [/(<\/ul>|<\/ol>)<\/p>/g, '$1'],
1423
+ [/<p>(<hr[^>]*>)<\/p>/g, '$1'],
1424
+ [/<p>(<table[^>]*>)/g, '$1'],
1425
+ [/(<\/table>)<\/p>/g, '$1'],
1426
+ [/<p>(<pre[^>]*>)/g, '$1'],
1427
+ [/(<\/pre>)<\/p>/g, '$1'],
1428
+ [new RegExp(`<p>(${PLACEHOLDER_CB}\\d+§)<\/p>`, 'g'), '$1']
1429
+ ];
1430
+
1431
+ cleanupPatterns.forEach(([pattern, replacement]) => {
1432
+ html = html.replace(pattern, replacement);
1433
+ });
1434
+
1435
+ // Fix orphaned closing </p> tags after block elements
1436
+ // When a paragraph follows a block element, ensure it has opening <p>
1437
+ html = html.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g, '$1\n<p>$2');
1438
+
1439
+ // Phase 4: Restore code blocks and inline code
1440
+
1441
+ // Restore code blocks
1442
+ codeBlocks.forEach((block, i) => {
1443
+ let replacement;
1444
+
1445
+ if (block.custom && fence_plugin && fence_plugin.render) {
1446
+ // Use custom fence plugin (v1.1.0: object format with render function)
1447
+ replacement = fence_plugin.render(block.code, block.lang);
1448
+
1449
+ // If plugin returns undefined, fall back to default rendering
1450
+ if (replacement === undefined) {
1451
+ const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
1452
+ const codeAttr = inline_styles ? getAttr('code') : langClass;
1453
+ const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
1454
+ const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
1455
+ replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;
1456
+ } else if (bidirectional) {
1457
+ // If bidirectional and plugin provided HTML, add data attributes for roundtrip
1458
+ replacement = replacement.replace(/^<(\w+)/,
1459
+ `<$1 data-qd-fence="${escapeHtml(block.fence)}" data-qd-lang="${escapeHtml(block.lang)}" data-qd-source="${escapeHtml(block.code)}"`);
1460
+ }
1461
+ } else {
1462
+ // Default rendering
1463
+ const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
1464
+ const codeAttr = inline_styles ? getAttr('code') : langClass;
1465
+ const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
1466
+ const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
1467
+ replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${block.code}</code></pre>`;
1468
+ }
1469
+
1470
+ const placeholder = `${PLACEHOLDER_CB}${i}§`;
1471
+ html = html.replace(placeholder, replacement);
1472
+ });
1473
+
1474
+ // Restore inline code
1475
+ inlineCodes.forEach((code, i) => {
1476
+ const placeholder = `${PLACEHOLDER_IC}${i}§`;
1477
+ html = html.replace(placeholder, `<code${getAttr('code')}${dataQd('`')}>${code}</code>`);
1478
+ });
1479
+
1480
+ return html.trim();
1481
+ }
1482
+
1483
+ /**
1484
+ * Process inline markdown formatting
1485
+ */
1486
+ function processInlineMarkdown(text, getAttr) {
1487
+
1488
+ // Process inline formatting patterns
1489
+ const patterns = [
1490
+ [/\*\*(.+?)\*\*/g, 'strong'],
1491
+ [/__(.+?)__/g, 'strong'],
1492
+ [/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
1493
+ [/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
1494
+ [/~~(.+?)~~/g, 'del'],
1495
+ [/`([^`]+)`/g, 'code']
1496
+ ];
1497
+
1498
+ patterns.forEach(([pattern, tag]) => {
1499
+ text = text.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
1500
+ });
1501
+
1502
+ return text;
1503
+ }
1504
+
1505
+ /**
1506
+ * Process markdown tables
1507
+ */
1508
+ function processTable(text, getAttr) {
1509
+ const lines = text.split('\n');
1510
+ const result = [];
1511
+ let inTable = false;
1512
+ let tableLines = [];
1513
+
1514
+ for (let i = 0; i < lines.length; i++) {
1515
+ const line = lines[i].trim();
1516
+
1517
+ // Check if this line looks like a table row (with or without trailing |)
1518
+ if (line.includes('|') && (line.startsWith('|') || /[^\\|]/.test(line))) {
1519
+ if (!inTable) {
1520
+ inTable = true;
1521
+ tableLines = [];
1522
+ }
1523
+ tableLines.push(line);
1524
+ } else {
1525
+ // Not a table line
1526
+ if (inTable) {
1527
+ // Process the accumulated table
1528
+ const tableHtml = buildTable(tableLines, getAttr);
1529
+ if (tableHtml) {
1530
+ result.push(tableHtml);
1531
+ } else {
1532
+ // Not a valid table, restore original lines
1533
+ result.push(...tableLines);
1534
+ }
1535
+ inTable = false;
1536
+ tableLines = [];
1537
+ }
1538
+ result.push(lines[i]);
1539
+ }
1540
+ }
1541
+
1542
+ // Handle table at end of text
1543
+ if (inTable && tableLines.length > 0) {
1544
+ const tableHtml = buildTable(tableLines, getAttr);
1545
+ if (tableHtml) {
1546
+ result.push(tableHtml);
1547
+ } else {
1548
+ result.push(...tableLines);
1549
+ }
1550
+ }
1551
+
1552
+ return result.join('\n');
1553
+ }
1554
+
1555
+ /**
1556
+ * Build an HTML table from markdown table lines
1557
+ */
1558
+ function buildTable(lines, getAttr) {
1559
+
1560
+ if (lines.length < 2) return null;
1561
+
1562
+ // Check for separator line (second line should be the separator)
1563
+ let separatorIndex = -1;
1564
+ for (let i = 1; i < lines.length; i++) {
1565
+ // Support separator with or without leading/trailing pipes
1566
+ if (/^\|?[\s\-:|]+\|?$/.test(lines[i]) && lines[i].includes('-')) {
1567
+ separatorIndex = i;
1568
+ break;
1569
+ }
1570
+ }
1571
+
1572
+ if (separatorIndex === -1) return null;
1573
+
1574
+ const headerLines = lines.slice(0, separatorIndex);
1575
+ const bodyLines = lines.slice(separatorIndex + 1);
1576
+
1577
+ // Parse alignment from separator
1578
+ const separator = lines[separatorIndex];
1579
+ // Handle pipes at start/end or not
1580
+ const separatorCells = separator.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
1581
+ const alignments = separatorCells.map(cell => {
1582
+ const trimmed = cell.trim();
1583
+ if (trimmed.startsWith(':') && trimmed.endsWith(':')) return 'center';
1584
+ if (trimmed.endsWith(':')) return 'right';
1585
+ return 'left';
1586
+ });
1587
+
1588
+ let html = `<table${getAttr('table')}>\n`;
1589
+
1590
+ // Build header
1591
+ // Note: headerLines will always have length > 0 since separatorIndex starts from 1
1592
+ html += `<thead${getAttr('thead')}>\n`;
1593
+ headerLines.forEach(line => {
1594
+ html += `<tr${getAttr('tr')}>\n`;
1595
+ // Handle pipes at start/end or not
1596
+ const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
1597
+ cells.forEach((cell, i) => {
1598
+ const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
1599
+ const processedCell = processInlineMarkdown(cell.trim(), getAttr);
1600
+ html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\n`;
1601
+ });
1602
+ html += '</tr>\n';
1603
+ });
1604
+ html += '</thead>\n';
1605
+
1606
+ // Build body
1607
+ if (bodyLines.length > 0) {
1608
+ html += `<tbody${getAttr('tbody')}>\n`;
1609
+ bodyLines.forEach(line => {
1610
+ html += `<tr${getAttr('tr')}>\n`;
1611
+ // Handle pipes at start/end or not
1612
+ const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
1613
+ cells.forEach((cell, i) => {
1614
+ const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
1615
+ const processedCell = processInlineMarkdown(cell.trim(), getAttr);
1616
+ html += `<td${getAttr('td', alignStyle)}>${processedCell}</td>\n`;
1617
+ });
1618
+ html += '</tr>\n';
1619
+ });
1620
+ html += '</tbody>\n';
1621
+ }
1622
+
1623
+ html += '</table>';
1624
+ return html;
1625
+ }
1626
+
1627
+ /**
1628
+ * Process markdown lists (ordered and unordered)
1629
+ */
1630
+ function processLists(text, getAttr, inline_styles, bidirectional) {
1631
+
1632
+ const lines = text.split('\n');
1633
+ const result = [];
1634
+ let listStack = []; // Track nested lists
1635
+
1636
+ // Helper to escape HTML for data-qd attributes
1637
+ const escapeHtml = (text) => text.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[m]);
1638
+ const dataQd = bidirectional ? (marker) => ` data-qd="${escapeHtml(marker)}"` : () => '';
1639
+
1640
+ for (let i = 0; i < lines.length; i++) {
1641
+ const line = lines[i];
1642
+ const match = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);
1643
+
1644
+ if (match) {
1645
+ const [, indent, marker, content] = match;
1646
+ const level = Math.floor(indent.length / 2);
1647
+ const isOrdered = /^\d+\./.test(marker);
1648
+ const listType = isOrdered ? 'ol' : 'ul';
1649
+
1650
+ // Check for task list items
1651
+ let listItemContent = content;
1652
+ let taskListClass = '';
1653
+ const taskMatch = content.match(/^\[([x ])\]\s+(.*)$/i);
1654
+ if (taskMatch && !isOrdered) {
1655
+ const [, checked, taskContent] = taskMatch;
1656
+ const isChecked = checked.toLowerCase() === 'x';
1657
+ const checkboxAttr = inline_styles
1658
+ ? ' style="margin-right:.5em"'
1659
+ : ` class="${CLASS_PREFIX}task-checkbox"`;
1660
+ listItemContent = `<input type="checkbox"${checkboxAttr}${isChecked ? ' checked' : ''} disabled> ${taskContent}`;
1661
+ taskListClass = inline_styles ? ' style="list-style:none"' : ` class="${CLASS_PREFIX}task-item"`;
1662
+ }
1663
+
1664
+ // Close deeper levels
1665
+ while (listStack.length > level + 1) {
1666
+ const list = listStack.pop();
1667
+ result.push(`</${list.type}>`);
1668
+ }
1669
+
1670
+ // Open new level if needed
1671
+ if (listStack.length === level) {
1672
+ // Need to open a new list
1673
+ listStack.push({ type: listType, level });
1674
+ result.push(`<${listType}${getAttr(listType)}>`);
1675
+ } else if (listStack.length === level + 1) {
1676
+ // Check if we need to switch list type
1677
+ const currentList = listStack[listStack.length - 1];
1678
+ if (currentList.type !== listType) {
1679
+ result.push(`</${currentList.type}>`);
1680
+ listStack.pop();
1681
+ listStack.push({ type: listType, level });
1682
+ result.push(`<${listType}${getAttr(listType)}>`);
1683
+ }
1684
+ }
1685
+
1686
+ const liAttr = taskListClass || getAttr('li');
1687
+ result.push(`<li${liAttr}${dataQd(marker)}>${listItemContent}</li>`);
1688
+ } else {
1689
+ // Not a list item, close all lists
1690
+ while (listStack.length > 0) {
1691
+ const list = listStack.pop();
1692
+ result.push(`</${list.type}>`);
1693
+ }
1694
+ result.push(line);
1695
+ }
1696
+ }
1697
+
1698
+ // Close any remaining lists
1699
+ while (listStack.length > 0) {
1700
+ const list = listStack.pop();
1701
+ result.push(`</${list.type}>`);
1702
+ }
1703
+
1704
+ return result.join('\n');
1705
+ }
1706
+
1707
+ /**
1708
+ * Emit CSS styles for quikdown elements
1709
+ * @param {string} prefix - Optional class prefix (default: 'quikdown-')
1710
+ * @param {string} theme - Optional theme: 'light' (default) or 'dark'
1711
+ * @returns {string} CSS string with quikdown styles
1712
+ */
1713
+ quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
1714
+ const styles = QUIKDOWN_STYLES;
1715
+
1716
+ // Define theme color overrides
1717
+ const themeOverrides = {
1718
+ dark: {
1719
+ '#f4f4f4': '#2a2a2a', // pre background
1720
+ '#f0f0f0': '#2a2a2a', // code background
1721
+ '#f2f2f2': '#2a2a2a', // th background
1722
+ '#ddd': '#3a3a3a', // borders
1723
+ '#06c': '#6db3f2', // links
1724
+ _textColor: '#e0e0e0'
1725
+ },
1726
+ light: {
1727
+ _textColor: '#333' // Explicit text color for light theme
1728
+ }
1729
+ };
1730
+
1731
+ let css = '';
1732
+ for (const [tag, style] of Object.entries(styles)) {
1733
+ let themedStyle = style;
1734
+
1735
+ // Apply theme overrides if dark theme
1736
+ if (theme === 'dark' && themeOverrides.dark) {
1737
+ // Replace colors
1738
+ for (const [oldColor, newColor] of Object.entries(themeOverrides.dark)) {
1739
+ if (!oldColor.startsWith('_')) {
1740
+ themedStyle = themedStyle.replace(new RegExp(oldColor, 'g'), newColor);
1741
+ }
1742
+ }
1743
+
1744
+ // Add text color for certain elements in dark theme
1745
+ const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];
1746
+ if (needsTextColor.includes(tag)) {
1747
+ themedStyle += `;color:${themeOverrides.dark._textColor}`;
1748
+ }
1749
+ } else if (theme === 'light' && themeOverrides.light) {
1750
+ // Add explicit text color for light theme elements too
1751
+ const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];
1752
+ if (needsTextColor.includes(tag)) {
1753
+ themedStyle += `;color:${themeOverrides.light._textColor}`;
1754
+ }
1755
+ }
1756
+
1757
+ css += `.${prefix}${tag} { ${themedStyle} }\n`;
1758
+ }
1759
+
1760
+ return css;
1761
+ };
1762
+
1763
+ /**
1764
+ * Configure quikdown with options and return a function
1765
+ * @param {Object} options - Configuration options
1766
+ * @returns {Function} Configured quikdown function
1767
+ */
1768
+ quikdown.configure = function(options) {
1769
+ return function(markdown) {
1770
+ return quikdown(markdown, options);
1771
+ };
1772
+ };
1773
+
1774
+ /**
1775
+ * Version information
1776
+ */
1777
+ quikdown.version = quikdownVersion;
1778
+
1779
+ // Export for both CommonJS and ES6
1780
+ /* istanbul ignore next */
1781
+ if (typeof module !== 'undefined' && module.exports) {
1782
+ module.exports = quikdown;
1783
+ }
1784
+
1785
+ // For browser global
1786
+ /* istanbul ignore next */
1787
+ if (typeof window !== 'undefined') {
1788
+ window.quikdown = quikdown;
1789
+ }
1790
+
1791
+ /**
1792
+ * quikdown_bd - Bidirectional markdown/HTML converter
1793
+ * Extends core quikdown with HTML→Markdown conversion
1794
+ *
1795
+ * Uses data-qd attributes to preserve original markdown syntax
1796
+ * Enables HTML→Markdown conversion for quikdown-generated HTML
1797
+ */
1798
+
1799
+
1800
+ /**
1801
+ * Create bidirectional version by extending quikdown
1802
+ * This wraps quikdown and adds the toMarkdown method
1803
+ */
1804
+ function quikdown_bd(markdown, options = {}) {
1805
+ // Use core quikdown with bidirectional flag to add data-qd attributes
1806
+ return quikdown(markdown, { ...options, bidirectional: true });
1807
+ }
1808
+
1809
+ // Copy all properties and methods from quikdown (including version)
1810
+ Object.keys(quikdown).forEach(key => {
1811
+ quikdown_bd[key] = quikdown[key];
1812
+ });
1813
+
1814
+ // Add the toMarkdown method for HTML→Markdown conversion
1815
+ quikdown_bd.toMarkdown = function(htmlOrElement, options = {}) {
1816
+ // Accept either HTML string or DOM element
1817
+ let container;
1818
+ if (typeof htmlOrElement === 'string') {
1819
+ container = document.createElement('div');
1820
+ container.innerHTML = htmlOrElement;
1821
+ } else if (htmlOrElement instanceof Element) {
1822
+ /* istanbul ignore next - browser-only code path, not testable in jsdom */
1823
+ container = htmlOrElement;
1824
+ } else {
1825
+ return '';
1826
+ }
1827
+
1828
+ // Walk the DOM tree and reconstruct markdown
1829
+ function walkNode(node, parentContext = {}) {
1830
+ if (node.nodeType === Node.TEXT_NODE) {
1831
+ // Return text content, preserving whitespace where needed
1832
+ return node.textContent;
1833
+ }
1834
+
1835
+ if (node.nodeType !== Node.ELEMENT_NODE) {
1836
+ return '';
1837
+ }
1838
+
1839
+ const tag = node.tagName.toLowerCase();
1840
+ const dataQd = node.getAttribute('data-qd');
1841
+
1842
+ // Process children with context
1843
+ let childContent = '';
1844
+ for (let child of node.childNodes) {
1845
+ childContent += walkNode(child, { parentTag: tag, ...parentContext });
1846
+ }
1847
+
1848
+ // Determine markdown based on element and attributes
1849
+ switch (tag) {
1850
+ case 'h1':
1851
+ case 'h2':
1852
+ case 'h3':
1853
+ case 'h4':
1854
+ case 'h5':
1855
+ case 'h6':
1856
+ const level = parseInt(tag[1]);
1857
+ const prefix = dataQd || '#'.repeat(level);
1858
+ return `${prefix} ${childContent.trim()}\n\n`;
1859
+
1860
+ case 'strong':
1861
+ case 'b':
1862
+ if (!childContent) return ''; // Don't add markers for empty content
1863
+ const boldMarker = dataQd || '**';
1864
+ return `${boldMarker}${childContent}${boldMarker}`;
1865
+
1866
+ case 'em':
1867
+ case 'i':
1868
+ if (!childContent) return ''; // Don't add markers for empty content
1869
+ const emMarker = dataQd || '*';
1870
+ return `${emMarker}${childContent}${emMarker}`;
1871
+
1872
+ case 'del':
1873
+ case 's':
1874
+ case 'strike':
1875
+ if (!childContent) return ''; // Don't add markers for empty content
1876
+ const delMarker = dataQd || '~~';
1877
+ return `${delMarker}${childContent}${delMarker}`;
1878
+
1879
+ case 'code':
1880
+ // Note: code inside pre is handled directly by the pre case using querySelector
1881
+ if (!childContent) return ''; // Don't add markers for empty content
1882
+ const codeMarker = dataQd || '`';
1883
+ return `${codeMarker}${childContent}${codeMarker}`;
1884
+
1885
+ case 'pre':
1886
+ const fence = node.getAttribute('data-qd-fence') || dataQd || '```';
1887
+ const lang = node.getAttribute('data-qd-lang') || '';
1888
+
1889
+ // Check if this was created by a fence plugin with reverse handler
1890
+ if (options.fence_plugin && options.fence_plugin.reverse && lang) {
1891
+ try {
1892
+ const result = options.fence_plugin.reverse(node);
1893
+ if (result && result.content) {
1894
+ const fenceMarker = result.fence || fence;
1895
+ const langStr = result.lang || lang;
1896
+ return `${fenceMarker}${langStr}\n${result.content}\n${fenceMarker}\n\n`;
1897
+ }
1898
+ } catch (err) {
1899
+ console.warn('Fence reverse handler error:', err);
1900
+ // Fall through to default handling
1901
+ }
1902
+ }
1903
+
1904
+ // Fallback: use data-qd-source if available
1905
+ const source = node.getAttribute('data-qd-source');
1906
+ if (source) {
1907
+ return `${fence}${lang}\n${source}\n${fence}\n\n`;
1908
+ }
1909
+
1910
+ // Final fallback: extract text content
1911
+ const codeEl = node.querySelector('code');
1912
+ const codeContent = codeEl ? codeEl.textContent : childContent;
1913
+ return `${fence}${lang}\n${codeContent.trimEnd()}\n${fence}\n\n`;
1914
+
1915
+ case 'blockquote':
1916
+ const quoteMarker = dataQd || '>';
1917
+ const lines = childContent.trim().split('\n');
1918
+ return lines.map(line => `${quoteMarker} ${line}`).join('\n') + '\n\n';
1919
+
1920
+ case 'hr':
1921
+ const hrMarker = dataQd || '---';
1922
+ return `${hrMarker}\n\n`;
1923
+
1924
+ case 'br':
1925
+ const brMarker = dataQd || ' ';
1926
+ return `${brMarker}\n`;
1927
+
1928
+ case 'a':
1929
+ const linkText = node.getAttribute('data-qd-text') || childContent.trim();
1930
+ const href = node.getAttribute('href') || '';
1931
+ // Check for autolinks
1932
+ if (linkText === href && !dataQd) {
1933
+ return `<${href}>`;
1934
+ }
1935
+ return `[${linkText}](${href})`;
1936
+
1937
+ case 'img':
1938
+ const alt = node.getAttribute('data-qd-alt') || node.getAttribute('alt') || '';
1939
+ const src = node.getAttribute('data-qd-src') || node.getAttribute('src') || '';
1940
+ const imgMarker = dataQd || '!';
1941
+ return `${imgMarker}[${alt}](${src})`;
1942
+
1943
+ case 'ul':
1944
+ case 'ol':
1945
+ return walkList(node, tag === 'ol') + '\n';
1946
+
1947
+ case 'li':
1948
+ // Handled by list processor
1949
+ return childContent;
1950
+
1951
+ case 'table':
1952
+ return walkTable(node) + '\n\n';
1953
+
1954
+ case 'p':
1955
+ // Check if it's actually a paragraph or just a wrapper
1956
+ if (childContent.trim()) {
1957
+ // Check if paragraph ends with a line that's just whitespace
1958
+ // This indicates an intentional blank line before the next element
1959
+ const lines = childContent.split('\n');
1960
+ let content = childContent.trim();
1961
+
1962
+ // If the last line(s) are just whitespace, preserve one blank line
1963
+ if (lines.length > 1) {
1964
+ let trailingBlankLines = 0;
1965
+ for (let i = lines.length - 1; i >= 0; i--) {
1966
+ if (lines[i].trim() === '') {
1967
+ trailingBlankLines++;
1968
+ } else {
1969
+ break;
1970
+ }
1971
+ }
1972
+ if (trailingBlankLines > 0) {
1973
+ // Add a line with just a space, followed by single newline
1974
+ // The \n\n will be added below for paragraph separation
1975
+ content = content + '\n ';
1976
+ // Only add one newline since we're preserving the space line
1977
+ return content + '\n';
1978
+ }
1979
+ }
1980
+
1981
+ return content + '\n\n';
1982
+ }
1983
+ return '';
1984
+
1985
+ case 'div':
1986
+ // Check if this was created by a fence plugin with reverse handler
1987
+ const divLang = node.getAttribute('data-qd-lang');
1988
+ const divFence = node.getAttribute('data-qd-fence');
1989
+
1990
+ if (divLang && options.fence_plugin && options.fence_plugin.reverse) {
1991
+ try {
1992
+ const result = options.fence_plugin.reverse(node);
1993
+ if (result && result.content) {
1994
+ const fenceMarker = result.fence || divFence || '```';
1995
+ const langStr = result.lang || divLang;
1996
+ return `${fenceMarker}${langStr}\n${result.content}\n${fenceMarker}\n\n`;
1997
+ }
1998
+ } catch (err) {
1999
+ console.warn('Fence reverse handler error:', err);
2000
+ // Fall through to default handling
2001
+ }
2002
+ }
2003
+
2004
+ // Fallback: use data-qd-source if available
2005
+ const divSource = node.getAttribute('data-qd-source');
2006
+ if (divSource && divFence) {
2007
+ return `${divFence}${divLang || ''}\n${divSource}\n${divFence}\n\n`;
2008
+ }
2009
+
2010
+ // Check if it's a mermaid container
2011
+ if (node.classList && node.classList.contains('mermaid-container')) {
2012
+ const fence = node.getAttribute('data-qd-fence') || '```';
2013
+ const lang = node.getAttribute('data-qd-lang') || 'mermaid';
2014
+
2015
+ // First check for data-qd-source attribute on the container
2016
+ const source = node.getAttribute('data-qd-source');
2017
+ if (source) {
2018
+ // Decode HTML entities from the attribute (mainly &quot;)
2019
+ const temp = document.createElement('textarea');
2020
+ temp.innerHTML = source;
2021
+ const code = temp.value;
2022
+ return `${fence}${lang}\n${code}\n${fence}\n\n`;
2023
+ }
2024
+
2025
+ // Check for source on the pre.mermaid element
2026
+ const mermaidPre = node.querySelector('pre.mermaid');
2027
+ if (mermaidPre) {
2028
+ const preSource = mermaidPre.getAttribute('data-qd-source');
2029
+ if (preSource) {
2030
+ const temp = document.createElement('textarea');
2031
+ temp.innerHTML = preSource;
2032
+ const code = temp.value;
2033
+ return `${fence}${lang}\n${code}\n${fence}\n\n`;
2034
+ }
2035
+ }
2036
+
2037
+ // Fallback: Look for the legacy .mermaid-source element
2038
+ const sourceElement = node.querySelector('.mermaid-source');
2039
+ if (sourceElement) {
2040
+ // Decode HTML entities
2041
+ const temp = document.createElement('div');
2042
+ temp.innerHTML = sourceElement.innerHTML;
2043
+ const code = temp.textContent;
2044
+ return `${fence}${lang}\n${code}\n${fence}\n\n`;
2045
+ }
2046
+
2047
+ // Final fallback: try to extract from the mermaid element (unreliable after rendering)
2048
+ const mermaidElement = node.querySelector('.mermaid');
2049
+ if (mermaidElement && mermaidElement.textContent.includes('graph')) {
2050
+ return `${fence}${lang}\n${mermaidElement.textContent.trim()}\n${fence}\n\n`;
2051
+ }
2052
+ }
2053
+ // Check if it's a standalone mermaid diagram (legacy)
2054
+ if (node.classList && node.classList.contains('mermaid')) {
2055
+ const fence = node.getAttribute('data-qd-fence') || '```';
2056
+ const lang = node.getAttribute('data-qd-lang') || 'mermaid';
2057
+ const code = node.textContent.trim();
2058
+ return `${fence}${lang}\n${code}\n${fence}\n\n`;
2059
+ }
2060
+ // Pass through other divs
2061
+ return childContent;
2062
+
2063
+ case 'span':
2064
+ // Pass through container elements
2065
+ return childContent;
2066
+
2067
+ default:
2068
+ return childContent;
2069
+ }
2070
+ }
2071
+
2072
+ // Walk list elements
2073
+ function walkList(listNode, isOrdered, depth = 0) {
2074
+ let result = '';
2075
+ let index = 1;
2076
+ const indent = ' '.repeat(depth);
2077
+
2078
+ for (let child of listNode.children) {
2079
+ if (child.tagName !== 'LI') continue;
2080
+
2081
+ const dataQd = child.getAttribute('data-qd');
2082
+ let marker = dataQd || (isOrdered ? `${index}.` : '-');
2083
+
2084
+ // Check for task list checkbox
2085
+ const checkbox = child.querySelector('input[type="checkbox"]');
2086
+ if (checkbox) {
2087
+ const checked = checkbox.checked ? 'x' : ' ';
2088
+ marker = '-';
2089
+ // Get text without the checkbox
2090
+ let text = '';
2091
+ for (let node of child.childNodes) {
2092
+ if (node.nodeType === Node.TEXT_NODE) {
2093
+ text += node.textContent;
2094
+ } else if (node.tagName && node.tagName !== 'INPUT') {
2095
+ text += walkNode(node);
2096
+ }
2097
+ }
2098
+ result += `${indent}${marker} [${checked}] ${text.trim()}\n`;
2099
+ } else {
2100
+ let itemContent = '';
2101
+
2102
+ for (let node of child.childNodes) {
2103
+ if (node.tagName === 'UL' || node.tagName === 'OL') {
2104
+ itemContent += walkList(node, node.tagName === 'OL', depth + 1);
2105
+ } else {
2106
+ itemContent += walkNode(node);
2107
+ }
2108
+ }
2109
+
2110
+ result += `${indent}${marker} ${itemContent.trim()}\n`;
2111
+ }
2112
+
2113
+ index++;
2114
+ }
2115
+
2116
+ return result;
2117
+ }
2118
+
2119
+ // Walk table elements
2120
+ function walkTable(table) {
2121
+ let result = '';
2122
+ const alignData = table.getAttribute('data-qd-align');
2123
+ const alignments = alignData ? alignData.split(',') : [];
2124
+
2125
+ // Process header
2126
+ const thead = table.querySelector('thead');
2127
+ if (thead) {
2128
+ const headerRow = thead.querySelector('tr');
2129
+ if (headerRow) {
2130
+ const headers = [];
2131
+ for (let th of headerRow.querySelectorAll('th')) {
2132
+ headers.push(th.textContent.trim());
2133
+ }
2134
+ result += '| ' + headers.join(' | ') + ' |\n';
2135
+
2136
+ // Add separator with alignment
2137
+ const separators = headers.map((_, i) => {
2138
+ const align = alignments[i] || 'left';
2139
+ if (align === 'center') return ':---:';
2140
+ if (align === 'right') return '---:';
2141
+ return '---';
2142
+ });
2143
+ result += '| ' + separators.join(' | ') + ' |\n';
2144
+ }
2145
+ }
2146
+
2147
+ // Process body
2148
+ const tbody = table.querySelector('tbody');
2149
+ if (tbody) {
2150
+ for (let row of tbody.querySelectorAll('tr')) {
2151
+ const cells = [];
2152
+ for (let td of row.querySelectorAll('td')) {
2153
+ cells.push(td.textContent.trim());
2154
+ }
2155
+ if (cells.length > 0) {
2156
+ result += '| ' + cells.join(' | ') + ' |\n';
2157
+ }
2158
+ }
2159
+ }
2160
+
2161
+ return result.trim();
2162
+ }
2163
+
2164
+ // Process the DOM tree
2165
+ let markdown = walkNode(container);
2166
+
2167
+ // Clean up
2168
+ markdown = markdown.replace(/\n{3,}/g, '\n\n'); // Remove excessive newlines
2169
+ markdown = markdown.trim();
2170
+
2171
+ return markdown;
2172
+ };
2173
+
2174
+ // Override the configure method to return a bidirectional version
2175
+ quikdown_bd.configure = function(options) {
2176
+ return function(markdown) {
2177
+ return quikdown_bd(markdown, options);
2178
+ };
2179
+ };
2180
+
2181
+ // Set version
2182
+ // Version is already copied from quikdown via Object.keys loop
2183
+
2184
+ // Export for both module and browser
2185
+ /* istanbul ignore next */
2186
+ if (typeof module !== 'undefined' && module.exports) {
2187
+ module.exports = quikdown_bd;
2188
+ }
2189
+
2190
+ /* istanbul ignore next */
2191
+ if (typeof window !== 'undefined') {
2192
+ window.quikdown_bd = quikdown_bd;
2193
+ }
2194
+
2195
+ // CDN URLs for dynamic loading (same as quikdown editor uses)
2196
+ var CDN = {
2197
+ hljs: 'https://unpkg.com/@highlightjs/cdn-assets/highlight.min.js',
2198
+ hljsCSS: 'https://unpkg.com/@highlightjs/cdn-assets/styles/github.min.css',
2199
+ mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js',
2200
+ mermaid: 'https://unpkg.com/mermaid/dist/mermaid.min.js',
2201
+ leaflet: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
2202
+ leafletCSS: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
2203
+ dompurify: 'https://unpkg.com/dompurify/dist/purify.min.js'
2204
+ };
2205
+
2206
+ // Load a script from CDN (cached — only loads once)
2207
+ var _loaded = {};
2208
+ function loadScript(src) {
2209
+ if (_loaded[src]) return _loaded[src];
2210
+ _loaded[src] = new Promise(function (resolve, reject) {
2211
+ var s = document.createElement('script');
2212
+ s.src = src;
2213
+ s.onload = function () {
2214
+ return resolve(true);
2215
+ };
2216
+ s.onerror = function () {
2217
+ _loaded[src] = null;
2218
+ reject(new Error('Failed to load: ' + src));
2219
+ };
2220
+ document.head.appendChild(s);
2221
+ });
2222
+ return _loaded[src];
2223
+ }
2224
+ function loadCSS(href) {
2225
+ if (_loaded[href]) return _loaded[href];
2226
+ _loaded[href] = new Promise(function (resolve) {
2227
+ var link = document.createElement('link');
2228
+ link.rel = 'stylesheet';
2229
+ link.href = href;
2230
+ link.onload = function () {
2231
+ return resolve(true);
2232
+ };
2233
+ document.head.appendChild(link);
2234
+ setTimeout(resolve, 1000); // CSS doesn't always fire onload
2235
+ });
2236
+ return _loaded[href];
2237
+ }
2238
+
2239
+ /**
2240
+ * Process fence blocks rendered by quikdown/bd.
2241
+ * Detects code blocks with language tags and renders them with the appropriate library.
2242
+ * Libraries are loaded from CDN on first use.
2243
+ */
2244
+ function postProcessMessage(_x) {
2245
+ return _postProcessMessage.apply(this, arguments);
2246
+ }
2247
+ /**
2248
+ * quikchat-md-full: batteries-included build.
2249
+ * Uses quikdown/bd for markdown parsing, then post-processes fence blocks
2250
+ * with dynamically loaded renderers (highlight.js, MathJax, Mermaid, Leaflet, etc.).
2251
+ */
2252
+ function _postProcessMessage() {
2253
+ _postProcessMessage = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(el) {
2254
+ var codeBlocks, langSet, specialLangs, needsHljs, svgBlocks, htmlBlocks, _iterator, _step, _block2, _pre2, src, _container2, mathBlocks, hasInlineMath, mermaidBlocks, _iterator2, _step2, block, pre, container, mermaidId, _yield$window$mermaid, svg, geoBlocks, _iterator3, _step3, _block, _pre, geojson, _container, map, layer, tableBlocks, jsonBlocks, _t3, _t8, _t0;
2255
+ return _regenerator().w(function (_context) {
2256
+ while (1) switch (_context.p = _context.n) {
2257
+ case 0:
2258
+ if (el) {
2259
+ _context.n = 1;
2260
+ break;
2261
+ }
2262
+ return _context.a(2);
2263
+ case 1:
2264
+ // --- Syntax highlighting (highlight.js) ---
2265
+ codeBlocks = el.querySelectorAll('pre.quikdown-pre code[class*="language-"]');
2266
+ if (!(codeBlocks.length > 0)) {
2267
+ _context.n = 6;
2268
+ break;
2269
+ }
2270
+ langSet = new Set();
2271
+ codeBlocks.forEach(function (cb) {
2272
+ var match = cb.className.match(/language-(\S+)/);
2273
+ if (match) langSet.add(match[1]);
2274
+ });
2275
+
2276
+ // Skip languages handled by other renderers
2277
+ specialLangs = new Set(['svg', 'html', 'math', 'tex', 'latex', 'katex', 'mermaid', 'geojson', 'csv', 'tsv', 'psv', 'json', 'json5', 'stl']);
2278
+ needsHljs = _toConsumableArray(langSet).some(function (l) {
2279
+ return !specialLangs.has(l);
2280
+ });
2281
+ if (!(needsHljs && !window.hljs)) {
2282
+ _context.n = 5;
2283
+ break;
2284
+ }
2285
+ _context.p = 2;
2286
+ _context.n = 3;
2287
+ return Promise.all([loadScript(CDN.hljs), loadCSS(CDN.hljsCSS)]);
2288
+ case 3:
2289
+ _context.n = 5;
2290
+ break;
2291
+ case 4:
2292
+ _context.p = 4;
2293
+ _context.v;
2294
+ case 5:
2295
+ if (window.hljs) {
2296
+ codeBlocks.forEach(function (cb) {
2297
+ var match = cb.className.match(/language-(\S+)/);
2298
+ if (match && !specialLangs.has(match[1]) && !cb.classList.contains('hljs')) {
2299
+ window.hljs.highlightElement(cb);
2300
+ }
2301
+ });
2302
+ }
2303
+ case 6:
2304
+ // --- SVG (inline, no external lib) ---
2305
+ svgBlocks = el.querySelectorAll('pre.quikdown-pre[data-qd-lang="svg"], pre.quikdown-pre code.language-svg');
2306
+ svgBlocks.forEach(function (block) {
2307
+ var pre = block.closest('pre') || block;
2308
+ var code = pre.querySelector('code') || pre;
2309
+ var svgSource = code.textContent;
2310
+ try {
2311
+ var parser = new DOMParser();
2312
+ var doc = parser.parseFromString(svgSource, 'image/svg+xml');
2313
+ if (!doc.querySelector('parsererror')) {
2314
+ var svg = doc.documentElement;
2315
+ // Sanitize: remove scripts and event handlers
2316
+ svg.querySelectorAll('script').forEach(function (s) {
2317
+ return s.remove();
2318
+ });
2319
+ var walker = document.createTreeWalker(svg, NodeFilter.SHOW_ELEMENT);
2320
+ var node = walker.nextNode();
2321
+ while (node) {
2322
+ for (var i = node.attributes.length - 1; i >= 0; i--) {
2323
+ var attr = node.attributes[i];
2324
+ if (attr.name.startsWith('on') || attr.value.includes('javascript:')) {
2325
+ node.removeAttribute(attr.name);
2326
+ }
2327
+ }
2328
+ node = walker.nextNode();
2329
+ }
2330
+ var container = document.createElement('div');
2331
+ container.className = 'qde-svg-container';
2332
+ container.innerHTML = new XMLSerializer().serializeToString(svg);
2333
+ pre.replaceWith(container);
2334
+ }
2335
+ } catch (_e) {/* leave as code block */}
2336
+ });
2337
+
2338
+ // --- HTML (inline, sanitized with DOMPurify) ---
2339
+ htmlBlocks = el.querySelectorAll('pre.quikdown-pre code.language-html');
2340
+ _iterator = _createForOfIteratorHelper(htmlBlocks);
2341
+ _context.p = 7;
2342
+ _iterator.s();
2343
+ case 8:
2344
+ if ((_step = _iterator.n()).done) {
2345
+ _context.n = 16;
2346
+ break;
2347
+ }
2348
+ _block2 = _step.value;
2349
+ _pre2 = _block2.closest('pre');
2350
+ if (_pre2) {
2351
+ _context.n = 9;
2352
+ break;
2353
+ }
2354
+ return _context.a(3, 15);
2355
+ case 9:
2356
+ // Only render if the code is likely meant as rendered HTML (short snippets)
2357
+ // Skip if it looks like documentation (contains doctype, html, head tags)
2358
+ src = _block2.textContent;
2359
+ if (!src.match(/<(!DOCTYPE|html|head|body)/i)) {
2360
+ _context.n = 10;
2361
+ break;
2362
+ }
2363
+ return _context.a(3, 15);
2364
+ case 10:
2365
+ if (window.DOMPurify) {
2366
+ _context.n = 14;
2367
+ break;
2368
+ }
2369
+ _context.p = 11;
2370
+ _context.n = 12;
2371
+ return loadScript(CDN.dompurify);
2372
+ case 12:
2373
+ _context.n = 14;
2374
+ break;
2375
+ case 13:
2376
+ _context.p = 13;
2377
+ _context.v;
2378
+ return _context.a(3, 15);
2379
+ case 14:
2380
+ if (window.DOMPurify) {
2381
+ _container2 = document.createElement('div');
2382
+ _container2.className = 'qde-html-container';
2383
+ _container2.innerHTML = window.DOMPurify.sanitize(src);
2384
+ _pre2.replaceWith(_container2);
2385
+ }
2386
+ case 15:
2387
+ _context.n = 8;
2388
+ break;
2389
+ case 16:
2390
+ _context.n = 18;
2391
+ break;
2392
+ case 17:
2393
+ _context.p = 17;
2394
+ _t3 = _context.v;
2395
+ _iterator.e(_t3);
2396
+ case 18:
2397
+ _context.p = 18;
2398
+ _iterator.f();
2399
+ return _context.f(18);
2400
+ case 19:
2401
+ // --- Math (MathJax) ---
2402
+ // Look for math fences and inline math ($...$, $$...$$)
2403
+ mathBlocks = el.querySelectorAll('pre.quikdown-pre code.language-math, pre.quikdown-pre code.language-tex, pre.quikdown-pre code.language-latex, pre.quikdown-pre code.language-katex');
2404
+ hasInlineMath = el.innerHTML.match(/\$\$[\s\S]+?\$\$|\$[^$\n]+?\$/);
2405
+ if (!(mathBlocks.length > 0 || hasInlineMath)) {
2406
+ _context.n = 28;
2407
+ break;
2408
+ }
2409
+ if (!(!window.MathJax || !window.MathJax.typesetPromise)) {
2410
+ _context.n = 24;
2411
+ break;
2412
+ }
2413
+ if (window.mathJaxLoading) {
2414
+ _context.n = 24;
2415
+ break;
2416
+ }
2417
+ window.mathJaxLoading = true;
2418
+ window.MathJax = {
2419
+ loader: {
2420
+ load: ['input/tex', 'output/svg']
2421
+ },
2422
+ tex: {
2423
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
2424
+ displayMath: [['$$', '$$'], ['\\[', '\\]']],
2425
+ processEscapes: true,
2426
+ processEnvironments: true
2427
+ },
2428
+ options: {
2429
+ renderActions: {
2430
+ addMenu: []
2431
+ }
2432
+ },
2433
+ svg: {
2434
+ fontCache: 'none'
2435
+ },
2436
+ startup: {
2437
+ typeset: false
2438
+ }
2439
+ };
2440
+ _context.p = 20;
2441
+ _context.n = 21;
2442
+ return loadScript(CDN.mathjax);
2443
+ case 21:
2444
+ _context.n = 23;
2445
+ break;
2446
+ case 22:
2447
+ _context.p = 22;
2448
+ _context.v;
2449
+ case 23:
2450
+ window.mathJaxLoading = false;
2451
+ case 24:
2452
+ // Convert math fence blocks to divs MathJax can process
2453
+ mathBlocks.forEach(function (block) {
2454
+ var pre = block.closest('pre');
2455
+ if (!pre) return;
2456
+ var mathDiv = document.createElement('div');
2457
+ mathDiv.className = 'math-display';
2458
+ mathDiv.textContent = '$$' + block.textContent + '$$';
2459
+ pre.replaceWith(mathDiv);
2460
+ });
2461
+ if (!(window.MathJax && window.MathJax.typesetPromise)) {
2462
+ _context.n = 28;
2463
+ break;
2464
+ }
2465
+ _context.p = 25;
2466
+ _context.n = 26;
2467
+ return window.MathJax.typesetPromise([el]);
2468
+ case 26:
2469
+ _context.n = 28;
2470
+ break;
2471
+ case 27:
2472
+ _context.p = 27;
2473
+ _context.v;
2474
+ case 28:
2475
+ // --- Mermaid diagrams ---
2476
+ mermaidBlocks = el.querySelectorAll('pre.quikdown-pre code.language-mermaid');
2477
+ if (!(mermaidBlocks.length > 0)) {
2478
+ _context.n = 43;
2479
+ break;
2480
+ }
2481
+ if (window.mermaid) {
2482
+ _context.n = 32;
2483
+ break;
2484
+ }
2485
+ _context.p = 29;
2486
+ _context.n = 30;
2487
+ return loadScript(CDN.mermaid);
2488
+ case 30:
2489
+ if (window.mermaid) {
2490
+ window.mermaid.initialize({
2491
+ startOnLoad: false,
2492
+ theme: 'default'
2493
+ });
2494
+ }
2495
+ _context.n = 32;
2496
+ break;
2497
+ case 31:
2498
+ _context.p = 31;
2499
+ _context.v;
2500
+ case 32:
2501
+ if (!window.mermaid) {
2502
+ _context.n = 43;
2503
+ break;
2504
+ }
2505
+ _iterator2 = _createForOfIteratorHelper(mermaidBlocks);
2506
+ _context.p = 33;
2507
+ _iterator2.s();
2508
+ case 34:
2509
+ if ((_step2 = _iterator2.n()).done) {
2510
+ _context.n = 40;
2511
+ break;
2512
+ }
2513
+ block = _step2.value;
2514
+ pre = block.closest('pre');
2515
+ if (pre) {
2516
+ _context.n = 35;
2517
+ break;
2518
+ }
2519
+ return _context.a(3, 39);
2520
+ case 35:
2521
+ container = document.createElement('div');
2522
+ container.className = 'qde-mermaid-container';
2523
+ mermaidId = 'mermaid-' + Math.random().toString(36).substring(2, 9);
2524
+ _context.p = 36;
2525
+ _context.n = 37;
2526
+ return window.mermaid.render(mermaidId, block.textContent);
2527
+ case 37:
2528
+ _yield$window$mermaid = _context.v;
2529
+ svg = _yield$window$mermaid.svg;
2530
+ container.innerHTML = svg;
2531
+ pre.replaceWith(container);
2532
+ _context.n = 39;
2533
+ break;
2534
+ case 38:
2535
+ _context.p = 38;
2536
+ _context.v;
2537
+ case 39:
2538
+ _context.n = 34;
2539
+ break;
2540
+ case 40:
2541
+ _context.n = 42;
2542
+ break;
2543
+ case 41:
2544
+ _context.p = 41;
2545
+ _t8 = _context.v;
2546
+ _iterator2.e(_t8);
2547
+ case 42:
2548
+ _context.p = 42;
2549
+ _iterator2.f();
2550
+ return _context.f(42);
2551
+ case 43:
2552
+ // --- GeoJSON / Leaflet maps ---
2553
+ geoBlocks = el.querySelectorAll('pre.quikdown-pre code.language-geojson');
2554
+ if (!(geoBlocks.length > 0)) {
2555
+ _context.n = 55;
2556
+ break;
2557
+ }
2558
+ if (window.L) {
2559
+ _context.n = 47;
2560
+ break;
2561
+ }
2562
+ _context.p = 44;
2563
+ _context.n = 45;
2564
+ return Promise.all([loadScript(CDN.leaflet), loadCSS(CDN.leafletCSS)]);
2565
+ case 45:
2566
+ _context.n = 47;
2567
+ break;
2568
+ case 46:
2569
+ _context.p = 46;
2570
+ _context.v;
2571
+ case 47:
2572
+ if (!window.L) {
2573
+ _context.n = 55;
2574
+ break;
2575
+ }
2576
+ _iterator3 = _createForOfIteratorHelper(geoBlocks);
2577
+ _context.p = 48;
2578
+ _iterator3.s();
2579
+ case 49:
2580
+ if ((_step3 = _iterator3.n()).done) {
2581
+ _context.n = 52;
2582
+ break;
2583
+ }
2584
+ _block = _step3.value;
2585
+ _pre = _block.closest('pre');
2586
+ if (_pre) {
2587
+ _context.n = 50;
2588
+ break;
2589
+ }
2590
+ return _context.a(3, 51);
2591
+ case 50:
2592
+ try {
2593
+ geojson = JSON.parse(_block.textContent);
2594
+ _container = document.createElement('div');
2595
+ _container.className = 'qde-geojson-container';
2596
+ _container.style.cssText = 'height: 300px; width: 100%; border-radius: 4px;';
2597
+ _pre.replaceWith(_container);
2598
+ map = window.L.map(_container);
2599
+ window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
2600
+ attribution: '&copy; OpenStreetMap'
2601
+ }).addTo(map);
2602
+ layer = window.L.geoJSON(geojson).addTo(map);
2603
+ map.fitBounds(layer.getBounds());
2604
+ } catch (_e) {/* leave as code block */}
2605
+ case 51:
2606
+ _context.n = 49;
2607
+ break;
2608
+ case 52:
2609
+ _context.n = 54;
2610
+ break;
2611
+ case 53:
2612
+ _context.p = 53;
2613
+ _t0 = _context.v;
2614
+ _iterator3.e(_t0);
2615
+ case 54:
2616
+ _context.p = 54;
2617
+ _iterator3.f();
2618
+ return _context.f(54);
2619
+ case 55:
2620
+ // --- CSV / TSV / PSV tables ---
2621
+ tableBlocks = el.querySelectorAll('pre.quikdown-pre code.language-csv, pre.quikdown-pre code.language-tsv, pre.quikdown-pre code.language-psv');
2622
+ tableBlocks.forEach(function (block) {
2623
+ var pre = block.closest('pre');
2624
+ if (!pre) return;
2625
+ var lang = block.className.match(/language-(csv|tsv|psv)/);
2626
+ if (!lang) return;
2627
+ var delim = {
2628
+ csv: ',',
2629
+ tsv: '\t',
2630
+ psv: '|'
2631
+ }[lang[1]];
2632
+ var lines = block.textContent.trim().split('\n');
2633
+ if (lines.length < 2) return;
2634
+ try {
2635
+ var table = document.createElement('table');
2636
+ table.className = 'quikdown-table';
2637
+ lines.forEach(function (line, i) {
2638
+ var row = document.createElement('tr');
2639
+ var cells = line.split(delim).map(function (c) {
2640
+ return c.trim();
2641
+ });
2642
+ cells.forEach(function (cell) {
2643
+ var td = document.createElement(i === 0 ? 'th' : 'td');
2644
+ td.className = i === 0 ? 'quikdown-th' : 'quikdown-td';
2645
+ td.textContent = cell;
2646
+ row.appendChild(td);
2647
+ });
2648
+ table.appendChild(row);
2649
+ });
2650
+ pre.replaceWith(table);
2651
+ } catch (_e) {/* leave as code block */}
2652
+ });
2653
+
2654
+ // --- JSON pretty-print ---
2655
+ jsonBlocks = el.querySelectorAll('pre.quikdown-pre code.language-json, pre.quikdown-pre code.language-json5');
2656
+ jsonBlocks.forEach(function (block) {
2657
+ try {
2658
+ var obj = JSON.parse(block.textContent);
2659
+ block.textContent = JSON.stringify(obj, null, 2);
2660
+ // If hljs is loaded, re-highlight
2661
+ if (window.hljs && window.hljs.getLanguage('json')) {
2662
+ window.hljs.highlightElement(block);
2663
+ }
2664
+ } catch (_e) {/* not valid JSON, leave as is */}
2665
+ });
2666
+ case 56:
2667
+ return _context.a(2);
2668
+ }
2669
+ }, _callee, null, [[48, 53, 54, 55], [44, 46], [36, 38], [33, 41, 42, 43], [29, 31], [25, 27], [20, 22], [11, 13], [7, 17, 18, 19], [2, 4]]);
2670
+ }));
2671
+ return _postProcessMessage.apply(this, arguments);
2672
+ }
2673
+ var quikchatMDFull = /*#__PURE__*/function (_quikchat) {
2674
+ function quikchatMDFull(parentElement, onSend) {
2675
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
2676
+ _classCallCheck(this, quikchatMDFull);
2677
+ if (!options.messageFormatter) {
2678
+ options.messageFormatter = function (content) {
2679
+ return quikdown_bd(content);
2680
+ };
2681
+ }
2682
+ return _callSuper(this, quikchatMDFull, [parentElement, onSend, options]);
2683
+ }
2684
+
2685
+ // Override message methods to add post-processing
2686
+ _inherits(quikchatMDFull, _quikchat);
2687
+ return _createClass(quikchatMDFull, [{
2688
+ key: "messageAddNew",
2689
+ value: function messageAddNew() {
2690
+ var content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
2691
+ var userString = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "user";
2692
+ var align = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "right";
2693
+ var role = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "user";
2694
+ var id = _superPropGet(quikchatMDFull, "messageAddNew", this, 3)([content, userString, align, role]);
2695
+ var el = this.messageGetDOMObject(id);
2696
+ if (el) {
2697
+ postProcessMessage(el.querySelector('.quikchat-message-content'));
2698
+ }
2699
+ return id;
2700
+ }
2701
+ }, {
2702
+ key: "messageAddFull",
2703
+ value: function messageAddFull(input) {
2704
+ var id = _superPropGet(quikchatMDFull, "messageAddFull", this, 3)([input]);
2705
+ var el = this.messageGetDOMObject(id);
2706
+ if (el) {
2707
+ postProcessMessage(el.querySelector('.quikchat-message-content'));
2708
+ }
2709
+ return id;
2710
+ }
2711
+ }, {
2712
+ key: "messageAppendContent",
2713
+ value: function messageAppendContent(n, content) {
2714
+ var result = _superPropGet(quikchatMDFull, "messageAppendContent", this, 3)([n, content]);
2715
+ if (result) {
2716
+ var el = this.messageGetDOMObject(n);
2717
+ if (el) {
2718
+ postProcessMessage(el.querySelector('.quikchat-message-content'));
2719
+ }
2720
+ }
2721
+ return result;
2722
+ }
2723
+ }, {
2724
+ key: "messageReplaceContent",
2725
+ value: function messageReplaceContent(n, content) {
2726
+ var result = _superPropGet(quikchatMDFull, "messageReplaceContent", this, 3)([n, content]);
2727
+ if (result) {
2728
+ var el = this.messageGetDOMObject(n);
2729
+ if (el) {
2730
+ postProcessMessage(el.querySelector('.quikchat-message-content'));
2731
+ }
2732
+ }
2733
+ return result;
2734
+ }
2735
+ }, {
2736
+ key: "destroy",
2737
+ value: function destroy() {
2738
+ // No hidden container to clean up in this approach
2739
+ }
2740
+ }]);
2741
+ }(quikchat); // Expose quikdown bd and post-processor for direct access
2742
+ quikchatMDFull.quikdown = quikdown_bd;
2743
+ quikchatMDFull.postProcessMessage = postProcessMessage;
2744
+
2745
+ return quikchatMDFull;
2746
+
2747
+ }));
2748
+ //# sourceMappingURL=quikchat-md-full.umd.js.map