quikdown 1.0.4 → 1.1.0
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 +124 -499
- package/dist/quikdown.cjs +130 -48
- package/dist/quikdown.d.ts +40 -5
- package/dist/quikdown.dark.css +1 -1
- package/dist/quikdown.esm.js +130 -48
- package/dist/quikdown.esm.min.js +2 -2
- package/dist/quikdown.esm.min.js.map +1 -1
- package/dist/quikdown.light.css +1 -1
- package/dist/quikdown.umd.js +130 -48
- package/dist/quikdown.umd.min.js +2 -2
- package/dist/quikdown.umd.min.js.map +1 -1
- package/dist/quikdown_bd.cjs +652 -260
- package/dist/quikdown_bd.d.ts +53 -19
- package/dist/quikdown_bd.esm.js +652 -260
- package/dist/quikdown_bd.esm.min.js +2 -2
- package/dist/quikdown_bd.esm.min.js.map +1 -1
- package/dist/quikdown_bd.umd.js +652 -260
- package/dist/quikdown_bd.umd.min.js +2 -2
- package/dist/quikdown_bd.umd.min.js.map +1 -1
- package/dist/quikdown_edit.cjs +2474 -0
- package/dist/quikdown_edit.d.ts +195 -0
- package/dist/quikdown_edit.esm.js +2472 -0
- package/dist/quikdown_edit.esm.min.js +14 -0
- package/dist/quikdown_edit.esm.min.js.map +1 -0
- package/dist/quikdown_edit.umd.js +2480 -0
- package/dist/quikdown_edit.umd.min.js +14 -0
- package/dist/quikdown_edit.umd.min.js.map +1 -0
- package/package.json +71 -29
- package/dist/quikdown-lex.cjs +0 -810
- package/dist/quikdown-lex.esm.js +0 -808
- package/dist/quikdown-lex.esm.min.js +0 -8
- package/dist/quikdown-lex.esm.min.js.map +0 -1
- package/dist/quikdown-lex.umd.js +0 -816
- package/dist/quikdown-lex.umd.min.js +0 -8
- package/dist/quikdown-lex.umd.min.js.map +0 -1
package/dist/quikdown.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* quikdown - Lightweight Markdown Parser
|
|
3
|
-
* @version 1.0
|
|
3
|
+
* @version 1.1.0
|
|
4
4
|
* @license BSD-2-Clause
|
|
5
5
|
* @copyright DeftIO 2025
|
|
6
6
|
*/
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
* @param {Function} options.fence_plugin - Custom renderer for fenced code blocks
|
|
15
15
|
* (content, fence_string) => html string
|
|
16
16
|
* @param {boolean} options.inline_styles - If true, uses inline styles instead of classes
|
|
17
|
+
* @param {boolean} options.bidirectional - If true, adds data-qd attributes for source tracking
|
|
18
|
+
* @param {boolean} options.lazy_linefeeds - If true, single newlines become <br> tags
|
|
17
19
|
* @returns {string} - The rendered HTML
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
// Version will be injected at build time
|
|
21
|
-
const quikdownVersion = '1.0
|
|
23
|
+
const quikdownVersion = '1.1.0';
|
|
22
24
|
|
|
23
25
|
// Constants for reuse
|
|
24
26
|
const CLASS_PREFIX = 'quikdown-';
|
|
@@ -60,12 +62,25 @@ const QUIKDOWN_STYLES = {
|
|
|
60
62
|
function createGetAttr(inline_styles, styles) {
|
|
61
63
|
return function(tag, additionalStyle = '') {
|
|
62
64
|
if (inline_styles) {
|
|
63
|
-
|
|
65
|
+
let style = styles[tag];
|
|
64
66
|
if (!style && !additionalStyle) return '';
|
|
65
|
-
|
|
67
|
+
|
|
68
|
+
// Remove default text-align if we're adding a different alignment
|
|
69
|
+
if (additionalStyle && additionalStyle.includes('text-align') && style && style.includes('text-align')) {
|
|
70
|
+
style = style.replace(/text-align:[^;]+;?/, '').trim();
|
|
71
|
+
if (style && !style.endsWith(';')) style += ';';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* istanbul ignore next - defensive: additionalStyle without style doesn't occur with current tags */
|
|
75
|
+
const fullStyle = additionalStyle ? (style ? `${style}${additionalStyle}` : additionalStyle) : style;
|
|
66
76
|
return ` style="${fullStyle}"`;
|
|
67
77
|
} else {
|
|
68
|
-
|
|
78
|
+
const classAttr = ` class="${CLASS_PREFIX}${tag}"`;
|
|
79
|
+
// Apply inline styles for alignment even when using CSS classes
|
|
80
|
+
if (additionalStyle) {
|
|
81
|
+
return `${classAttr} style="${additionalStyle}"`;
|
|
82
|
+
}
|
|
83
|
+
return classAttr;
|
|
69
84
|
}
|
|
70
85
|
};
|
|
71
86
|
}
|
|
@@ -75,7 +90,7 @@ function quikdown(markdown, options = {}) {
|
|
|
75
90
|
return '';
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
const { fence_plugin, inline_styles = false } = options;
|
|
93
|
+
const { fence_plugin, inline_styles = false, bidirectional = false, lazy_linefeeds = false } = options;
|
|
79
94
|
const styles = QUIKDOWN_STYLES; // Use module-level styles
|
|
80
95
|
const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once
|
|
81
96
|
|
|
@@ -84,8 +99,12 @@ function quikdown(markdown, options = {}) {
|
|
|
84
99
|
return text.replace(/[&<>"']/g, m => ESC_MAP[m]);
|
|
85
100
|
}
|
|
86
101
|
|
|
102
|
+
// Helper to add data-qd attributes for bidirectional support
|
|
103
|
+
const dataQd = bidirectional ? (marker) => ` data-qd="${escapeHtml(marker)}"` : () => '';
|
|
104
|
+
|
|
87
105
|
// Sanitize URLs to prevent XSS attacks
|
|
88
106
|
function sanitizeUrl(url, allowUnsafe = false) {
|
|
107
|
+
/* istanbul ignore next - defensive programming, regex ensures url is never empty */
|
|
89
108
|
if (!url) return '';
|
|
90
109
|
|
|
91
110
|
// If unsafe URLs are explicitly allowed, return as-is
|
|
@@ -127,18 +146,21 @@ function quikdown(markdown, options = {}) {
|
|
|
127
146
|
// Trim the language specification
|
|
128
147
|
const langTrimmed = lang ? lang.trim() : '';
|
|
129
148
|
|
|
130
|
-
// If custom fence plugin is provided, use it
|
|
131
|
-
if (fence_plugin && typeof fence_plugin === 'function') {
|
|
149
|
+
// If custom fence plugin is provided, use it (v1.1.0: object format required)
|
|
150
|
+
if (fence_plugin && fence_plugin.render && typeof fence_plugin.render === 'function') {
|
|
132
151
|
codeBlocks.push({
|
|
133
152
|
lang: langTrimmed,
|
|
134
153
|
code: code.trimEnd(),
|
|
135
|
-
custom: true
|
|
154
|
+
custom: true,
|
|
155
|
+
fence: fence,
|
|
156
|
+
hasReverse: !!fence_plugin.reverse
|
|
136
157
|
});
|
|
137
158
|
} else {
|
|
138
159
|
codeBlocks.push({
|
|
139
160
|
lang: langTrimmed,
|
|
140
161
|
code: escapeHtml(code.trimEnd()),
|
|
141
|
-
custom: false
|
|
162
|
+
custom: false,
|
|
163
|
+
fence: fence
|
|
142
164
|
});
|
|
143
165
|
}
|
|
144
166
|
return placeholder;
|
|
@@ -162,7 +184,7 @@ function quikdown(markdown, options = {}) {
|
|
|
162
184
|
// Process headings (supports optional trailing #'s)
|
|
163
185
|
html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
|
|
164
186
|
const level = hashes.length;
|
|
165
|
-
return `<h${level}${getAttr('h' + level)}>${content}</h${level}>`;
|
|
187
|
+
return `<h${level}${getAttr('h' + level)}${dataQd(hashes)}>${content}</h${level}>`;
|
|
166
188
|
});
|
|
167
189
|
|
|
168
190
|
// Process blockquotes (must handle escaped > since we already escaped HTML)
|
|
@@ -170,18 +192,20 @@ function quikdown(markdown, options = {}) {
|
|
|
170
192
|
// Merge consecutive blockquotes
|
|
171
193
|
html = html.replace(/<\/blockquote>\n<blockquote>/g, '\n');
|
|
172
194
|
|
|
173
|
-
// Process horizontal rules
|
|
174
|
-
html = html.replace(
|
|
195
|
+
// Process horizontal rules (allow trailing spaces)
|
|
196
|
+
html = html.replace(/^---+\s*$/gm, `<hr${getAttr('hr')}>`);
|
|
175
197
|
|
|
176
198
|
// Process lists
|
|
177
|
-
html = processLists(html, getAttr, inline_styles);
|
|
199
|
+
html = processLists(html, getAttr, inline_styles, bidirectional);
|
|
178
200
|
|
|
179
201
|
// Phase 3: Process inline elements
|
|
180
202
|
|
|
181
203
|
// Images (must come before links, with URL sanitization)
|
|
182
204
|
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
|
|
183
205
|
const sanitizedSrc = sanitizeUrl(src, options.allow_unsafe_urls);
|
|
184
|
-
|
|
206
|
+
const altAttr = bidirectional && alt ? ` data-qd-alt="${escapeHtml(alt)}"` : '';
|
|
207
|
+
const srcAttr = bidirectional ? ` data-qd-src="${escapeHtml(src)}"` : '';
|
|
208
|
+
return `<img${getAttr('img')} src="${sanitizedSrc}" alt="${alt}"${altAttr}${srcAttr}${dataQd('!')}>`;
|
|
185
209
|
});
|
|
186
210
|
|
|
187
211
|
// Links (with URL sanitization)
|
|
@@ -190,7 +214,8 @@ function quikdown(markdown, options = {}) {
|
|
|
190
214
|
const sanitizedHref = sanitizeUrl(href, options.allow_unsafe_urls);
|
|
191
215
|
const isExternal = /^https?:\/\//i.test(sanitizedHref);
|
|
192
216
|
const rel = isExternal ? ' rel="noopener noreferrer"' : '';
|
|
193
|
-
|
|
217
|
+
const textAttr = bidirectional ? ` data-qd-text="${escapeHtml(text)}"` : '';
|
|
218
|
+
return `<a${getAttr('a')} href="${sanitizedHref}"${rel}${textAttr}${dataQd('[')}>${text}</a>`;
|
|
194
219
|
});
|
|
195
220
|
|
|
196
221
|
// Autolinks - convert bare URLs to clickable links
|
|
@@ -201,23 +226,64 @@ function quikdown(markdown, options = {}) {
|
|
|
201
226
|
|
|
202
227
|
// Process inline formatting (bold, italic, strikethrough)
|
|
203
228
|
const inlinePatterns = [
|
|
204
|
-
[/\*\*(.+?)\*\*/g, 'strong'],
|
|
205
|
-
[/__(.+?)__/g, 'strong'],
|
|
206
|
-
[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
|
|
207
|
-
[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
|
|
208
|
-
[/~~(.+?)~~/g, 'del']
|
|
229
|
+
[/\*\*(.+?)\*\*/g, 'strong', '**'],
|
|
230
|
+
[/__(.+?)__/g, 'strong', '__'],
|
|
231
|
+
[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em', '*'],
|
|
232
|
+
[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em', '_'],
|
|
233
|
+
[/~~(.+?)~~/g, 'del', '~~']
|
|
209
234
|
];
|
|
210
235
|
|
|
211
|
-
inlinePatterns.forEach(([pattern, tag]) => {
|
|
212
|
-
html = html.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
|
|
236
|
+
inlinePatterns.forEach(([pattern, tag, marker]) => {
|
|
237
|
+
html = html.replace(pattern, `<${tag}${getAttr(tag)}${dataQd(marker)}>$1</${tag}>`);
|
|
213
238
|
});
|
|
214
239
|
|
|
215
|
-
// Line breaks
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
// Line breaks
|
|
241
|
+
if (lazy_linefeeds) {
|
|
242
|
+
// Lazy linefeeds: single newline becomes <br> (except between paragraphs and after/before block elements)
|
|
243
|
+
const blocks = [];
|
|
244
|
+
let bi = 0;
|
|
245
|
+
|
|
246
|
+
// Protect tables and lists
|
|
247
|
+
html = html.replace(/<(table|[uo]l)[^>]*>[\s\S]*?<\/\1>/g, m => {
|
|
248
|
+
blocks[bi] = m;
|
|
249
|
+
return `§B${bi++}§`;
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Handle paragraphs and block elements
|
|
253
|
+
html = html.replace(/\n\n+/g, '§P§')
|
|
254
|
+
// After block elements
|
|
255
|
+
.replace(/(<\/(?:h[1-6]|blockquote|pre)>)\n/g, '$1§N§')
|
|
256
|
+
.replace(/(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)\n/g, '$1§N§')
|
|
257
|
+
// Before block elements
|
|
258
|
+
.replace(/\n(<(?:h[1-6]|blockquote|pre|hr)[^>]*>)/g, '§N§$1')
|
|
259
|
+
.replace(/\n(§B\d+§)/g, '§N§$1')
|
|
260
|
+
.replace(/(§B\d+§)\n/g, '$1§N§')
|
|
261
|
+
// Convert remaining newlines
|
|
262
|
+
.replace(/\n/g, `<br${getAttr('br')}>`)
|
|
263
|
+
// Restore
|
|
264
|
+
.replace(/§N§/g, '\n')
|
|
265
|
+
.replace(/§P§/g, '</p><p>');
|
|
266
|
+
|
|
267
|
+
// Restore protected blocks
|
|
268
|
+
blocks.forEach((b, i) => html = html.replace(`§B${i}§`, b));
|
|
269
|
+
|
|
270
|
+
html = '<p>' + html + '</p>';
|
|
271
|
+
} else {
|
|
272
|
+
// Standard: two spaces at end of line for line breaks
|
|
273
|
+
html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
|
|
274
|
+
|
|
275
|
+
// Paragraphs (double newlines)
|
|
276
|
+
// Don't add </p> after block elements (they're not in paragraphs)
|
|
277
|
+
html = html.replace(/\n\n+/g, (match, offset) => {
|
|
278
|
+
// Check if we're after a block element closing tag
|
|
279
|
+
const before = html.substring(0, offset);
|
|
280
|
+
if (before.match(/<\/(h[1-6]|blockquote|ul|ol|table|pre|hr)>$/)) {
|
|
281
|
+
return '<p>'; // Just open a new paragraph
|
|
282
|
+
}
|
|
283
|
+
return '</p><p>'; // Normal paragraph break
|
|
284
|
+
});
|
|
285
|
+
html = '<p>' + html + '</p>';
|
|
286
|
+
}
|
|
221
287
|
|
|
222
288
|
// Clean up empty paragraphs and unwrap block elements
|
|
223
289
|
const cleanupPatterns = [
|
|
@@ -240,26 +306,39 @@ function quikdown(markdown, options = {}) {
|
|
|
240
306
|
html = html.replace(pattern, replacement);
|
|
241
307
|
});
|
|
242
308
|
|
|
309
|
+
// Fix orphaned closing </p> tags after block elements
|
|
310
|
+
// When a paragraph follows a block element, ensure it has opening <p>
|
|
311
|
+
html = html.replace(/(<\/(?:h[1-6]|blockquote|ul|ol|table|pre|hr)>)\n([^<])/g, '$1\n<p>$2');
|
|
312
|
+
|
|
243
313
|
// Phase 4: Restore code blocks and inline code
|
|
244
314
|
|
|
245
315
|
// Restore code blocks
|
|
246
316
|
codeBlocks.forEach((block, i) => {
|
|
247
317
|
let replacement;
|
|
248
318
|
|
|
249
|
-
if (block.custom && fence_plugin) {
|
|
250
|
-
// Use custom fence plugin
|
|
251
|
-
replacement = fence_plugin(block.code, block.lang);
|
|
319
|
+
if (block.custom && fence_plugin && fence_plugin.render) {
|
|
320
|
+
// Use custom fence plugin (v1.1.0: object format with render function)
|
|
321
|
+
replacement = fence_plugin.render(block.code, block.lang);
|
|
322
|
+
|
|
252
323
|
// If plugin returns undefined, fall back to default rendering
|
|
253
324
|
if (replacement === undefined) {
|
|
254
325
|
const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
|
|
255
326
|
const codeAttr = inline_styles ? getAttr('code') : langClass;
|
|
256
|
-
|
|
327
|
+
const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
|
|
328
|
+
const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
|
|
329
|
+
replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${escapeHtml(block.code)}</code></pre>`;
|
|
330
|
+
} else if (bidirectional) {
|
|
331
|
+
// If bidirectional and plugin provided HTML, add data attributes for roundtrip
|
|
332
|
+
replacement = replacement.replace(/^<(\w+)/,
|
|
333
|
+
`<$1 data-qd-fence="${escapeHtml(block.fence)}" data-qd-lang="${escapeHtml(block.lang)}" data-qd-source="${escapeHtml(block.code)}"`);
|
|
257
334
|
}
|
|
258
335
|
} else {
|
|
259
336
|
// Default rendering
|
|
260
337
|
const langClass = !inline_styles && block.lang ? ` class="language-${block.lang}"` : '';
|
|
261
338
|
const codeAttr = inline_styles ? getAttr('code') : langClass;
|
|
262
|
-
|
|
339
|
+
const langAttr = bidirectional && block.lang ? ` data-qd-lang="${escapeHtml(block.lang)}"` : '';
|
|
340
|
+
const fenceAttr = bidirectional ? ` data-qd-fence="${escapeHtml(block.fence)}"` : '';
|
|
341
|
+
replacement = `<pre${getAttr('pre')}${fenceAttr}${langAttr}><code${codeAttr}>${block.code}</code></pre>`;
|
|
263
342
|
}
|
|
264
343
|
|
|
265
344
|
const placeholder = `${PLACEHOLDER_CB}${i}§`;
|
|
@@ -269,7 +348,7 @@ function quikdown(markdown, options = {}) {
|
|
|
269
348
|
// Restore inline code
|
|
270
349
|
inlineCodes.forEach((code, i) => {
|
|
271
350
|
const placeholder = `${PLACEHOLDER_IC}${i}§`;
|
|
272
|
-
html = html.replace(placeholder, `<code${getAttr('code')}>${code}</code>`);
|
|
351
|
+
html = html.replace(placeholder, `<code${getAttr('code')}${dataQd('`')}>${code}</code>`);
|
|
273
352
|
});
|
|
274
353
|
|
|
275
354
|
return html.trim();
|
|
@@ -383,9 +462,9 @@ function buildTable(lines, getAttr) {
|
|
|
383
462
|
let html = `<table${getAttr('table')}>\n`;
|
|
384
463
|
|
|
385
464
|
// Build header
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
465
|
+
// Note: headerLines will always have length > 0 since separatorIndex starts from 1
|
|
466
|
+
html += `<thead${getAttr('thead')}>\n`;
|
|
467
|
+
headerLines.forEach(line => {
|
|
389
468
|
html += `<tr${getAttr('tr')}>\n`;
|
|
390
469
|
// Handle pipes at start/end or not
|
|
391
470
|
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
|
|
@@ -395,9 +474,8 @@ function buildTable(lines, getAttr) {
|
|
|
395
474
|
html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\n`;
|
|
396
475
|
});
|
|
397
476
|
html += '</tr>\n';
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
477
|
+
});
|
|
478
|
+
html += '</thead>\n';
|
|
401
479
|
|
|
402
480
|
// Build body
|
|
403
481
|
if (bodyLines.length > 0) {
|
|
@@ -423,12 +501,16 @@ function buildTable(lines, getAttr) {
|
|
|
423
501
|
/**
|
|
424
502
|
* Process markdown lists (ordered and unordered)
|
|
425
503
|
*/
|
|
426
|
-
function processLists(text, getAttr, inline_styles) {
|
|
504
|
+
function processLists(text, getAttr, inline_styles, bidirectional) {
|
|
427
505
|
|
|
428
506
|
const lines = text.split('\n');
|
|
429
507
|
const result = [];
|
|
430
508
|
let listStack = []; // Track nested lists
|
|
431
509
|
|
|
510
|
+
// Helper to escape HTML for data-qd attributes
|
|
511
|
+
const escapeHtml = (text) => text.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[m]);
|
|
512
|
+
const dataQd = bidirectional ? (marker) => ` data-qd="${escapeHtml(marker)}"` : () => '';
|
|
513
|
+
|
|
432
514
|
for (let i = 0; i < lines.length; i++) {
|
|
433
515
|
const line = lines[i];
|
|
434
516
|
const match = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);
|
|
@@ -476,7 +558,7 @@ function processLists(text, getAttr, inline_styles) {
|
|
|
476
558
|
}
|
|
477
559
|
|
|
478
560
|
const liAttr = taskListClass || getAttr('li');
|
|
479
|
-
result.push(`<li${liAttr}>${listItemContent}</li>`);
|
|
561
|
+
result.push(`<li${liAttr}${dataQd(marker)}>${listItemContent}</li>`);
|
|
480
562
|
} else {
|
|
481
563
|
// Not a list item, close all lists
|
|
482
564
|
while (listStack.length > 0) {
|
|
@@ -522,8 +604,7 @@ quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
|
|
|
522
604
|
|
|
523
605
|
let css = '';
|
|
524
606
|
for (const [tag, style] of Object.entries(styles)) {
|
|
525
|
-
|
|
526
|
-
let themedStyle = style;
|
|
607
|
+
let themedStyle = style;
|
|
527
608
|
|
|
528
609
|
// Apply theme overrides if dark theme
|
|
529
610
|
if (theme === 'dark' && themeOverrides.dark) {
|
|
@@ -546,9 +627,8 @@ quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
|
|
|
546
627
|
themedStyle += `;color:${themeOverrides.light._textColor}`;
|
|
547
628
|
}
|
|
548
629
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
630
|
+
|
|
631
|
+
css += `.${prefix}${tag} { ${themedStyle} }\n`;
|
|
552
632
|
}
|
|
553
633
|
|
|
554
634
|
return css;
|
|
@@ -571,11 +651,13 @@ quikdown.configure = function(options) {
|
|
|
571
651
|
quikdown.version = quikdownVersion;
|
|
572
652
|
|
|
573
653
|
// Export for both CommonJS and ES6
|
|
654
|
+
/* istanbul ignore next */
|
|
574
655
|
if (typeof module !== 'undefined' && module.exports) {
|
|
575
656
|
module.exports = quikdown;
|
|
576
657
|
}
|
|
577
658
|
|
|
578
659
|
// For browser global
|
|
660
|
+
/* istanbul ignore next */
|
|
579
661
|
if (typeof window !== 'undefined') {
|
|
580
662
|
window.quikdown = quikdown;
|
|
581
663
|
}
|
package/dist/quikdown.d.ts
CHANGED
|
@@ -5,17 +5,38 @@
|
|
|
5
5
|
|
|
6
6
|
declare module 'quikdown' {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Fence plugin for custom code block rendering (v1.1.0+)
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
10
|
+
export interface FencePlugin {
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Return undefined to use default rendering.
|
|
12
|
+
* Render markdown fence to HTML
|
|
14
13
|
* @param content - The code block content (unescaped)
|
|
15
14
|
* @param language - The language identifier (or empty string)
|
|
16
15
|
* @returns HTML string or undefined for default rendering
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
render: (content: string, language: string) => string | undefined;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert HTML element back to markdown fence (optional)
|
|
21
|
+
* @param element - The HTML element to convert
|
|
22
|
+
* @returns Fence details or null to use default
|
|
23
|
+
*/
|
|
24
|
+
reverse?: (element: HTMLElement) => {
|
|
25
|
+
fence: string;
|
|
26
|
+
lang: string;
|
|
27
|
+
content: string;
|
|
28
|
+
} | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for configuring the quikdown parser
|
|
33
|
+
*/
|
|
34
|
+
export interface QuikdownOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Custom renderer for fenced code blocks (v1.1.0: object format required)
|
|
37
|
+
* @since 1.1.0 - Must be an object with render function
|
|
38
|
+
*/
|
|
39
|
+
fence_plugin?: FencePlugin;
|
|
19
40
|
|
|
20
41
|
/**
|
|
21
42
|
* If true, uses inline styles instead of CSS classes.
|
|
@@ -30,6 +51,20 @@ declare module 'quikdown' {
|
|
|
30
51
|
* @default false
|
|
31
52
|
*/
|
|
32
53
|
allow_unsafe_urls?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* If true, adds data-qd attributes for bidirectional conversion.
|
|
57
|
+
* Enables HTML to Markdown conversion.
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
bidirectional?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* If true, single newlines become <br> tags.
|
|
64
|
+
* Useful for chat/LLM applications where Enter should create a line break.
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
lazy_linefeeds?: boolean;
|
|
33
68
|
}
|
|
34
69
|
|
|
35
70
|
/**
|