quikdown 1.2.9 → 1.2.11
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 +5 -5
- package/dist/quikdown.cjs +104 -5
- package/dist/quikdown.d.ts +12 -0
- package/dist/quikdown.dark.css +1 -1
- package/dist/quikdown.esm.js +104 -5
- package/dist/quikdown.esm.min.js +2 -2
- package/dist/quikdown.esm.min.js.gz +0 -0
- package/dist/quikdown.esm.min.js.map +1 -1
- package/dist/quikdown.light.css +1 -1
- package/dist/quikdown.umd.js +104 -5
- package/dist/quikdown.umd.min.js +2 -2
- package/dist/quikdown.umd.min.js.gz +0 -0
- package/dist/quikdown.umd.min.js.map +1 -1
- package/dist/quikdown_ast.cjs +16 -28
- package/dist/quikdown_ast.esm.js +16 -28
- package/dist/quikdown_ast.esm.min.js +2 -2
- package/dist/quikdown_ast.esm.min.js.gz +0 -0
- package/dist/quikdown_ast.esm.min.js.map +1 -1
- package/dist/quikdown_ast.umd.js +16 -28
- package/dist/quikdown_ast.umd.min.js +2 -2
- package/dist/quikdown_ast.umd.min.js.gz +0 -0
- package/dist/quikdown_ast.umd.min.js.map +1 -1
- package/dist/quikdown_ast_html.cjs +17 -29
- package/dist/quikdown_ast_html.esm.js +17 -29
- package/dist/quikdown_ast_html.esm.min.js +2 -2
- package/dist/quikdown_ast_html.esm.min.js.gz +0 -0
- package/dist/quikdown_ast_html.esm.min.js.map +1 -1
- package/dist/quikdown_ast_html.umd.js +17 -29
- package/dist/quikdown_ast_html.umd.min.js +2 -2
- package/dist/quikdown_ast_html.umd.min.js.gz +0 -0
- package/dist/quikdown_ast_html.umd.min.js.map +1 -1
- package/dist/quikdown_bd.cjs +104 -5
- package/dist/quikdown_bd.esm.js +104 -5
- package/dist/quikdown_bd.esm.min.js +2 -2
- package/dist/quikdown_bd.esm.min.js.gz +0 -0
- package/dist/quikdown_bd.esm.min.js.map +1 -1
- package/dist/quikdown_bd.umd.js +104 -5
- package/dist/quikdown_bd.umd.min.js +2 -2
- package/dist/quikdown_bd.umd.min.js.gz +0 -0
- package/dist/quikdown_bd.umd.min.js.map +1 -1
- package/dist/quikdown_edit.cjs +244 -12
- package/dist/quikdown_edit.esm.js +244 -12
- package/dist/quikdown_edit.esm.min.js +3 -3
- package/dist/quikdown_edit.esm.min.js.gz +0 -0
- package/dist/quikdown_edit.esm.min.js.map +1 -1
- package/dist/quikdown_edit.umd.js +244 -12
- package/dist/quikdown_edit.umd.min.js +3 -3
- package/dist/quikdown_edit.umd.min.js.gz +0 -0
- package/dist/quikdown_edit.umd.min.js.map +1 -1
- package/dist/quikdown_json.cjs +17 -29
- package/dist/quikdown_json.esm.js +17 -29
- package/dist/quikdown_json.esm.min.js +2 -2
- package/dist/quikdown_json.esm.min.js.gz +0 -0
- package/dist/quikdown_json.esm.min.js.map +1 -1
- package/dist/quikdown_json.umd.js +17 -29
- package/dist/quikdown_json.umd.min.js +2 -2
- package/dist/quikdown_json.umd.min.js.gz +0 -0
- package/dist/quikdown_json.umd.min.js.map +1 -1
- package/dist/quikdown_yaml.cjs +17 -29
- package/dist/quikdown_yaml.esm.js +17 -29
- package/dist/quikdown_yaml.esm.min.js +2 -2
- package/dist/quikdown_yaml.esm.min.js.gz +0 -0
- package/dist/quikdown_yaml.esm.min.js.map +1 -1
- package/dist/quikdown_yaml.umd.js +17 -29
- package/dist/quikdown_yaml.umd.min.js +2 -2
- package/dist/quikdown_yaml.umd.min.js.gz +0 -0
- package/dist/quikdown_yaml.umd.min.js.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/deftio/quikdown/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/quikdown)
|
|
5
|
-
[](https://github.com/deftio/quikdown)
|
|
6
6
|
[](https://opensource.org/licenses/BSD-2-Clause)
|
|
7
|
-
[](https://bundlephobia.com/package/quikdown)
|
|
8
8
|
|
|
9
9
|
A small, secure markdown parser and editor for browsers and Node.js. Three modules — use only what you need.
|
|
10
10
|
|
|
11
|
-
- **quikdown.js** (9.
|
|
12
|
-
- **quikdown_bd.js** (14.
|
|
13
|
-
- **quikdown_edit.js** (
|
|
11
|
+
- **quikdown.js** (9.8 KB) — Markdown to HTML parser. XSS-safe, fence plugin callbacks, inline styles or CSS classes.
|
|
12
|
+
- **quikdown_bd.js** (14.6 KB) — Bidirectional: everything in core plus HTML to Markdown round-trip.
|
|
13
|
+
- **quikdown_edit.js** (84.3 KB) — Drop-in split-view editor with live preview, undo/redo, bidirectional editing, and lazy-loaded fence plugins for code highlighting, Mermaid, MathJax, SVG, CSV, GeoJSON, and STL.
|
|
14
14
|
- **quikdown_edit_standalone.js** (3.8 MB) — Offline/air-gapped editor. Same as above but bundles highlight.js, Mermaid, DOMPurify, Leaflet, and Three.js — no CDN required. See [Standalone Docs](docs/standalone-editor.md).
|
|
15
15
|
- **quikdown_ast.js** / **quikdown_json.js** / **quikdown_yaml.js** / **quikdown_ast_html.js** — AST companion libraries for structured output.
|
|
16
16
|
|
package/dist/quikdown.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* quikdown - Lightweight Markdown Parser
|
|
3
|
-
* @version 1.2.
|
|
3
|
+
* @version 1.2.11
|
|
4
4
|
* @license BSD-2-Clause
|
|
5
5
|
* @copyright DeftIO 2025
|
|
6
6
|
*/
|
|
@@ -114,7 +114,7 @@ function isDashHRLine(trimmed) {
|
|
|
114
114
|
// ────────────────────────────────────────────────────────────────────
|
|
115
115
|
|
|
116
116
|
/** Build-time version stamp (injected by tools/updateVersion) */
|
|
117
|
-
const quikdownVersion = '1.2.
|
|
117
|
+
const quikdownVersion = '1.2.11';
|
|
118
118
|
|
|
119
119
|
/** CSS class prefix used for all generated elements */
|
|
120
120
|
const CLASS_PREFIX = 'quikdown-';
|
|
@@ -122,6 +122,10 @@ const CLASS_PREFIX = 'quikdown-';
|
|
|
122
122
|
/** Placeholder sigils — chosen to be extremely unlikely in real text */
|
|
123
123
|
const PLACEHOLDER_CB = '§CB'; // fenced code blocks
|
|
124
124
|
const PLACEHOLDER_IC = '§IC'; // inline code spans
|
|
125
|
+
const PLACEHOLDER_HT = '§HT'; // safe HTML tags (limited mode)
|
|
126
|
+
|
|
127
|
+
/** Attributes whose values need URL sanitization */
|
|
128
|
+
const URL_ATTRIBUTES = { href:1, src:1, action:1, formaction:1 };
|
|
125
129
|
|
|
126
130
|
/** HTML entity escape map */
|
|
127
131
|
const ESC_MAP = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
@@ -256,6 +260,46 @@ function quikdown(markdown, options = {}) {
|
|
|
256
260
|
return trimmedUrl;
|
|
257
261
|
}
|
|
258
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Sanitize attributes on an HTML tag string for limited mode.
|
|
265
|
+
* Strips on* event handlers (case-insensitive) and runs sanitizeUrl()
|
|
266
|
+
* on href/src/action/formaction values.
|
|
267
|
+
*/
|
|
268
|
+
function sanitizeHtmlTagAttrs(tagStr) {
|
|
269
|
+
// Self-closing or void tag without attributes — pass through
|
|
270
|
+
if (!/\s/.test(tagStr.replace(/<\/?[a-zA-Z][a-zA-Z0-9]*/, '').replace(/\/?>$/, ''))) {
|
|
271
|
+
return tagStr;
|
|
272
|
+
}
|
|
273
|
+
// Parse: <tagname ...attrs... > or <tagname ...attrs... />
|
|
274
|
+
const m = tagStr.match(/^(<\/?[a-zA-Z][a-zA-Z0-9]*)([\s\S]*?)(\/?>)$/);
|
|
275
|
+
/* istanbul ignore next - defensive: Phase 1.5 regex guarantees valid tag shape */
|
|
276
|
+
if (!m) return tagStr;
|
|
277
|
+
|
|
278
|
+
const [, open, attrStr, close] = m;
|
|
279
|
+
// Match individual attributes: name="value", name='value', name=value, or bare name
|
|
280
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- linear: no nested quantifiers
|
|
281
|
+
const attrRe = /([a-zA-Z_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
282
|
+
const attrs = [];
|
|
283
|
+
let am;
|
|
284
|
+
while ((am = attrRe.exec(attrStr)) !== null) {
|
|
285
|
+
const name = am[1];
|
|
286
|
+
const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : am[4];
|
|
287
|
+
// Strip event handlers (on*)
|
|
288
|
+
if (/^on/i.test(name)) continue;
|
|
289
|
+
if (value === undefined) {
|
|
290
|
+
// Boolean attribute (e.g. disabled, checked)
|
|
291
|
+
attrs.push(name);
|
|
292
|
+
} else {
|
|
293
|
+
let sanitized = value;
|
|
294
|
+
if (name.toLowerCase() in URL_ATTRIBUTES) {
|
|
295
|
+
sanitized = sanitizeUrl(value);
|
|
296
|
+
}
|
|
297
|
+
attrs.push(`${name}="${sanitized}"`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return open + (attrs.length ? ' ' + attrs.join(' ') : '') + close;
|
|
301
|
+
}
|
|
302
|
+
|
|
259
303
|
// ────────────────────────────────────────────────────────────────
|
|
260
304
|
// Phase 1 — Code Extraction
|
|
261
305
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -307,17 +351,57 @@ function quikdown(markdown, options = {}) {
|
|
|
307
351
|
return placeholder;
|
|
308
352
|
});
|
|
309
353
|
|
|
354
|
+
// ────────────────────────────────────────────────────────────────
|
|
355
|
+
// Phase 1.5 — Safe HTML Extraction (whitelist mode)
|
|
356
|
+
// ────────────────────────────────────────────────────────────────
|
|
357
|
+
// When allow_unsafe_html is an object or array, extract whitelisted
|
|
358
|
+
// HTML tags, sanitize their attributes, and replace with placeholders.
|
|
359
|
+
// Non-whitelisted tags stay in text so Phase 2 will escape them.
|
|
360
|
+
|
|
361
|
+
const safeTags = [];
|
|
362
|
+
// Normalize: array → object for O(1) lookup; object used as-is
|
|
363
|
+
const htmlAllow = Array.isArray(allow_unsafe_html)
|
|
364
|
+
? Object.fromEntries(allow_unsafe_html.map(t => [t, 1]))
|
|
365
|
+
: (allow_unsafe_html && typeof allow_unsafe_html === 'object') ? allow_unsafe_html : null;
|
|
366
|
+
|
|
367
|
+
if (htmlAllow) {
|
|
368
|
+
// Pass through HTML comments — browsers render them as nothing
|
|
369
|
+
html = html.replace(/<!--[\s\S]*?-->/g, (match) => {
|
|
370
|
+
const idx = safeTags.length;
|
|
371
|
+
safeTags.push(match);
|
|
372
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
373
|
+
});
|
|
374
|
+
html = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g, (match, tagName) => {
|
|
375
|
+
if (tagName.toLowerCase() in htmlAllow) {
|
|
376
|
+
const sanitized = sanitizeHtmlTagAttrs(match);
|
|
377
|
+
const idx = safeTags.length;
|
|
378
|
+
safeTags.push(sanitized);
|
|
379
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
380
|
+
}
|
|
381
|
+
// Not whitelisted — leave in text for Phase 2 to escape
|
|
382
|
+
return match;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
310
386
|
// ────────────────────────────────────────────────────────────────
|
|
311
387
|
// Phase 2 — HTML Escaping
|
|
312
388
|
// ────────────────────────────────────────────────────────────────
|
|
313
389
|
// All remaining text (everything except code placeholders) is escaped
|
|
314
390
|
// to prevent XSS. The `allow_unsafe_html` option skips this for
|
|
315
391
|
// trusted pipelines that intentionally embed raw HTML.
|
|
392
|
+
// For whitelist mode, escaping still runs (only `true` bypasses it).
|
|
316
393
|
|
|
317
|
-
if (
|
|
394
|
+
if (allow_unsafe_html !== true) {
|
|
318
395
|
html = escapeHtml(html);
|
|
319
396
|
}
|
|
320
397
|
|
|
398
|
+
// Restore safe HTML tag placeholders after escaping
|
|
399
|
+
if (htmlAllow) {
|
|
400
|
+
safeTags.forEach((tag, i) => {
|
|
401
|
+
html = html.replace(`${PLACEHOLDER_HT}${i}§`, tag);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
321
405
|
// ────────────────────────────────────────────────────────────────
|
|
322
406
|
// Phase 3 — Block Scanning + Inline Formatting + Paragraphs
|
|
323
407
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -359,7 +443,6 @@ function quikdown(markdown, options = {}) {
|
|
|
359
443
|
// Images (must come before links —  vs [text](url))
|
|
360
444
|
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
|
|
361
445
|
const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);
|
|
362
|
-
// Bidirectional attributes are only exercised via quikdown_bd bundle.
|
|
363
446
|
/* istanbul ignore next - bd-only branch */
|
|
364
447
|
const altAttr = bidirectional && alt ? ` data-qd-alt="${escapeHtml(alt)}"` : '';
|
|
365
448
|
/* istanbul ignore next - bd-only branch */
|
|
@@ -383,8 +466,12 @@ function quikdown(markdown, options = {}) {
|
|
|
383
466
|
return `${prefix}<a${getAttr('a')} href="${sanitizedUrl}" rel="noopener noreferrer">${url}</a>`;
|
|
384
467
|
});
|
|
385
468
|
|
|
469
|
+
// Protect rendered tags so emphasis regexes don't see attribute
|
|
470
|
+
// values — fixes #3 (underscores in URLs interpreted as emphasis).
|
|
471
|
+
const savedTags = [];
|
|
472
|
+
html = html.replace(/<[^>]+>/g, m => { savedTags.push(m); return `%%T${savedTags.length - 1}%%`; });
|
|
473
|
+
|
|
386
474
|
// Bold, italic, strikethrough
|
|
387
|
-
// Order matters: ** before * (so ** isn't consumed as two *s)
|
|
388
475
|
const inlinePatterns = [
|
|
389
476
|
[/\*\*(.+?)\*\*/g, 'strong', '**'],
|
|
390
477
|
[/__(.+?)__/g, 'strong', '__'],
|
|
@@ -396,6 +483,9 @@ function quikdown(markdown, options = {}) {
|
|
|
396
483
|
html = html.replace(pattern, `<${tag}${getAttr(tag)}${dataQd(marker)}>$1</${tag}>`);
|
|
397
484
|
});
|
|
398
485
|
|
|
486
|
+
// Restore protected tags
|
|
487
|
+
html = html.replace(/%%T(\d+)%%/g, (_, i) => savedTags[i]);
|
|
488
|
+
|
|
399
489
|
// ── Step 5: Line breaks + paragraph wrapping ──
|
|
400
490
|
if (lazy_linefeeds) {
|
|
401
491
|
// Lazy linefeeds mode: every single \n becomes <br> EXCEPT:
|
|
@@ -553,6 +643,14 @@ function scanLineBlocks(text, getAttr, dataQd) {
|
|
|
553
643
|
while (i < lines.length) {
|
|
554
644
|
const line = lines[i];
|
|
555
645
|
|
|
646
|
+
// ── Markdown comment (reference-link hack) ──
|
|
647
|
+
// [//]: # (comment) or [//]: # "comment" or [//]: #
|
|
648
|
+
// These produce no output — standard markdown comment convention.
|
|
649
|
+
if (/^\[\/\/\]: #/.test(line)) {
|
|
650
|
+
i++;
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
|
|
556
654
|
// ── Heading ──
|
|
557
655
|
// Count leading '#' characters. Valid heading: 1-6 hashes then a space.
|
|
558
656
|
// Example: "## Hello World ##" → <h2>Hello World</h2>
|
|
@@ -917,6 +1015,7 @@ quikdown.configure = function(options) {
|
|
|
917
1015
|
/** Semantic version (injected at build time) */
|
|
918
1016
|
quikdown.version = quikdownVersion;
|
|
919
1017
|
|
|
1018
|
+
|
|
920
1019
|
// ════════════════════════════════════════════════════════════════════
|
|
921
1020
|
// Exports
|
|
922
1021
|
// ════════════════════════════════════════════════════════════════════
|
package/dist/quikdown.d.ts
CHANGED
|
@@ -52,6 +52,17 @@ declare module 'quikdown' {
|
|
|
52
52
|
*/
|
|
53
53
|
allow_unsafe_urls?: boolean;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Controls HTML passthrough in parsed output.
|
|
57
|
+
* - false (default): all HTML tags are escaped (secure)
|
|
58
|
+
* - true: all HTML passes through unchanged (unsafe, use with trusted content only)
|
|
59
|
+
* - Record<string, any>: object whose keys are lowercase tag names to allow
|
|
60
|
+
* (e.g. { img: 1, a: 1, div: 1 }). Use quikdown.SAFE_HTML_TAGS for a curated default.
|
|
61
|
+
* - string[]: array of tag names (converted to object internally)
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
allow_unsafe_html?: boolean | Record<string, any> | string[];
|
|
65
|
+
|
|
55
66
|
/**
|
|
56
67
|
* If true, adds data-qd attributes for bidirectional conversion.
|
|
57
68
|
* Enables HTML to Markdown conversion.
|
|
@@ -95,6 +106,7 @@ declare module 'quikdown' {
|
|
|
95
106
|
* The version of quikdown
|
|
96
107
|
*/
|
|
97
108
|
export const version: string;
|
|
109
|
+
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
export = quikdown;
|
package/dist/quikdown.dark.css
CHANGED
package/dist/quikdown.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* quikdown - Lightweight Markdown Parser
|
|
3
|
-
* @version 1.2.
|
|
3
|
+
* @version 1.2.11
|
|
4
4
|
* @license BSD-2-Clause
|
|
5
5
|
* @copyright DeftIO 2025
|
|
6
6
|
*/
|
|
@@ -112,7 +112,7 @@ function isDashHRLine(trimmed) {
|
|
|
112
112
|
// ────────────────────────────────────────────────────────────────────
|
|
113
113
|
|
|
114
114
|
/** Build-time version stamp (injected by tools/updateVersion) */
|
|
115
|
-
const quikdownVersion = '1.2.
|
|
115
|
+
const quikdownVersion = '1.2.11';
|
|
116
116
|
|
|
117
117
|
/** CSS class prefix used for all generated elements */
|
|
118
118
|
const CLASS_PREFIX = 'quikdown-';
|
|
@@ -120,6 +120,10 @@ const CLASS_PREFIX = 'quikdown-';
|
|
|
120
120
|
/** Placeholder sigils — chosen to be extremely unlikely in real text */
|
|
121
121
|
const PLACEHOLDER_CB = '§CB'; // fenced code blocks
|
|
122
122
|
const PLACEHOLDER_IC = '§IC'; // inline code spans
|
|
123
|
+
const PLACEHOLDER_HT = '§HT'; // safe HTML tags (limited mode)
|
|
124
|
+
|
|
125
|
+
/** Attributes whose values need URL sanitization */
|
|
126
|
+
const URL_ATTRIBUTES = { href:1, src:1, action:1, formaction:1 };
|
|
123
127
|
|
|
124
128
|
/** HTML entity escape map */
|
|
125
129
|
const ESC_MAP = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
@@ -254,6 +258,46 @@ function quikdown(markdown, options = {}) {
|
|
|
254
258
|
return trimmedUrl;
|
|
255
259
|
}
|
|
256
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Sanitize attributes on an HTML tag string for limited mode.
|
|
263
|
+
* Strips on* event handlers (case-insensitive) and runs sanitizeUrl()
|
|
264
|
+
* on href/src/action/formaction values.
|
|
265
|
+
*/
|
|
266
|
+
function sanitizeHtmlTagAttrs(tagStr) {
|
|
267
|
+
// Self-closing or void tag without attributes — pass through
|
|
268
|
+
if (!/\s/.test(tagStr.replace(/<\/?[a-zA-Z][a-zA-Z0-9]*/, '').replace(/\/?>$/, ''))) {
|
|
269
|
+
return tagStr;
|
|
270
|
+
}
|
|
271
|
+
// Parse: <tagname ...attrs... > or <tagname ...attrs... />
|
|
272
|
+
const m = tagStr.match(/^(<\/?[a-zA-Z][a-zA-Z0-9]*)([\s\S]*?)(\/?>)$/);
|
|
273
|
+
/* istanbul ignore next - defensive: Phase 1.5 regex guarantees valid tag shape */
|
|
274
|
+
if (!m) return tagStr;
|
|
275
|
+
|
|
276
|
+
const [, open, attrStr, close] = m;
|
|
277
|
+
// Match individual attributes: name="value", name='value', name=value, or bare name
|
|
278
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- linear: no nested quantifiers
|
|
279
|
+
const attrRe = /([a-zA-Z_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
280
|
+
const attrs = [];
|
|
281
|
+
let am;
|
|
282
|
+
while ((am = attrRe.exec(attrStr)) !== null) {
|
|
283
|
+
const name = am[1];
|
|
284
|
+
const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : am[4];
|
|
285
|
+
// Strip event handlers (on*)
|
|
286
|
+
if (/^on/i.test(name)) continue;
|
|
287
|
+
if (value === undefined) {
|
|
288
|
+
// Boolean attribute (e.g. disabled, checked)
|
|
289
|
+
attrs.push(name);
|
|
290
|
+
} else {
|
|
291
|
+
let sanitized = value;
|
|
292
|
+
if (name.toLowerCase() in URL_ATTRIBUTES) {
|
|
293
|
+
sanitized = sanitizeUrl(value);
|
|
294
|
+
}
|
|
295
|
+
attrs.push(`${name}="${sanitized}"`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return open + (attrs.length ? ' ' + attrs.join(' ') : '') + close;
|
|
299
|
+
}
|
|
300
|
+
|
|
257
301
|
// ────────────────────────────────────────────────────────────────
|
|
258
302
|
// Phase 1 — Code Extraction
|
|
259
303
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -305,17 +349,57 @@ function quikdown(markdown, options = {}) {
|
|
|
305
349
|
return placeholder;
|
|
306
350
|
});
|
|
307
351
|
|
|
352
|
+
// ────────────────────────────────────────────────────────────────
|
|
353
|
+
// Phase 1.5 — Safe HTML Extraction (whitelist mode)
|
|
354
|
+
// ────────────────────────────────────────────────────────────────
|
|
355
|
+
// When allow_unsafe_html is an object or array, extract whitelisted
|
|
356
|
+
// HTML tags, sanitize their attributes, and replace with placeholders.
|
|
357
|
+
// Non-whitelisted tags stay in text so Phase 2 will escape them.
|
|
358
|
+
|
|
359
|
+
const safeTags = [];
|
|
360
|
+
// Normalize: array → object for O(1) lookup; object used as-is
|
|
361
|
+
const htmlAllow = Array.isArray(allow_unsafe_html)
|
|
362
|
+
? Object.fromEntries(allow_unsafe_html.map(t => [t, 1]))
|
|
363
|
+
: (allow_unsafe_html && typeof allow_unsafe_html === 'object') ? allow_unsafe_html : null;
|
|
364
|
+
|
|
365
|
+
if (htmlAllow) {
|
|
366
|
+
// Pass through HTML comments — browsers render them as nothing
|
|
367
|
+
html = html.replace(/<!--[\s\S]*?-->/g, (match) => {
|
|
368
|
+
const idx = safeTags.length;
|
|
369
|
+
safeTags.push(match);
|
|
370
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
371
|
+
});
|
|
372
|
+
html = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g, (match, tagName) => {
|
|
373
|
+
if (tagName.toLowerCase() in htmlAllow) {
|
|
374
|
+
const sanitized = sanitizeHtmlTagAttrs(match);
|
|
375
|
+
const idx = safeTags.length;
|
|
376
|
+
safeTags.push(sanitized);
|
|
377
|
+
return `${PLACEHOLDER_HT}${idx}§`;
|
|
378
|
+
}
|
|
379
|
+
// Not whitelisted — leave in text for Phase 2 to escape
|
|
380
|
+
return match;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
308
384
|
// ────────────────────────────────────────────────────────────────
|
|
309
385
|
// Phase 2 — HTML Escaping
|
|
310
386
|
// ────────────────────────────────────────────────────────────────
|
|
311
387
|
// All remaining text (everything except code placeholders) is escaped
|
|
312
388
|
// to prevent XSS. The `allow_unsafe_html` option skips this for
|
|
313
389
|
// trusted pipelines that intentionally embed raw HTML.
|
|
390
|
+
// For whitelist mode, escaping still runs (only `true` bypasses it).
|
|
314
391
|
|
|
315
|
-
if (
|
|
392
|
+
if (allow_unsafe_html !== true) {
|
|
316
393
|
html = escapeHtml(html);
|
|
317
394
|
}
|
|
318
395
|
|
|
396
|
+
// Restore safe HTML tag placeholders after escaping
|
|
397
|
+
if (htmlAllow) {
|
|
398
|
+
safeTags.forEach((tag, i) => {
|
|
399
|
+
html = html.replace(`${PLACEHOLDER_HT}${i}§`, tag);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
319
403
|
// ────────────────────────────────────────────────────────────────
|
|
320
404
|
// Phase 3 — Block Scanning + Inline Formatting + Paragraphs
|
|
321
405
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -357,7 +441,6 @@ function quikdown(markdown, options = {}) {
|
|
|
357
441
|
// Images (must come before links —  vs [text](url))
|
|
358
442
|
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
|
|
359
443
|
const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);
|
|
360
|
-
// Bidirectional attributes are only exercised via quikdown_bd bundle.
|
|
361
444
|
/* istanbul ignore next - bd-only branch */
|
|
362
445
|
const altAttr = bidirectional && alt ? ` data-qd-alt="${escapeHtml(alt)}"` : '';
|
|
363
446
|
/* istanbul ignore next - bd-only branch */
|
|
@@ -381,8 +464,12 @@ function quikdown(markdown, options = {}) {
|
|
|
381
464
|
return `${prefix}<a${getAttr('a')} href="${sanitizedUrl}" rel="noopener noreferrer">${url}</a>`;
|
|
382
465
|
});
|
|
383
466
|
|
|
467
|
+
// Protect rendered tags so emphasis regexes don't see attribute
|
|
468
|
+
// values — fixes #3 (underscores in URLs interpreted as emphasis).
|
|
469
|
+
const savedTags = [];
|
|
470
|
+
html = html.replace(/<[^>]+>/g, m => { savedTags.push(m); return `%%T${savedTags.length - 1}%%`; });
|
|
471
|
+
|
|
384
472
|
// Bold, italic, strikethrough
|
|
385
|
-
// Order matters: ** before * (so ** isn't consumed as two *s)
|
|
386
473
|
const inlinePatterns = [
|
|
387
474
|
[/\*\*(.+?)\*\*/g, 'strong', '**'],
|
|
388
475
|
[/__(.+?)__/g, 'strong', '__'],
|
|
@@ -394,6 +481,9 @@ function quikdown(markdown, options = {}) {
|
|
|
394
481
|
html = html.replace(pattern, `<${tag}${getAttr(tag)}${dataQd(marker)}>$1</${tag}>`);
|
|
395
482
|
});
|
|
396
483
|
|
|
484
|
+
// Restore protected tags
|
|
485
|
+
html = html.replace(/%%T(\d+)%%/g, (_, i) => savedTags[i]);
|
|
486
|
+
|
|
397
487
|
// ── Step 5: Line breaks + paragraph wrapping ──
|
|
398
488
|
if (lazy_linefeeds) {
|
|
399
489
|
// Lazy linefeeds mode: every single \n becomes <br> EXCEPT:
|
|
@@ -551,6 +641,14 @@ function scanLineBlocks(text, getAttr, dataQd) {
|
|
|
551
641
|
while (i < lines.length) {
|
|
552
642
|
const line = lines[i];
|
|
553
643
|
|
|
644
|
+
// ── Markdown comment (reference-link hack) ──
|
|
645
|
+
// [//]: # (comment) or [//]: # "comment" or [//]: #
|
|
646
|
+
// These produce no output — standard markdown comment convention.
|
|
647
|
+
if (/^\[\/\/\]: #/.test(line)) {
|
|
648
|
+
i++;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
554
652
|
// ── Heading ──
|
|
555
653
|
// Count leading '#' characters. Valid heading: 1-6 hashes then a space.
|
|
556
654
|
// Example: "## Hello World ##" → <h2>Hello World</h2>
|
|
@@ -915,6 +1013,7 @@ quikdown.configure = function(options) {
|
|
|
915
1013
|
/** Semantic version (injected at build time) */
|
|
916
1014
|
quikdown.version = quikdownVersion;
|
|
917
1015
|
|
|
1016
|
+
|
|
918
1017
|
// ════════════════════════════════════════════════════════════════════
|
|
919
1018
|
// Exports
|
|
920
1019
|
// ════════════════════════════════════════════════════════════════════
|
package/dist/quikdown.esm.min.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* quikdown - Lightweight Markdown Parser
|
|
3
|
-
* @version 1.2.
|
|
3
|
+
* @version 1.2.11
|
|
4
4
|
* @license BSD-2-Clause
|
|
5
5
|
* @copyright DeftIO 2025
|
|
6
6
|
*/
|
|
7
|
-
function e(e){if(e.length<3)return!1;for(let t=0;t<e.length;t++){const n=e[t];if("-"!==n){if(" "===n||"\t"===n){for(let n=t+1;n<e.length;n++)if(" "!==e[n]&&"\t"!==e[n])return!1;return t>=3}return!1}}return!0}const t="quikdown-",n="§CB",r={"&":"&","<":"<",">":">",'"':""","'":"'"},o={h1:"font-size:2em;font-weight:600;margin:.67em 0;text-align:left",h2:"font-size:1.5em;font-weight:600;margin:.83em 0",h3:"font-size:1.25em;font-weight:600;margin:1em 0",h4:"font-size:1em;font-weight:600;margin:1.33em 0",h5:"font-size:.875em;font-weight:600;margin:1.67em 0",h6:"font-size:.85em;font-weight:600;margin:2em 0",pre:"background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0",code:"background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace",blockquote:"border-left:4px solid #ddd;margin-left:0;padding-left:1em",table:"border-collapse:collapse;width:100%;margin:1em 0",th:"border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left",td:"border:1px solid #ddd;padding:8px;text-align:left",hr:"border:none;border-top:1px solid #ddd;margin:1em 0",img:"max-width:100%;height:auto",a:"color:#06c;text-decoration:underline",strong:"font-weight:bold",em:"font-style:italic",del:"text-decoration:line-through",ul:"margin:.5em 0;padding-left:2em",ol:"margin:.5em 0;padding-left:2em",li:"margin:.25em 0","task-item":"list-style:none","task-checkbox":"margin-right:.5em"};function l(l,a={}){if(!l||"string"!=typeof l)return"";const{fence_plugin:s,inline_styles:i=!1,bidirectional:p=!1,lazy_linefeeds:d=!1,allow_unsafe_html:g=!1}=a,f=function(e,n){return function(r,o=""){if(e){let e=n[r];return e||o?(o&&o.includes("text-align")&&e&&e.includes("text-align")&&(e=e.replace(/text-align:[^;]+;?/,"").trim(),e&&!e.endsWith(";")&&(e+=";")),` style="${o?e?`${e}${o}`:o:e}"`):""}{const e=` class="${t}${r}"`;return o?`${e} style="${o}"`:e}}}(i,o);function $(e){return e.replace(/[&<>"']/g,e=>r[e])}const u=p?e=>` data-qd="${$(e)}"`:()=>"";function h(e,t=!1){if(!e)return"";if(t)return e;const n=e.trim(),r=n.toLowerCase(),o=["javascript:","vbscript:","data:"];for(const e of o)if(r.startsWith(e))return"data:"===e&&r.startsWith("data:image/")?n:"#";return n}let m=l;const b=[],_=[];m=m.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm,(e,t,r,o)=>{const l=`${n}${b.length}§`,a=r?r.trim():"";return s&&s.render&&"function"==typeof s.render?b.push({lang:a,code:o.trimEnd(),custom:!0,fence:t,hasReverse:!!s.reverse}):b.push({lang:a,code:$(o.trimEnd()),custom:!1,fence:t}),l}),m=m.replace(/`([^`]+)`/g,(e,t)=>{const n=`§IC${_.length}§`;return _.push($(t)),n}),g||(m=$(m)),m=function(e,t){const n=e.split("\n"),r=[];let o=!1,l=[];for(let e=0;e<n.length;e++){const a=n[e].trim();if(a.includes("|")&&(a.startsWith("|")||/[^\\|]/.test(a)))o||(o=!0,l=[]),l.push(a);else{if(o){const e=c(l,t);e?r.push(e):r.push(...l),o=!1,l=[]}r.push(n[e])}}if(o&&l.length>0){const e=c(l,t);e?r.push(e):r.push(...l)}return r.join("\n")}(m,f),m=function(t,n,r){const o=t.split("\n"),l=[];let a=0;for(;a<o.length;){const t=o[a];let c=0;for(;c<t.length&&c<7&&"#"===t[c];)c++;if(c>=1&&c<=6&&" "===t[c]){const e=t.slice(c+1).replace(/\s*#+\s*$/,""),o="h"+c;l.push(`<${o}${n(o)}${r("#".repeat(c))}>${e}</${o}>`),a++;continue}e(t)?(l.push(`<hr${n("hr")}>`),a++):/^>\s+/.test(t)?(l.push(`<blockquote${n("blockquote")}>${t.replace(/^>\s+/,"")}</blockquote>`),a++):(l.push(t),a++)}let c=l.join("\n");return c=c.replace(/<\/blockquote>\n<blockquote>/g,"\n"),c}(m,f,u),m=function(e,n,r,o){const l=e.split("\n"),a=[],c=[],s=e=>e.replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[e])),i=o?e=>` data-qd="${s(e)}"`:()=>"";for(let e=0;e<l.length;e++){const o=l[e],s=o.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);if(s){const[,e,o,l]=s,p=Math.floor(e.length/2),d=/^\d+\./.test(o),g=d?"ol":"ul";let f=l,$="";const u=l.match(/^\[([x ])\]\s+(.*)$/i);if(u&&!d){const[,e,n]=u,o="x"===e.toLowerCase();f=`<input type="checkbox"${r?' style="margin-right:.5em"':` class="${t}task-checkbox"`}${o?" checked":""} disabled> ${n}`,$=r?' style="list-style:none"':` class="${t}task-item"`}for(;c.length>p+1;){const e=c.pop();a.push(`</${e.type}>`)}if(c.length===p)c.push({type:g,level:p}),a.push(`<${g}${n(g)}>`);else if(c.length===p+1){const e=c[c.length-1];e.type!==g&&(a.push(`</${e.type}>`),c.pop(),c.push({type:g,level:p}),a.push(`<${g}${n(g)}>`))}const h=$||n("li");a.push(`<li${h}${i(o)}>${f}</li>`)}else{for(;c.length>0;){const e=c.pop();a.push(`</${e.type}>`)}a.push(o)}}for(;c.length>0;){const e=c.pop();a.push(`</${e.type}>`)}return a.join("\n")}(m,f,i,p),m=m.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,t,n)=>{const r=h(n,a.allow_unsafe_urls),o=p&&t?` data-qd-alt="${$(t)}"`:"",l=p?` data-qd-src="${$(n)}"`:"";return`<img${f("img")} src="${r}" alt="${t}"${o}${l}${u("!")}>`}),m=m.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(e,t,n)=>{const r=h(n,a.allow_unsafe_urls),o=/^https?:\/\//i.test(r)?' rel="noopener noreferrer"':"",l=p?` data-qd-text="${$(t)}"`:"";return`<a${f("a")} href="${r}"${o}${l}${u("[")}>${t}</a>`}),m=m.replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(e,t,n)=>{const r=h(n,a.allow_unsafe_urls);return`${t}<a${f("a")} href="${r}" rel="noopener noreferrer">${n}</a>`});if([[/\*\*(.+?)\*\*/g,"strong","**"],[/__(.+?)__/g,"strong","__"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em","*"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em","_"],[/~~(.+?)~~/g,"del","~~"]].forEach(([e,t,n])=>{m=m.replace(e,`<${t}${f(t)}${u(n)}>$1</${t}>`)}),d){const e=[];let t=0;m=m.replace(/<(table|[uo]l)[^>]*>[\s\S]*?<\/\1>/g,n=>(e[t]=n,`§B${t++}§`)),m=m.replace(/\n\n+/g,"§P§").replace(/(<\/(?:h[1-6]|blockquote|pre)>)\n/g,"$1§N§").replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\n/g,"$1§N§").replace(/\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g,"§N§$1").replace(/\n(§B\d+§)/g,"§N§$1").replace(/(§B\d+§)\n/g,"$1§N§").replace(/\n/g,`<br${f("br")}>`).replace(/§N§/g,"\n").replace(/§P§/g,"</p><p>"),e.forEach((e,t)=>m=m.replace(`§B${t}§`,e)),m="<p>"+m+"</p>"}else m=m.replace(/ {2}$/gm,`<br${f("br")}>`),m=m.replace(/\n\n+/g,(e,t)=>m.substring(0,t).match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)?"<p>":"</p><p>"),m="<p>"+m+"</p>";return[[/<p><\/p>/g,""],[/<p>(<h[1-6][^>]*>)/g,"$1"],[/(<\/h[1-6]>)<\/p>/g,"$1"],[/<p>(<blockquote[^>]*>)/g,"$1"],[/(<\/blockquote>)<\/p>/g,"$1"],[/<p>(<ul[^>]*>|<ol[^>]*>)/g,"$1"],[/(<\/ul>|<\/ol>)<\/p>/g,"$1"],[/<p>(<hr[^>]*>)<\/p>/g,"$1"],[/<p>(<table[^>]*>)/g,"$1"],[/(<\/table>)<\/p>/g,"$1"],[/<p>(<pre[^>]*>)/g,"$1"],[/(<\/pre>)<\/p>/g,"$1"],[new RegExp(`<p>(${n}\\d+§)</p>`,"g"),"$1"]].forEach(([e,t])=>{m=m.replace(e,t)}),m=m.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g,"$1\n<p>$2"),b.forEach((e,t)=>{let r;if(e.custom&&s&&s.render)if(r=s.render(e.code,e.lang),void 0===r){const t=!i&&e.lang?` class="language-${e.lang}"`:"",n=i?f("code"):t,o=p&&e.lang?` data-qd-lang="${$(e.lang)}"`:"",l=p?` data-qd-fence="${$(e.fence)}"`:"";r=`<pre${f("pre")}${l}${o}><code${n}>${$(e.code)}</code></pre>`}else p&&(r=r.replace(/^<(\w+)/,`<$1 data-qd-fence="${$(e.fence)}" data-qd-lang="${$(e.lang)}" data-qd-source="${$(e.code)}"`));else{const t=!i&&e.lang?` class="language-${e.lang}"`:"",n=i?f("code"):t,o=p&&e.lang?` data-qd-lang="${$(e.lang)}"`:"",l=p?` data-qd-fence="${$(e.fence)}"`:"";r=`<pre${f("pre")}${l}${o}><code${n}>${e.code}</code></pre>`}const o=`${n}${t}§`;m=m.replace(o,r)}),_.forEach((e,t)=>{const n=`§IC${t}§`;m=m.replace(n,`<code${f("code")}${u("`")}>${e}</code>`)}),m.trim()}function a(e,t){return[[/\*\*(.+?)\*\*/g,"strong"],[/__(.+?)__/g,"strong"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em"],[/~~(.+?)~~/g,"del"],[/`([^`]+)`/g,"code"]].forEach(([n,r])=>{e=e.replace(n,`<${r}${t(r)}>$1</${r}>`)}),e}function c(e,t){if(e.length<2)return null;let n=-1;for(let t=1;t<e.length;t++)if(/^\|?[\s\-:|]+\|?$/.test(e[t])&&e[t].includes("-")){n=t;break}if(-1===n)return null;const r=e.slice(0,n),o=e.slice(n+1),l=e[n].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"});let c=`<table${t("table")}>\n`;return c+=`<thead${t("thead")}>\n`,r.forEach(e=>{c+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const r=l[n]&&"left"!==l[n]?`text-align:${l[n]}`:"",o=a(e.trim(),t);c+=`<th${t("th",r)}>${o}</th>\n`}),c+="</tr>\n"}),c+="</thead>\n",o.length>0&&(c+=`<tbody${t("tbody")}>\n`,o.forEach(e=>{c+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const r=l[n]&&"left"!==l[n]?`text-align:${l[n]}`:"",o=a(e.trim(),t);c+=`<td${t("td",r)}>${o}</td>\n`}),c+="</tr>\n"}),c+="</tbody>\n"),c+="</table>",c}l.emitStyles=function(e="quikdown-",t="light"){const n=o,r={"#f4f4f4":"#2a2a2a","#f0f0f0":"#2a2a2a","#f2f2f2":"#2a2a2a","#ddd":"#3a3a3a","#06c":"#6db3f2",_textColor:"#e0e0e0"},l={_textColor:"#333"};let a="";for(const[o,c]of Object.entries(n)){let n=c;if("dark"===t&&r){for(const[e,t]of Object.entries(r))e.startsWith("_")||(n=n.replaceAll(e,t));["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(o)&&(n+=`;color:${r._textColor}`)}else if("light"===t&&l){["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(o)&&(n+=`;color:${l._textColor}`)}a+=`.${e}${o} { ${n} }\n`}return a},l.configure=function(e){return function(t){return l(t,e)}},l.version="1.2.9","undefined"!=typeof module&&module.exports&&(module.exports=l),"undefined"!=typeof window&&(window.quikdown=l);export{l as default};
|
|
7
|
+
function e(e){if(e.length<3)return!1;for(let t=0;t<e.length;t++){const n=e[t];if("-"!==n){if(" "===n||"\t"===n){for(let n=t+1;n<e.length;n++)if(" "!==e[n]&&"\t"!==e[n])return!1;return t>=3}return!1}}return!0}const t="quikdown-",n="§CB",r="§HT",o={href:1,src:1,action:1,formaction:1},l={"&":"&","<":"<",">":">",'"':""","'":"'"},a={h1:"font-size:2em;font-weight:600;margin:.67em 0;text-align:left",h2:"font-size:1.5em;font-weight:600;margin:.83em 0",h3:"font-size:1.25em;font-weight:600;margin:1em 0",h4:"font-size:1em;font-weight:600;margin:1.33em 0",h5:"font-size:.875em;font-weight:600;margin:1.67em 0",h6:"font-size:.85em;font-weight:600;margin:2em 0",pre:"background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0",code:"background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace",blockquote:"border-left:4px solid #ddd;margin-left:0;padding-left:1em",table:"border-collapse:collapse;width:100%;margin:1em 0",th:"border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left",td:"border:1px solid #ddd;padding:8px;text-align:left",hr:"border:none;border-top:1px solid #ddd;margin:1em 0",img:"max-width:100%;height:auto",a:"color:#06c;text-decoration:underline",strong:"font-weight:bold",em:"font-style:italic",del:"text-decoration:line-through",ul:"margin:.5em 0;padding-left:2em",ol:"margin:.5em 0;padding-left:2em",li:"margin:.25em 0","task-item":"list-style:none","task-checkbox":"margin-right:.5em"};function c(c,s={}){if(!c||"string"!=typeof c)return"";const{fence_plugin:p,inline_styles:g=!1,bidirectional:d=!1,lazy_linefeeds:f=!1,allow_unsafe_html:u=!1}=s,$=function(e,n){return function(r,o=""){if(e){let e=n[r];return e||o?(o&&o.includes("text-align")&&e&&e.includes("text-align")&&(e=e.replace(/text-align:[^;]+;?/,"").trim(),e&&!e.endsWith(";")&&(e+=";")),` style="${o?e?`${e}${o}`:o:e}"`):""}{const e=` class="${t}${r}"`;return o?`${e} style="${o}"`:e}}}(g,a);function h(e){return e.replace(/[&<>"']/g,e=>l[e])}const m=d?e=>` data-qd="${h(e)}"`:()=>"";function b(e,t=!1){if(!e)return"";if(t)return e;const n=e.trim(),r=n.toLowerCase(),o=["javascript:","vbscript:","data:"];for(const e of o)if(r.startsWith(e))return"data:"===e&&r.startsWith("data:image/")?n:"#";return n}let _=c;const x=[],q=[];_=_.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm,(e,t,r,o)=>{const l=`${n}${x.length}§`,a=r?r.trim():"";return p&&p.render&&"function"==typeof p.render?x.push({lang:a,code:o.trimEnd(),custom:!0,fence:t,hasReverse:!!p.reverse}):x.push({lang:a,code:h(o.trimEnd()),custom:!1,fence:t}),l}),_=_.replace(/`([^`]+)`/g,(e,t)=>{const n=`§IC${q.length}§`;return q.push(h(t)),n});const k=[],w=Array.isArray(u)?Object.fromEntries(u.map(e=>[e,1])):u&&"object"==typeof u?u:null;w&&(_=_.replace(/<!--[\s\S]*?-->/g,e=>{const t=k.length;return k.push(e),`${r}${t}§`}),_=_.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*\/?>/g,(e,t)=>{if(t.toLowerCase()in w){const t=function(e){if(!/\s/.test(e.replace(/<\/?[a-zA-Z][a-zA-Z0-9]*/,"").replace(/\/?>$/,"")))return e;const t=e.match(/^(<\/?[a-zA-Z][a-zA-Z0-9]*)([\s\S]*?)(\/?>)$/);if(!t)return e;const[,n,r,l]=t,a=/([a-zA-Z_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g,c=[];let s;for(;null!==(s=a.exec(r));){const e=s[1],t=void 0!==s[2]?s[2]:void 0!==s[3]?s[3]:s[4];if(!/^on/i.test(e))if(void 0===t)c.push(e);else{let n=t;e.toLowerCase()in o&&(n=b(t)),c.push(`${e}="${n}"`)}}return n+(c.length?" "+c.join(" "):"")+l}(e),n=k.length;return k.push(t),`${r}${n}§`}return e})),!0!==u&&(_=h(_)),w&&k.forEach((e,t)=>{_=_.replace(`${r}${t}§`,e)}),_=function(e,t){const n=e.split("\n"),r=[];let o=!1,l=[];for(let e=0;e<n.length;e++){const a=n[e].trim();if(a.includes("|")&&(a.startsWith("|")||/[^\\|]/.test(a)))o||(o=!0,l=[]),l.push(a);else{if(o){const e=i(l,t);e?r.push(e):r.push(...l),o=!1,l=[]}r.push(n[e])}}if(o&&l.length>0){const e=i(l,t);e?r.push(e):r.push(...l)}return r.join("\n")}(_,$),_=function(t,n,r){const o=t.split("\n"),l=[];let a=0;for(;a<o.length;){const t=o[a];if(/^\[\/\/\]: #/.test(t)){a++;continue}let c=0;for(;c<t.length&&c<7&&"#"===t[c];)c++;if(c>=1&&c<=6&&" "===t[c]){const e=t.slice(c+1).replace(/\s*#+\s*$/,""),o="h"+c;l.push(`<${o}${n(o)}${r("#".repeat(c))}>${e}</${o}>`),a++;continue}e(t)?(l.push(`<hr${n("hr")}>`),a++):/^>\s+/.test(t)?(l.push(`<blockquote${n("blockquote")}>${t.replace(/^>\s+/,"")}</blockquote>`),a++):(l.push(t),a++)}let c=l.join("\n");return c=c.replace(/<\/blockquote>\n<blockquote>/g,"\n"),c}(_,$,m),_=function(e,n,r,o){const l=e.split("\n"),a=[],c=[],s=e=>e.replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[e])),i=o?e=>` data-qd="${s(e)}"`:()=>"";for(let e=0;e<l.length;e++){const o=l[e],s=o.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);if(s){const[,e,o,l]=s,p=Math.floor(e.length/2),g=/^\d+\./.test(o),d=g?"ol":"ul";let f=l,u="";const $=l.match(/^\[([x ])\]\s+(.*)$/i);if($&&!g){const[,e,n]=$,o="x"===e.toLowerCase();f=`<input type="checkbox"${r?' style="margin-right:.5em"':` class="${t}task-checkbox"`}${o?" checked":""} disabled> ${n}`,u=r?' style="list-style:none"':` class="${t}task-item"`}for(;c.length>p+1;){const e=c.pop();a.push(`</${e.type}>`)}if(c.length===p)c.push({type:d,level:p}),a.push(`<${d}${n(d)}>`);else if(c.length===p+1){const e=c[c.length-1];e.type!==d&&(a.push(`</${e.type}>`),c.pop(),c.push({type:d,level:p}),a.push(`<${d}${n(d)}>`))}const h=u||n("li");a.push(`<li${h}${i(o)}>${f}</li>`)}else{for(;c.length>0;){const e=c.pop();a.push(`</${e.type}>`)}a.push(o)}}for(;c.length>0;){const e=c.pop();a.push(`</${e.type}>`)}return a.join("\n")}(_,$,g,d),_=_.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(e,t,n)=>{const r=b(n,s.allow_unsafe_urls),o=d&&t?` data-qd-alt="${h(t)}"`:"",l=d?` data-qd-src="${h(n)}"`:"";return`<img${$("img")} src="${r}" alt="${t}"${o}${l}${m("!")}>`}),_=_.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(e,t,n)=>{const r=b(n,s.allow_unsafe_urls),o=/^https?:\/\//i.test(r)?' rel="noopener noreferrer"':"",l=d?` data-qd-text="${h(t)}"`:"";return`<a${$("a")} href="${r}"${o}${l}${m("[")}>${t}</a>`}),_=_.replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(e,t,n)=>{const r=b(n,s.allow_unsafe_urls);return`${t}<a${$("a")} href="${r}" rel="noopener noreferrer">${n}</a>`});const y=[];_=_.replace(/<[^>]+>/g,e=>(y.push(e),`%%T${y.length-1}%%`));if([[/\*\*(.+?)\*\*/g,"strong","**"],[/__(.+?)__/g,"strong","__"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em","*"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em","_"],[/~~(.+?)~~/g,"del","~~"]].forEach(([e,t,n])=>{_=_.replace(e,`<${t}${$(t)}${m(n)}>$1</${t}>`)}),_=_.replace(/%%T(\d+)%%/g,(e,t)=>y[t]),f){const e=[];let t=0;_=_.replace(/<(table|[uo]l)[^>]*>[\s\S]*?<\/\1>/g,n=>(e[t]=n,`§B${t++}§`)),_=_.replace(/\n\n+/g,"§P§").replace(/(<\/(?:h[1-6]|blockquote|pre)>)\n/g,"$1§N§").replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\n/g,"$1§N§").replace(/\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g,"§N§$1").replace(/\n(§B\d+§)/g,"§N§$1").replace(/(§B\d+§)\n/g,"$1§N§").replace(/\n/g,`<br${$("br")}>`).replace(/§N§/g,"\n").replace(/§P§/g,"</p><p>"),e.forEach((e,t)=>_=_.replace(`§B${t}§`,e)),_="<p>"+_+"</p>"}else _=_.replace(/ {2}$/gm,`<br${$("br")}>`),_=_.replace(/\n\n+/g,(e,t)=>_.substring(0,t).match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)?"<p>":"</p><p>"),_="<p>"+_+"</p>";return[[/<p><\/p>/g,""],[/<p>(<h[1-6][^>]*>)/g,"$1"],[/(<\/h[1-6]>)<\/p>/g,"$1"],[/<p>(<blockquote[^>]*>)/g,"$1"],[/(<\/blockquote>)<\/p>/g,"$1"],[/<p>(<ul[^>]*>|<ol[^>]*>)/g,"$1"],[/(<\/ul>|<\/ol>)<\/p>/g,"$1"],[/<p>(<hr[^>]*>)<\/p>/g,"$1"],[/<p>(<table[^>]*>)/g,"$1"],[/(<\/table>)<\/p>/g,"$1"],[/<p>(<pre[^>]*>)/g,"$1"],[/(<\/pre>)<\/p>/g,"$1"],[new RegExp(`<p>(${n}\\d+§)</p>`,"g"),"$1"]].forEach(([e,t])=>{_=_.replace(e,t)}),_=_.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g,"$1\n<p>$2"),x.forEach((e,t)=>{let r;if(e.custom&&p&&p.render)if(r=p.render(e.code,e.lang),void 0===r){const t=!g&&e.lang?` class="language-${e.lang}"`:"",n=g?$("code"):t,o=d&&e.lang?` data-qd-lang="${h(e.lang)}"`:"",l=d?` data-qd-fence="${h(e.fence)}"`:"";r=`<pre${$("pre")}${l}${o}><code${n}>${h(e.code)}</code></pre>`}else d&&(r=r.replace(/^<(\w+)/,`<$1 data-qd-fence="${h(e.fence)}" data-qd-lang="${h(e.lang)}" data-qd-source="${h(e.code)}"`));else{const t=!g&&e.lang?` class="language-${e.lang}"`:"",n=g?$("code"):t,o=d&&e.lang?` data-qd-lang="${h(e.lang)}"`:"",l=d?` data-qd-fence="${h(e.fence)}"`:"";r=`<pre${$("pre")}${l}${o}><code${n}>${e.code}</code></pre>`}const o=`${n}${t}§`;_=_.replace(o,r)}),q.forEach((e,t)=>{const n=`§IC${t}§`;_=_.replace(n,`<code${$("code")}${m("`")}>${e}</code>`)}),_.trim()}function s(e,t){return[[/\*\*(.+?)\*\*/g,"strong"],[/__(.+?)__/g,"strong"],[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,"em"],[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g,"em"],[/~~(.+?)~~/g,"del"],[/`([^`]+)`/g,"code"]].forEach(([n,r])=>{e=e.replace(n,`<${r}${t(r)}>$1</${r}>`)}),e}function i(e,t){if(e.length<2)return null;let n=-1;for(let t=1;t<e.length;t++)if(/^\|?[\s\-:|]+\|?$/.test(e[t])&&e[t].includes("-")){n=t;break}if(-1===n)return null;const r=e.slice(0,n),o=e.slice(n+1),l=e[n].trim().replace(/^\|/,"").replace(/\|$/,"").split("|").map(e=>{const t=e.trim();return t.startsWith(":")&&t.endsWith(":")?"center":t.endsWith(":")?"right":"left"});let a=`<table${t("table")}>\n`;return a+=`<thead${t("thead")}>\n`,r.forEach(e=>{a+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const r=l[n]&&"left"!==l[n]?`text-align:${l[n]}`:"",o=s(e.trim(),t);a+=`<th${t("th",r)}>${o}</th>\n`}),a+="</tr>\n"}),a+="</thead>\n",o.length>0&&(a+=`<tbody${t("tbody")}>\n`,o.forEach(e=>{a+=`<tr${t("tr")}>\n`;e.trim().replace(/^\|/,"").replace(/\|$/,"").split("|").forEach((e,n)=>{const r=l[n]&&"left"!==l[n]?`text-align:${l[n]}`:"",o=s(e.trim(),t);a+=`<td${t("td",r)}>${o}</td>\n`}),a+="</tr>\n"}),a+="</tbody>\n"),a+="</table>",a}c.emitStyles=function(e="quikdown-",t="light"){const n=a,r={"#f4f4f4":"#2a2a2a","#f0f0f0":"#2a2a2a","#f2f2f2":"#2a2a2a","#ddd":"#3a3a3a","#06c":"#6db3f2",_textColor:"#e0e0e0"},o={_textColor:"#333"};let l="";for(const[a,c]of Object.entries(n)){let n=c;if("dark"===t&&r){for(const[e,t]of Object.entries(r))e.startsWith("_")||(n=n.replaceAll(e,t));["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(a)&&(n+=`;color:${r._textColor}`)}else if("light"===t&&o){["h1","h2","h3","h4","h5","h6","td","li","blockquote"].includes(a)&&(n+=`;color:${o._textColor}`)}l+=`.${e}${a} { ${n} }\n`}return l},c.configure=function(e){return function(t){return c(t,e)}},c.version="1.2.11","undefined"!=typeof module&&module.exports&&(module.exports=c),"undefined"!=typeof window&&(window.quikdown=c);export{c as default};
|
|
8
8
|
//# sourceMappingURL=quikdown.esm.min.js.map
|
|
Binary file
|