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/dist/quikchat-md.umd.js
CHANGED
|
@@ -904,9 +904,9 @@
|
|
|
904
904
|
key: "version",
|
|
905
905
|
value: function version() {
|
|
906
906
|
return {
|
|
907
|
-
"version": "1.2.
|
|
907
|
+
"version": "1.2.7",
|
|
908
908
|
"license": "BSD-2",
|
|
909
|
-
"url": "https://github/deftio/quikchat"
|
|
909
|
+
"url": "https://github.com/deftio/quikchat"
|
|
910
910
|
};
|
|
911
911
|
}
|
|
912
912
|
|
|
@@ -964,7 +964,7 @@
|
|
|
964
964
|
|
|
965
965
|
/**
|
|
966
966
|
* quikdown - Lightweight Markdown Parser
|
|
967
|
-
* @version 1.2.
|
|
967
|
+
* @version 1.2.12
|
|
968
968
|
* @license BSD-2-Clause
|
|
969
969
|
* @copyright DeftIO 2025
|
|
970
970
|
*/
|
|
@@ -1076,7 +1076,7 @@
|
|
|
1076
1076
|
// ────────────────────────────────────────────────────────────────────
|
|
1077
1077
|
|
|
1078
1078
|
/** Build-time version stamp (injected by tools/updateVersion) */
|
|
1079
|
-
const quikdownVersion = '1.2.
|
|
1079
|
+
const quikdownVersion = '1.2.12';
|
|
1080
1080
|
|
|
1081
1081
|
/** CSS class prefix used for all generated elements */
|
|
1082
1082
|
const CLASS_PREFIX = 'quikdown-';
|
|
@@ -1084,6 +1084,10 @@
|
|
|
1084
1084
|
/** Placeholder sigils — chosen to be extremely unlikely in real text */
|
|
1085
1085
|
const PLACEHOLDER_CB = '§CB'; // fenced code blocks
|
|
1086
1086
|
const PLACEHOLDER_IC = '§IC'; // inline code spans
|
|
1087
|
+
const PLACEHOLDER_HT = '§HT'; // safe HTML tags (limited mode)
|
|
1088
|
+
|
|
1089
|
+
/** Attributes whose values need URL sanitization */
|
|
1090
|
+
const URL_ATTRIBUTES = { href:1, src:1, action:1, formaction:1 };
|
|
1087
1091
|
|
|
1088
1092
|
/** HTML entity escape map */
|
|
1089
1093
|
const ESC_MAP = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
@@ -1218,6 +1222,46 @@
|
|
|
1218
1222
|
return trimmedUrl;
|
|
1219
1223
|
}
|
|
1220
1224
|
|
|
1225
|
+
/**
|
|
1226
|
+
* Sanitize attributes on an HTML tag string for limited mode.
|
|
1227
|
+
* Strips on* event handlers (case-insensitive) and runs sanitizeUrl()
|
|
1228
|
+
* on href/src/action/formaction values.
|
|
1229
|
+
*/
|
|
1230
|
+
function sanitizeHtmlTagAttrs(tagStr) {
|
|
1231
|
+
// Self-closing or void tag without attributes — pass through
|
|
1232
|
+
if (!/\s/.test(tagStr.replace(/<\/?[a-zA-Z][a-zA-Z0-9]*/, '').replace(/\/?>$/, ''))) {
|
|
1233
|
+
return tagStr;
|
|
1234
|
+
}
|
|
1235
|
+
// Parse: <tagname ...attrs... > or <tagname ...attrs... />
|
|
1236
|
+
const m = tagStr.match(/^(<\/?[a-zA-Z][a-zA-Z0-9]*)([\s\S]*?)(\/?>)$/);
|
|
1237
|
+
/* istanbul ignore next - defensive: Phase 1.5 regex guarantees valid tag shape */
|
|
1238
|
+
if (!m) return tagStr;
|
|
1239
|
+
|
|
1240
|
+
const [, open, attrStr, close] = m;
|
|
1241
|
+
// Match individual attributes: name="value", name='value', name=value, or bare name
|
|
1242
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- linear: no nested quantifiers
|
|
1243
|
+
const attrRe = /([a-zA-Z_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
1244
|
+
const attrs = [];
|
|
1245
|
+
let am;
|
|
1246
|
+
while ((am = attrRe.exec(attrStr)) !== null) {
|
|
1247
|
+
const name = am[1];
|
|
1248
|
+
const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : am[4];
|
|
1249
|
+
// Strip event handlers (on*)
|
|
1250
|
+
if (/^on/i.test(name)) continue;
|
|
1251
|
+
if (value === undefined) {
|
|
1252
|
+
// Boolean attribute (e.g. disabled, checked)
|
|
1253
|
+
attrs.push(name);
|
|
1254
|
+
} else {
|
|
1255
|
+
let sanitized = value;
|
|
1256
|
+
if (name.toLowerCase() in URL_ATTRIBUTES) {
|
|
1257
|
+
sanitized = sanitizeUrl(value);
|
|
1258
|
+
}
|
|
1259
|
+
attrs.push(`${name}="${sanitized}"`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return open + (attrs.length ? ' ' + attrs.join(' ') : '') + close;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1221
1265
|
// ────────────────────────────────────────────────────────────────
|
|
1222
1266
|
// Phase 1 — Code Extraction
|
|
1223
1267
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -1269,17 +1313,57 @@
|
|
|
1269
1313
|
return placeholder;
|
|
1270
1314
|
});
|
|
1271
1315
|
|
|
1316
|
+
// ────────────────────────────────────────────────────────────────
|
|
1317
|
+
// Phase 1.5 — Safe HTML Extraction (whitelist mode)
|
|
1318
|
+
// ────────────────────────────────────────────────────────────────
|
|
1319
|
+
// When allow_unsafe_html is an object or array, extract whitelisted
|
|
1320
|
+
// HTML tags, sanitize their attributes, and replace with placeholders.
|
|
1321
|
+
// Non-whitelisted tags stay in text so Phase 2 will escape them.
|
|
1322
|
+
|
|
1323
|
+
const safeTags = [];
|
|
1324
|
+
// Normalize: array → object for O(1) lookup; object used as-is
|
|
1325
|
+
const htmlAllow = Array.isArray(allow_unsafe_html)
|
|
1326
|
+
? Object.fromEntries(allow_unsafe_html.map(t => [t, 1]))
|
|
1327
|
+
: (allow_unsafe_html && typeof allow_unsafe_html === 'object') ? allow_unsafe_html : null;
|
|
1328
|
+
|
|
1329
|
+
if (htmlAllow) {
|
|
1330
|
+
// Pass through HTML comments — browsers render them as nothing
|
|
1331
|
+
html = html.replace(/<!--[\s\S]*?-->/g, (match) => {
|
|
1332
|
+
const idx = safeTags.length;
|
|
1333
|
+
safeTags.push(match);
|
|
1334
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
1335
|
+
});
|
|
1336
|
+
html = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g, (match, tagName) => {
|
|
1337
|
+
if (tagName.toLowerCase() in htmlAllow) {
|
|
1338
|
+
const sanitized = sanitizeHtmlTagAttrs(match);
|
|
1339
|
+
const idx = safeTags.length;
|
|
1340
|
+
safeTags.push(sanitized);
|
|
1341
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
1342
|
+
}
|
|
1343
|
+
// Not whitelisted — leave in text for Phase 2 to escape
|
|
1344
|
+
return match;
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1272
1348
|
// ────────────────────────────────────────────────────────────────
|
|
1273
1349
|
// Phase 2 — HTML Escaping
|
|
1274
1350
|
// ────────────────────────────────────────────────────────────────
|
|
1275
1351
|
// All remaining text (everything except code placeholders) is escaped
|
|
1276
1352
|
// to prevent XSS. The `allow_unsafe_html` option skips this for
|
|
1277
1353
|
// trusted pipelines that intentionally embed raw HTML.
|
|
1354
|
+
// For whitelist mode, escaping still runs (only `true` bypasses it).
|
|
1278
1355
|
|
|
1279
|
-
if (
|
|
1356
|
+
if (allow_unsafe_html !== true) {
|
|
1280
1357
|
html = escapeHtml(html);
|
|
1281
1358
|
}
|
|
1282
1359
|
|
|
1360
|
+
// Restore safe HTML tag placeholders after escaping
|
|
1361
|
+
if (htmlAllow) {
|
|
1362
|
+
safeTags.forEach((tag, i) => {
|
|
1363
|
+
html = html.replace(`${PLACEHOLDER_HT}${i}§`, tag);
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1283
1367
|
// ────────────────────────────────────────────────────────────────
|
|
1284
1368
|
// Phase 3 — Block Scanning + Inline Formatting + Paragraphs
|
|
1285
1369
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -1521,6 +1605,14 @@
|
|
|
1521
1605
|
while (i < lines.length) {
|
|
1522
1606
|
const line = lines[i];
|
|
1523
1607
|
|
|
1608
|
+
// ── Markdown comment (reference-link hack) ──
|
|
1609
|
+
// [//]: # (comment) or [//]: # "comment" or [//]: #
|
|
1610
|
+
// These produce no output — standard markdown comment convention.
|
|
1611
|
+
if (/^\[\/\/\]: #/.test(line)) {
|
|
1612
|
+
i++;
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1524
1616
|
// ── Heading ──
|
|
1525
1617
|
// Count leading '#' characters. Valid heading: 1-6 hashes then a space.
|
|
1526
1618
|
// Example: "## Hello World ##" → <h2>Hello World</h2>
|
|
@@ -1885,6 +1977,7 @@
|
|
|
1885
1977
|
/** Semantic version (injected at build time) */
|
|
1886
1978
|
quikdown.version = quikdownVersion;
|
|
1887
1979
|
|
|
1980
|
+
|
|
1888
1981
|
// ════════════════════════════════════════════════════════════════════
|
|
1889
1982
|
// Exports
|
|
1890
1983
|
// ════════════════════════════════════════════════════════════════════
|