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
|
@@ -1068,9 +1068,9 @@
|
|
|
1068
1068
|
key: "version",
|
|
1069
1069
|
value: function version() {
|
|
1070
1070
|
return {
|
|
1071
|
-
"version": "1.2.
|
|
1071
|
+
"version": "1.2.7",
|
|
1072
1072
|
"license": "BSD-2",
|
|
1073
|
-
"url": "https://github/deftio/quikchat"
|
|
1073
|
+
"url": "https://github.com/deftio/quikchat"
|
|
1074
1074
|
};
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
@@ -1128,7 +1128,7 @@
|
|
|
1128
1128
|
|
|
1129
1129
|
/**
|
|
1130
1130
|
* quikdown_bd - Bidirectional Markdown Parser
|
|
1131
|
-
* @version 1.2.
|
|
1131
|
+
* @version 1.2.12
|
|
1132
1132
|
* @license BSD-2-Clause
|
|
1133
1133
|
* @copyright DeftIO 2025
|
|
1134
1134
|
*/
|
|
@@ -1240,7 +1240,7 @@
|
|
|
1240
1240
|
// ────────────────────────────────────────────────────────────────────
|
|
1241
1241
|
|
|
1242
1242
|
/** Build-time version stamp (injected by tools/updateVersion) */
|
|
1243
|
-
const quikdownVersion = '1.2.
|
|
1243
|
+
const quikdownVersion = '1.2.12';
|
|
1244
1244
|
|
|
1245
1245
|
/** CSS class prefix used for all generated elements */
|
|
1246
1246
|
const CLASS_PREFIX = 'quikdown-';
|
|
@@ -1248,6 +1248,10 @@
|
|
|
1248
1248
|
/** Placeholder sigils — chosen to be extremely unlikely in real text */
|
|
1249
1249
|
const PLACEHOLDER_CB = '§CB'; // fenced code blocks
|
|
1250
1250
|
const PLACEHOLDER_IC = '§IC'; // inline code spans
|
|
1251
|
+
const PLACEHOLDER_HT = '§HT'; // safe HTML tags (limited mode)
|
|
1252
|
+
|
|
1253
|
+
/** Attributes whose values need URL sanitization */
|
|
1254
|
+
const URL_ATTRIBUTES = { href:1, src:1, action:1, formaction:1 };
|
|
1251
1255
|
|
|
1252
1256
|
/** HTML entity escape map */
|
|
1253
1257
|
const ESC_MAP = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
@@ -1382,6 +1386,46 @@
|
|
|
1382
1386
|
return trimmedUrl;
|
|
1383
1387
|
}
|
|
1384
1388
|
|
|
1389
|
+
/**
|
|
1390
|
+
* Sanitize attributes on an HTML tag string for limited mode.
|
|
1391
|
+
* Strips on* event handlers (case-insensitive) and runs sanitizeUrl()
|
|
1392
|
+
* on href/src/action/formaction values.
|
|
1393
|
+
*/
|
|
1394
|
+
function sanitizeHtmlTagAttrs(tagStr) {
|
|
1395
|
+
// Self-closing or void tag without attributes — pass through
|
|
1396
|
+
if (!/\s/.test(tagStr.replace(/<\/?[a-zA-Z][a-zA-Z0-9]*/, '').replace(/\/?>$/, ''))) {
|
|
1397
|
+
return tagStr;
|
|
1398
|
+
}
|
|
1399
|
+
// Parse: <tagname ...attrs... > or <tagname ...attrs... />
|
|
1400
|
+
const m = tagStr.match(/^(<\/?[a-zA-Z][a-zA-Z0-9]*)([\s\S]*?)(\/?>)$/);
|
|
1401
|
+
/* istanbul ignore next - defensive: Phase 1.5 regex guarantees valid tag shape */
|
|
1402
|
+
if (!m) return tagStr;
|
|
1403
|
+
|
|
1404
|
+
const [, open, attrStr, close] = m;
|
|
1405
|
+
// Match individual attributes: name="value", name='value', name=value, or bare name
|
|
1406
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- linear: no nested quantifiers
|
|
1407
|
+
const attrRe = /([a-zA-Z_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
1408
|
+
const attrs = [];
|
|
1409
|
+
let am;
|
|
1410
|
+
while ((am = attrRe.exec(attrStr)) !== null) {
|
|
1411
|
+
const name = am[1];
|
|
1412
|
+
const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : am[4];
|
|
1413
|
+
// Strip event handlers (on*)
|
|
1414
|
+
if (/^on/i.test(name)) continue;
|
|
1415
|
+
if (value === undefined) {
|
|
1416
|
+
// Boolean attribute (e.g. disabled, checked)
|
|
1417
|
+
attrs.push(name);
|
|
1418
|
+
} else {
|
|
1419
|
+
let sanitized = value;
|
|
1420
|
+
if (name.toLowerCase() in URL_ATTRIBUTES) {
|
|
1421
|
+
sanitized = sanitizeUrl(value);
|
|
1422
|
+
}
|
|
1423
|
+
attrs.push(`${name}="${sanitized}"`);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return open + (attrs.length ? ' ' + attrs.join(' ') : '') + close;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1385
1429
|
// ────────────────────────────────────────────────────────────────
|
|
1386
1430
|
// Phase 1 — Code Extraction
|
|
1387
1431
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -1433,17 +1477,57 @@
|
|
|
1433
1477
|
return placeholder;
|
|
1434
1478
|
});
|
|
1435
1479
|
|
|
1480
|
+
// ────────────────────────────────────────────────────────────────
|
|
1481
|
+
// Phase 1.5 — Safe HTML Extraction (whitelist mode)
|
|
1482
|
+
// ────────────────────────────────────────────────────────────────
|
|
1483
|
+
// When allow_unsafe_html is an object or array, extract whitelisted
|
|
1484
|
+
// HTML tags, sanitize their attributes, and replace with placeholders.
|
|
1485
|
+
// Non-whitelisted tags stay in text so Phase 2 will escape them.
|
|
1486
|
+
|
|
1487
|
+
const safeTags = [];
|
|
1488
|
+
// Normalize: array → object for O(1) lookup; object used as-is
|
|
1489
|
+
const htmlAllow = Array.isArray(allow_unsafe_html)
|
|
1490
|
+
? Object.fromEntries(allow_unsafe_html.map(t => [t, 1]))
|
|
1491
|
+
: (allow_unsafe_html && typeof allow_unsafe_html === 'object') ? allow_unsafe_html : null;
|
|
1492
|
+
|
|
1493
|
+
if (htmlAllow) {
|
|
1494
|
+
// Pass through HTML comments — browsers render them as nothing
|
|
1495
|
+
html = html.replace(/<!--[\s\S]*?-->/g, (match) => {
|
|
1496
|
+
const idx = safeTags.length;
|
|
1497
|
+
safeTags.push(match);
|
|
1498
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
1499
|
+
});
|
|
1500
|
+
html = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g, (match, tagName) => {
|
|
1501
|
+
if (tagName.toLowerCase() in htmlAllow) {
|
|
1502
|
+
const sanitized = sanitizeHtmlTagAttrs(match);
|
|
1503
|
+
const idx = safeTags.length;
|
|
1504
|
+
safeTags.push(sanitized);
|
|
1505
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
1506
|
+
}
|
|
1507
|
+
// Not whitelisted — leave in text for Phase 2 to escape
|
|
1508
|
+
return match;
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1436
1512
|
// ────────────────────────────────────────────────────────────────
|
|
1437
1513
|
// Phase 2 — HTML Escaping
|
|
1438
1514
|
// ────────────────────────────────────────────────────────────────
|
|
1439
1515
|
// All remaining text (everything except code placeholders) is escaped
|
|
1440
1516
|
// to prevent XSS. The `allow_unsafe_html` option skips this for
|
|
1441
1517
|
// trusted pipelines that intentionally embed raw HTML.
|
|
1518
|
+
// For whitelist mode, escaping still runs (only `true` bypasses it).
|
|
1442
1519
|
|
|
1443
|
-
if (
|
|
1520
|
+
if (allow_unsafe_html !== true) {
|
|
1444
1521
|
html = escapeHtml(html);
|
|
1445
1522
|
}
|
|
1446
1523
|
|
|
1524
|
+
// Restore safe HTML tag placeholders after escaping
|
|
1525
|
+
if (htmlAllow) {
|
|
1526
|
+
safeTags.forEach((tag, i) => {
|
|
1527
|
+
html = html.replace(`${PLACEHOLDER_HT}${i}§`, tag);
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1447
1531
|
// ────────────────────────────────────────────────────────────────
|
|
1448
1532
|
// Phase 3 — Block Scanning + Inline Formatting + Paragraphs
|
|
1449
1533
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -1685,6 +1769,14 @@
|
|
|
1685
1769
|
while (i < lines.length) {
|
|
1686
1770
|
const line = lines[i];
|
|
1687
1771
|
|
|
1772
|
+
// ── Markdown comment (reference-link hack) ──
|
|
1773
|
+
// [//]: # (comment) or [//]: # "comment" or [//]: #
|
|
1774
|
+
// These produce no output — standard markdown comment convention.
|
|
1775
|
+
if (/^\[\/\/\]: #/.test(line)) {
|
|
1776
|
+
i++;
|
|
1777
|
+
continue;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1688
1780
|
// ── Heading ──
|
|
1689
1781
|
// Count leading '#' characters. Valid heading: 1-6 hashes then a space.
|
|
1690
1782
|
// Example: "## Hello World ##" → <h2>Hello World</h2>
|
|
@@ -2049,6 +2141,7 @@
|
|
|
2049
2141
|
/** Semantic version (injected at build time) */
|
|
2050
2142
|
quikdown.version = quikdownVersion;
|
|
2051
2143
|
|
|
2144
|
+
|
|
2052
2145
|
// ════════════════════════════════════════════════════════════════════
|
|
2053
2146
|
// Exports
|
|
2054
2147
|
// ════════════════════════════════════════════════════════════════════
|