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