quikchat 1.2.6 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +7 -2
  2. package/dist/build-manifest.json +57 -57
  3. package/dist/quikchat-md-full.cjs.js +98 -5
  4. package/dist/quikchat-md-full.cjs.js.map +1 -1
  5. package/dist/quikchat-md-full.cjs.min.js +3 -3
  6. package/dist/quikchat-md-full.cjs.min.js.map +1 -1
  7. package/dist/quikchat-md-full.esm.js +98 -5
  8. package/dist/quikchat-md-full.esm.js.map +1 -1
  9. package/dist/quikchat-md-full.esm.min.js +3 -3
  10. package/dist/quikchat-md-full.esm.min.js.map +1 -1
  11. package/dist/quikchat-md-full.umd.js +98 -5
  12. package/dist/quikchat-md-full.umd.js.map +1 -1
  13. package/dist/quikchat-md-full.umd.min.js +3 -3
  14. package/dist/quikchat-md-full.umd.min.js.map +1 -1
  15. package/dist/quikchat-md.cjs.js +98 -5
  16. package/dist/quikchat-md.cjs.js.map +1 -1
  17. package/dist/quikchat-md.cjs.min.js +3 -3
  18. package/dist/quikchat-md.cjs.min.js.map +1 -1
  19. package/dist/quikchat-md.esm.js +98 -5
  20. package/dist/quikchat-md.esm.js.map +1 -1
  21. package/dist/quikchat-md.esm.min.js +3 -3
  22. package/dist/quikchat-md.esm.min.js.map +1 -1
  23. package/dist/quikchat-md.umd.js +98 -5
  24. package/dist/quikchat-md.umd.js.map +1 -1
  25. package/dist/quikchat-md.umd.min.js +3 -3
  26. package/dist/quikchat-md.umd.min.js.map +1 -1
  27. package/dist/quikchat.cjs.js +2 -2
  28. package/dist/quikchat.cjs.js.map +1 -1
  29. package/dist/quikchat.cjs.min.js +1 -1
  30. package/dist/quikchat.cjs.min.js.map +1 -1
  31. package/dist/quikchat.esm.js +2 -2
  32. package/dist/quikchat.esm.js.map +1 -1
  33. package/dist/quikchat.esm.min.js +1 -1
  34. package/dist/quikchat.esm.min.js.map +1 -1
  35. package/dist/quikchat.umd.js +2 -2
  36. package/dist/quikchat.umd.js.map +1 -1
  37. package/dist/quikchat.umd.min.js +1 -1
  38. package/dist/quikchat.umd.min.js.map +1 -1
  39. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,11 @@
4
4
 
5
5
  # QuikChat
6
6
 
