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.
- package/README.md +7 -2
- package/dist/build-manifest.json +57 -57
- package/dist/quikchat-md-full.cjs.js +98 -5
- package/dist/quikchat-md-full.cjs.js.map +1 -1
- package/dist/quikchat-md-full.cjs.min.js +3 -3
- package/dist/quikchat-md-full.cjs.min.js.map +1 -1
- package/dist/quikchat-md-full.esm.js +98 -5
- package/dist/quikchat-md-full.esm.js.map +1 -1
- package/dist/quikchat-md-full.esm.min.js +3 -3
- package/dist/quikchat-md-full.esm.min.js.map +1 -1
- package/dist/quikchat-md-full.umd.js +98 -5
- package/dist/quikchat-md-full.umd.js.map +1 -1
- package/dist/quikchat-md-full.umd.min.js +3 -3
- package/dist/quikchat-md-full.umd.min.js.map +1 -1
- package/dist/quikchat-md.cjs.js +98 -5
- package/dist/quikchat-md.cjs.js.map +1 -1
- package/dist/quikchat-md.cjs.min.js +3 -3
- package/dist/quikchat-md.cjs.min.js.map +1 -1
- package/dist/quikchat-md.esm.js +98 -5
- package/dist/quikchat-md.esm.js.map +1 -1
- package/dist/quikchat-md.esm.min.js +3 -3
- package/dist/quikchat-md.esm.min.js.map +1 -1
- package/dist/quikchat-md.umd.js +98 -5
- package/dist/quikchat-md.umd.js.map +1 -1
- package/dist/quikchat-md.umd.min.js +3 -3
- package/dist/quikchat-md.umd.min.js.map +1 -1
- package/dist/quikchat.cjs.js +2 -2
- 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.esm.js +2 -2
- 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.umd.js +2 -2
- 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 +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
# QuikChat
|
|
6
6
|
|
|
7
|
-
A lightweight, zero-dependency
|
|
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, Solid — or 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
|
+
[](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,
|
|
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
|
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.2.
|
|
3
|
-
"generated": "2026-
|
|
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":
|
|
16
|
-
"minBytes":
|
|
17
|
-
"gzipBytes":
|
|
15
|
+
"rawBytes": 33119,
|
|
16
|
+
"minBytes": 17421,
|
|
17
|
+
"gzipBytes": 4849,
|
|
18
18
|
"rawSize": "32.34 KB",
|
|
19
19
|
"minSize": "17.01 KB",
|
|
20
|
-
"gzipSize": "4.
|
|
21
|
-
"sri": "sha384-
|
|
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":
|
|
28
|
-
"minBytes":
|
|
29
|
-
"gzipBytes":
|
|
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-
|
|
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":
|
|
40
|
-
"minBytes":
|
|
41
|
-
"gzipBytes":
|
|
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-
|
|
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":
|
|
72
|
-
"minBytes":
|
|
73
|
-
"gzipBytes":
|
|
74
|
-
"rawSize": "
|
|
75
|
-
"minSize": "
|
|
76
|
-
"gzipSize": "
|
|
77
|
-
"sri": "sha384-
|
|
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":
|
|
84
|
-
"minBytes":
|
|
85
|
-
"gzipBytes":
|
|
86
|
-
"rawSize": "
|
|
87
|
-
"minSize": "
|
|
88
|
-
"gzipSize": "
|
|
89
|
-
"sri": "sha384-
|
|
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":
|
|
96
|
-
"minBytes":
|
|
97
|
-
"gzipBytes":
|
|
98
|
-
"rawSize": "
|
|
99
|
-
"minSize": "
|
|
100
|
-
"gzipSize": "
|
|
101
|
-
"sri": "sha384-
|
|
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":
|
|
116
|
-
"minBytes":
|
|
117
|
-
"gzipBytes":
|
|
118
|
-
"rawSize": "
|
|
119
|
-
"minSize": "
|
|
120
|
-
"gzipSize": "
|
|
121
|
-
"sri": "sha384-
|
|
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":
|
|
128
|
-
"minBytes":
|
|
129
|
-
"gzipBytes":
|
|
130
|
-
"rawSize": "
|
|
131
|
-
"minSize": "
|
|
132
|
-
"gzipSize": "
|
|
133
|
-
"sri": "sha384-
|
|
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":
|
|
140
|
-
"minBytes":
|
|
141
|
-
"gzipBytes":
|
|
142
|
-
"rawSize": "
|
|
143
|
-
"minSize": "
|
|
144
|
-
"gzipSize": "
|
|
145
|
-
"sri": "sha384-
|
|
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.
|
|
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.
|
|
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.
|
|
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 = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
@@ -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 (
|
|
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
|
// ════════════════════════════════════════════════════════════════════
|