7
- A lightweight, zero-dependency vanilla JavaScript chat widget. Drop it into any pageno React, no Vue, no build step required and connect it to any LLM, WebSocket, or message source with plain `fetch()`.
7
+ A lightweight, zero-dependency JavaScript chat widget, with history save/restore, multiple instance support, and several other goodies. Works with any framework — React, Vue, Svelte, Angular, Solidor none at all. Connect it to any LLM, WebSocket, or message source with plain `fetch()`.
8
+
9
+ [Live Demo & Documentation](https://deftio.github.io/quikchat/site/)
10
+
11
+ [![QuikChat screenshot](site/quikchat-screenshot.png)](https://deftio.github.io/quikchat/site/)
8
12
 
9
13
  ```html
10
14
  <script src="https://unpkg.com/quikchat"></script>
@@ -191,12 +195,13 @@ Style messages by role with CSS hooks: `.quikchat-role-user`, `.quikchat-role-as
191
195
  | [LLM Integration](docs/llm-integration.md) | Ollama, OpenAI, LM Studio, tool calls, conversational memory |
192
196
  | [Theming](docs/theming.md) | Custom themes, CSS architecture, built-in themes |
193
197
  | [CSS Architecture](docs/css-architecture.md) | Base vs theme separation, ARIA accessibility |
198
+ | [Recipes](docs/recipes.md) | Common patterns: log viewer, tool-call visibility, session persistence, RTL |
194
199
 
195
200
  ## Demo & Examples
196
201
 
197
202
  [Live Demo](https://deftio.github.io/quikchat/site/) | [Examples](https://deftio.github.io/quikchat/examples/)
198
203
 
199
- Examples include: basic UMD/ESM usage, theme switching, dual chatrooms, markdown rendering ([basic](./examples/example_markdown.html) and [full with syntax highlighting + math + diagrams](./examples/example_md_full.html)), streaming with Ollama/OpenAI/LM Studio, and React integration.
204
+ Examples include: basic UMD/ESM usage, dual chatrooms, markdown rendering ([basic](./examples/example_markdown.html) and [full with syntax highlighting + math + diagrams](./examples/example_md_full.html)), LLM integrations (Ollama, OpenAI, LM Studio), [LLM tool-calling editor](./examples/example_tool_editor.html), tool-call visibility, session save/restore, RTL/i18n, log viewer, event timeline, framework integration (React, Vue, Solid, Svelte, Angular), and backend examples ([FastAPI](./examples/fastapi_llm/), [Express](./examples/npm_express/)).
200
205
 
201
206
  ## Build Variants
202
207
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.2.6",
3
- "generated": "2026-04-20T04:05:17.743Z",
2
+ "version": "1.2.7",
3
+ "generated": "2026-05-04T04:21:07.564Z",
4
4
  "builds": [
5
5
  {
6
6
  "id": "base",
@@ -12,37 +12,37 @@
12
12
  "format": "UMD",
13
13
  "raw": "quikchat.umd.js",
14
14
  "min": "quikchat.umd.min.js",
15
- "rawBytes": 33115,
16
- "minBytes": 17417,
17
- "gzipBytes": 4846,
15
+ "rawBytes": 33119,
16
+ "minBytes": 17421,
17
+ "gzipBytes": 4849,
18
18
  "rawSize": "32.34 KB",
19
19
  "minSize": "17.01 KB",
20
- "gzipSize": "4.73 KB",
21
- "sri": "sha384-NamQg4QPdOymZnlXQwkHrSrNMn014Ko+xOK1V/Q2/fKkqUgGd03BV5hIwP/sbv8f"
20
+ "gzipSize": "4.74 KB",
21
+ "sri": "sha384-HLvGJigsP5xa9TFN6KOihPuozxJlq36dC16VsNMBpVGiwLs75d+2/LEmqzJX58y8"
22
22
  },
23
23
  {
24
24
  "format": "CJS",
25
25
  "raw": "quikchat.cjs.js",
26
26
  "min": "quikchat.cjs.min.js",
27
- "rawBytes": 31003,
28
- "minBytes": 17202,
29
- "gzipBytes": 4778,
27
+ "rawBytes": 31007,
28
+ "minBytes": 17206,
29
+ "gzipBytes": 4781,
30
30
  "rawSize": "30.28 KB",
31
31
  "minSize": "16.80 KB",
32
32
  "gzipSize": "4.67 KB",
33
- "sri": "sha384-OrMbUwcaTwt6JOnMJt1nt5wKy4kzsGvKMh3Vj5eucEYuIXNTGTfy5E0fLvVGn+Ps"
33
+ "sri": "sha384-nHUMygH/rx+lxSW7F1l0fLp3FPfN/YdNiJj/qMkcqvIDHFuuegIubKpuiHCQqN3g"
34
34
  },
35
35
  {
36
36
  "format": "ESM",
37
37
  "raw": "quikchat.esm.js",
38
38
  "min": "quikchat.esm.min.js",
39
- "rawBytes": 30993,
40
- "minBytes": 17193,
41
- "gzipBytes": 4773,
39
+ "rawBytes": 30997,
40
+ "minBytes": 17197,
41
+ "gzipBytes": 4776,
42
42
  "rawSize": "30.27 KB",
43
43
  "minSize": "16.79 KB",
44
44
  "gzipSize": "4.66 KB",
45
- "sri": "sha384-K7KBg+8DlqPyiWBAjYIX29oIHyAAB2T4F8LZzuQrVNJM1pcwN/pbsbpixqtn1wdk"
45
+ "sri": "sha384-itfed90vasC8MaRc5SqS992oym6NQnXmvbX3q4F0fvGH96ZBwRLSD2PV+AgG7gEW"
46
46
  },
47
47
  {
48
48
  "format": "CSS",
@@ -68,37 +68,37 @@
68
68
  "format": "UMD",
69
69
  "raw": "quikchat-md.umd.js",
70
70
  "min": "quikchat-md.umd.min.js",
71
- "rawBytes": 80811,
72
- "minBytes": 28568,
73
- "gzipBytes": 8893,
74
- "rawSize": "78.92 KB",
75
- "minSize": "27.90 KB",
76
- "gzipSize": "8.68 KB",
77
- "sri": "sha384-VXGanV4Cb7faAB8zU43gnMHchw5Rq3cILG0ZclhR9Lm2TK5wgiyyElpiiWH6HSB5"
71
+ "rawBytes": 85353,
72
+ "minBytes": 29563,
73
+ "gzipBytes": 9303,
74
+ "rawSize": "83.35 KB",
75
+ "minSize": "28.87 KB",
76
+ "gzipSize": "9.08 KB",
77
+ "sri": "sha384-NyEFBtozHVJl7dPUvuCpBlwEGC34Qc0yosXjeVckvXdWf11s3szHrmUqVB1p5ELQ"
78
78
  },
79
79
  {
80
80
  "format": "CJS",
81
81
  "raw": "quikchat-md.cjs.js",
82
82
  "min": "quikchat-md.cjs.min.js",
83
- "rawBytes": 76919,
84
- "minBytes": 28341,
85
- "gzipBytes": 8823,
86
- "rawSize": "75.12 KB",
87
- "minSize": "27.68 KB",
88
- "gzipSize": "8.62 KB",
89
- "sri": "sha384-dxRHv50bbpwwAPKSDotvgeP7Qu6sImUGordj7IpEfxBhrtMW2/RxwSybatN5RNe0"
83
+ "rawBytes": 81293,
84
+ "minBytes": 29336,
85
+ "gzipBytes": 9233,
86
+ "rawSize": "79.39 KB",
87
+ "minSize": "28.65 KB",
88
+ "gzipSize": "9.02 KB",
89
+ "sri": "sha384-4inUk0zLpAEt509RM9wrO6pcfu6/3GLBJRWPW/3TBcyq62FRGRi6oh9KT1FEM4O/"
90
90
  },
91
91
  {
92
92
  "format": "ESM",
93
93
  "raw": "quikchat-md.esm.js",
94
94
  "min": "quikchat-md.esm.min.js",
95
- "rawBytes": 76909,
96
- "minBytes": 28332,
97
- "gzipBytes": 8825,
98
- "rawSize": "75.11 KB",
99
- "minSize": "27.67 KB",
100
- "gzipSize": "8.62 KB",
101
- "sri": "sha384-s8ZB9Irx2RS2UNJRYg9l2t0P8cM2BujXLQwb7DDyRW6LjtdhdAxsKu4Fs4utKf4x"
95
+ "rawBytes": 81283,
96
+ "minBytes": 29327,
97
+ "gzipBytes": 9234,
98
+ "rawSize": "79.38 KB",
99
+ "minSize": "28.64 KB",
100
+ "gzipSize": "9.02 KB",
101
+ "sri": "sha384-NU2rka4yCQspxc8DELK8bjof9XXD6DAglIDaPFVpi2NEENNbhPcGoPK5BNvVdivN"
102
102
  }
103
103
  ]
104
104
  },
@@ -112,37 +112,37 @@
112
112
  "format": "UMD",
113
113
  "raw": "quikchat-md-full.umd.js",
114
114
  "min": "quikchat-md-full.umd.min.js",
115
- "rawBytes": 125241,
116
- "minBytes": 45132,
117
- "gzipBytes": 14280,
118
- "rawSize": "122.31 KB",
119
- "minSize": "44.07 KB",
120
- "gzipSize": "13.95 KB",
121
- "sri": "sha384-2dRmdOJzLKWhFxyzFbctBDovq1kZgaPBnMj1g8O6An/mLqi3UrITvGRD89Cpaonb"
115
+ "rawBytes": 129783,
116
+ "minBytes": 46127,
117
+ "gzipBytes": 14672,
118
+ "rawSize": "126.74 KB",
119
+ "minSize": "45.05 KB",
120
+ "gzipSize": "14.33 KB",
121
+ "sri": "sha384-Lk+2rwvAXBi6oQHtO2UFzs8uD6lIsAVwZV310qBWpiGIaf2F1Obe/RKnMVn3LGbm"
122
122
  },
123
123
  {
124
124
  "format": "CJS",
125
125
  "raw": "quikchat-md-full.cjs.js",
126
126
  "min": "quikchat-md-full.cjs.min.js",
127
- "rawBytes": 119165,
128
- "minBytes": 44905,
129
- "gzipBytes": 14214,
130
- "rawSize": "116.37 KB",
131
- "minSize": "43.85 KB",
132
- "gzipSize": "13.88 KB",
133
- "sri": "sha384-D+XD9IV6jDdcO/WlNVabQ5rlpPgmkNcQSnZV4o86/8SieJ2LfDF/sBrsFVRJJh5j"
127
+ "rawBytes": 123539,
128
+ "minBytes": 45900,
129
+ "gzipBytes": 14604,
130
+ "rawSize": "120.64 KB",
131
+ "minSize": "44.82 KB",
132
+ "gzipSize": "14.26 KB",
133
+ "sri": "sha384-Z+2mOTSAZf3Dg92C2ltm1MgXLbyqkcy8WuXOBWbrOp4eRLXhvXAHM4683byCzCCY"
134
134
  },
135
135
  {
136
136
  "format": "ESM",
137
137
  "raw": "quikchat-md-full.esm.js",
138
138
  "min": "quikchat-md-full.esm.min.js",
139
- "rawBytes": 119155,
140
- "minBytes": 44896,
141
- "gzipBytes": 14212,
142
- "rawSize": "116.36 KB",
143
- "minSize": "43.84 KB",
144
- "gzipSize": "13.88 KB",
145
- "sri": "sha384-xlOEkcgiZiWTurpVc5nV4cz2y0W4pQmY9gAOR9to1+vWtezxQ7UGnP1eyO1X7U9w"
139
+ "rawBytes": 123529,
140
+ "minBytes": 45891,
141
+ "gzipBytes": 14602,
142
+ "rawSize": "120.63 KB",
143
+ "minSize": "44.82 KB",
144
+ "gzipSize": "14.26 KB",
145
+ "sri": "sha384-eqUE3H5exC1kVl2LwErt1dlpdvzzh6vKLlgANUrmecfjEKwFvFrnis6AMUlB/RVa"
146
146
  }
147
147
  ]
148
148
  }
@@ -1064,9 +1064,9 @@ var quikchat = /*#__PURE__*/function () {
1064
1064
  key: "version",
1065
1065
  value: function version() {
1066
1066
  return {
1067
- "version": "1.2.6",
1067
+ "version": "1.2.7",
1068
1068
  "license": "BSD-2",
1069
- "url": "https://github/deftio/quikchat"
1069
+ "url": "https://github.com/deftio/quikchat"
1070
1070
  };
1071
1071
  }
1072
1072
 
@@ -1124,7 +1124,7 @@ var quikchat = /*#__PURE__*/function () {
1124
1124
 
1125
1125
  /**
1126
1126
  * quikdown_bd - Bidirectional Markdown Parser
1127
- * @version 1.2.10
1127
+ * @version 1.2.12
1128
1128
  * @license BSD-2-Clause
1129
1129
  * @copyright DeftIO 2025
1130
1130
  */
@@ -1236,7 +1236,7 @@ function isDashHRLine(trimmed) {
1236
1236
  // ────────────────────────────────────────────────────────────────────
1237
1237
 
1238
1238
  /** Build-time version stamp (injected by tools/updateVersion) */
1239
- const quikdownVersion = '1.2.10';
1239
+ const quikdownVersion = '1.2.12';
1240
1240
 
1241
1241
  /** CSS class prefix used for all generated elements */
1242
1242
  const CLASS_PREFIX = 'quikdown-';
@@ -1244,6 +1244,10 @@ const CLASS_PREFIX = 'quikdown-';
1244
1244
  /** Placeholder sigils — chosen to be extremely unlikely in real text */
1245
1245
  const PLACEHOLDER_CB = '§CB'; // fenced code blocks
1246
1246
  const PLACEHOLDER_IC = '§IC'; // inline code spans
1247
+ const PLACEHOLDER_HT = '§HT'; // safe HTML tags (limited mode)
1248
+
1249
+ /** Attributes whose values need URL sanitization */
1250
+ const URL_ATTRIBUTES = { href:1, src:1, action:1, formaction:1 };
1247
1251
 
1248
1252
  /** HTML entity escape map */
1249
1253
  const ESC_MAP = {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'};
@@ -1378,6 +1382,46 @@ function quikdown(markdown, options = {}) {
1378
1382
  return trimmedUrl;
1379
1383
  }
1380
1384
 
1385
+ /**
1386
+ * Sanitize attributes on an HTML tag string for limited mode.
1387
+ * Strips on* event handlers (case-insensitive) and runs sanitizeUrl()
1388
+ * on href/src/action/formaction values.
1389
+ */
1390
+ function sanitizeHtmlTagAttrs(tagStr) {
1391
+ // Self-closing or void tag without attributes — pass through
1392
+ if (!/\s/.test(tagStr.replace(/<\/?[a-zA-Z][a-zA-Z0-9]*/, '').replace(/\/?>$/, ''))) {
1393
+ return tagStr;
1394
+ }
1395
+ // Parse: <tagname ...attrs... > or <tagname ...attrs... />
1396
+ const m = tagStr.match(/^(<\/?[a-zA-Z][a-zA-Z0-9]*)([\s\S]*?)(\/?>)$/);
1397
+ /* istanbul ignore next - defensive: Phase 1.5 regex guarantees valid tag shape */
1398
+ if (!m) return tagStr;
1399
+
1400
+ const [, open, attrStr, close] = m;
1401
+ // Match individual attributes: name="value", name='value', name=value, or bare name
1402
+ // eslint-disable-next-line security/detect-unsafe-regex -- linear: no nested quantifiers
1403
+ const attrRe = /([a-zA-Z_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
1404
+ const attrs = [];
1405
+ let am;
1406
+ while ((am = attrRe.exec(attrStr)) !== null) {
1407
+ const name = am[1];
1408
+ const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : am[4];
1409
+ // Strip event handlers (on*)
1410
+ if (/^on/i.test(name)) continue;
1411
+ if (value === undefined) {
1412
+ // Boolean attribute (e.g. disabled, checked)
1413
+ attrs.push(name);
1414
+ } else {
1415
+ let sanitized = value;
1416
+ if (name.toLowerCase() in URL_ATTRIBUTES) {
1417
+ sanitized = sanitizeUrl(value);
1418
+ }
1419
+ attrs.push(`${name}="${sanitized}"`);
1420
+ }
1421
+ }
1422
+ return open + (attrs.length ? ' ' + attrs.join(' ') : '') + close;
1423
+ }
1424
+
1381
1425
  // ────────────────────────────────────────────────────────────────
1382
1426
  // Phase 1 — Code Extraction
1383
1427
  // ────────────────────────────────────────────────────────────────
@@ -1429,17 +1473,57 @@ function quikdown(markdown, options = {}) {
1429
1473
  return placeholder;
1430
1474
  });
1431
1475
 
1476
+ // ────────────────────────────────────────────────────────────────
1477
+ // Phase 1.5 — Safe HTML Extraction (whitelist mode)
1478
+ // ────────────────────────────────────────────────────────────────
1479
+ // When allow_unsafe_html is an object or array, extract whitelisted
1480
+ // HTML tags, sanitize their attributes, and replace with placeholders.
1481
+ // Non-whitelisted tags stay in text so Phase 2 will escape them.
1482
+
1483
+ const safeTags = [];
1484
+ // Normalize: array → object for O(1) lookup; object used as-is
1485
+ const htmlAllow = Array.isArray(allow_unsafe_html)
1486
+ ? Object.fromEntries(allow_unsafe_html.map(t => [t, 1]))
1487
+ : (allow_unsafe_html && typeof allow_unsafe_html === 'object') ? allow_unsafe_html : null;
1488
+
1489
+ if (htmlAllow) {
1490
+ // Pass through HTML comments — browsers render them as nothing
1491
+ html = html.replace(/<!--[\s\S]*?-->/g, (match) => {
1492
+ const idx = safeTags.length;
1493
+ safeTags.push(match);
1494
+ return `${PLACEHOLDER_HT}${idx}§`;
1495
+ });
1496
+ html = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g, (match, tagName) => {
1497
+ if (tagName.toLowerCase() in htmlAllow) {
1498
+ const sanitized = sanitizeHtmlTagAttrs(match);
1499
+ const idx = safeTags.length;
1500
+ safeTags.push(sanitized);
1501
+ return `${PLACEHOLDER_HT}${idx}§`;
1502
+ }
1503
+ // Not whitelisted — leave in text for Phase 2 to escape
1504
+ return match;
1505
+ });
1506
+ }
1507
+
1432
1508
  // ────────────────────────────────────────────────────────────────
1433
1509
  // Phase 2 — HTML Escaping
1434
1510
  // ────────────────────────────────────────────────────────────────
1435
1511
  // All remaining text (everything except code placeholders) is escaped
1436
1512
  // to prevent XSS. The `allow_unsafe_html` option skips this for
1437
1513
  // trusted pipelines that intentionally embed raw HTML.
1514
+ // For whitelist mode, escaping still runs (only `true` bypasses it).
1438
1515
 
1439
- if (!allow_unsafe_html) {
1516
+ if (allow_unsafe_html !== true) {
1440
1517
  html = escapeHtml(html);
1441
1518
  }
1442
1519
 
1520
+ // Restore safe HTML tag placeholders after escaping
1521
+ if (htmlAllow) {
1522
+ safeTags.forEach((tag, i) => {
1523
+ html = html.replace(`${PLACEHOLDER_HT}${i}§`, tag);
1524
+ });
1525
+ }
1526
+
1443
1527
  // ────────────────────────────────────────────────────────────────
1444
1528
  // Phase 3 — Block Scanning + Inline Formatting + Paragraphs
1445
1529
  // ────────────────────────────────────────────────────────────────
@@ -1681,6 +1765,14 @@ function scanLineBlocks(text, getAttr, dataQd) {
1681
1765
  while (i < lines.length) {
1682
1766
  const line = lines[i];
1683
1767
 
1768
+ // ── Markdown comment (reference-link hack) ──
1769
+ // [//]: # (comment) or [//]: # "comment" or [//]: #
1770
+ // These produce no output — standard markdown comment convention.
1771
+ if (/^\[\/\/\]: #/.test(line)) {
1772
+ i++;
1773
+ continue;
1774
+ }
1775
+
1684
1776
  // ── Heading ──
1685
1777
  // Count leading '#' characters. Valid heading: 1-6 hashes then a space.
1686
1778
  // Example: "## Hello World ##" → <h2>Hello World</h2>
@@ -2045,6 +2137,7 @@ quikdown.configure = function(options) {
2045
2137
  /** Semantic version (injected at build time) */
2046
2138
  quikdown.version = quikdownVersion;
2047
2139
 
2140
+
2048
2141
  // ════════════════════════════════════════════════════════════════════
2049
2142
  // Exports
2050
2143
  // ════════════════════════════════════════════════════════════════════