quikdown 1.0.1 → 1.0.3
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 +117 -20
- package/dist/quikdown-lex.cjs +810 -0
- package/dist/quikdown-lex.esm.js +808 -0
- package/dist/quikdown-lex.esm.min.js +8 -0
- package/dist/quikdown-lex.esm.min.js.map +1 -0
- package/dist/quikdown-lex.umd.js +816 -0
- package/dist/quikdown-lex.umd.min.js +8 -0
- package/dist/quikdown-lex.umd.min.js.map +1 -0
- package/dist/quikdown.cjs +172 -176
- package/dist/quikdown.d.ts +70 -0
- package/dist/quikdown.dark.css +197 -0
- package/dist/quikdown.dark.min.css +2 -0
- package/dist/quikdown.esm.js +172 -176
- package/dist/quikdown.esm.min.js +2 -2
- package/dist/quikdown.esm.min.js.map +1 -1
- package/dist/quikdown.light.css +170 -0
- package/dist/quikdown.light.min.css +2 -0
- package/dist/quikdown.umd.js +172 -176
- package/dist/quikdown.umd.min.js +2 -2
- package/dist/quikdown.umd.min.js.map +1 -1
- package/package.json +8 -3
package/dist/quikdown.cjs
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* quikdown - Lightweight Markdown Parser
|
|
3
|
-
* @version 1.0.
|
|
3
|
+
* @version 1.0.3
|
|
4
4
|
* @license BSD-2-Clause
|
|
5
5
|
* @copyright DeftIO 2025
|
|
6
6
|
*/
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
// Auto-generated version file - DO NOT EDIT MANUALLY
|
|
10
|
-
// This file is automatically updated by tools/updateVersion.js
|
|
11
|
-
|
|
12
|
-
const quikdownVersion = "1.0.1";
|
|
13
|
-
|
|
14
9
|
/**
|
|
15
10
|
* quikdown - A minimal markdown parser optimized for chat/LLM output
|
|
16
11
|
* Supports tables, code blocks, lists, and common formatting
|
|
@@ -22,68 +17,71 @@ const quikdownVersion = "1.0.1";
|
|
|
22
17
|
* @returns {string} - The rendered HTML
|
|
23
18
|
*/
|
|
24
19
|
|
|
20
|
+
// Version will be injected at build time
|
|
21
|
+
const quikdownVersion = '1.0.3';
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const { fence_plugin, inline_styles = false } = options;
|
|
23
|
+
// Constants for reuse
|
|
24
|
+
const CLASS_PREFIX = 'quikdown-';
|
|
25
|
+
const PLACEHOLDER_CB = '§CB';
|
|
26
|
+
const PLACEHOLDER_IC = '§IC';
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const listStyle = 'margin: 0.5em 0; padding-left: 2em';
|
|
36
|
-
const cellBorder = 'border: 1px solid #ddd; padding: 8px';
|
|
37
|
-
|
|
38
|
-
const styles = {
|
|
39
|
-
h1: headingStyle,
|
|
40
|
-
h2: headingStyle,
|
|
41
|
-
h3: headingStyle,
|
|
42
|
-
h4: headingStyle,
|
|
43
|
-
h5: headingStyle,
|
|
44
|
-
h6: headingStyle,
|
|
45
|
-
pre: 'background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto',
|
|
46
|
-
code: 'background: #f0f0f0; padding: 2px 4px; border-radius: 3px',
|
|
47
|
-
blockquote: 'border-left: 4px solid #ddd; margin-left: 0; padding-left: 1em; color: #666',
|
|
48
|
-
table: 'border-collapse: collapse; width: 100%; margin: 1em 0',
|
|
49
|
-
thead: '',
|
|
50
|
-
tbody: '',
|
|
51
|
-
tr: '',
|
|
52
|
-
th: cellBorder + '; background-color: #f2f2f2; font-weight: bold',
|
|
53
|
-
td: cellBorder + '; text-align: left',
|
|
54
|
-
hr: 'border: none; border-top: 1px solid #ddd; margin: 1em 0',
|
|
55
|
-
img: 'max-width: 100%; height: auto',
|
|
56
|
-
a: 'color: #0066cc; text-decoration: underline',
|
|
57
|
-
strong: 'font-weight: bold',
|
|
58
|
-
em: 'font-style: italic',
|
|
59
|
-
del: 'text-decoration: line-through',
|
|
60
|
-
ul: listStyle,
|
|
61
|
-
ol: listStyle,
|
|
62
|
-
li: 'margin: 0.25em 0',
|
|
63
|
-
br: ''
|
|
64
|
-
};
|
|
28
|
+
// Escape map at module level
|
|
29
|
+
const ESC_MAP = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
|
|
65
30
|
|
|
66
|
-
|
|
67
|
-
|
|
31
|
+
// Single source of truth for all style definitions - optimized
|
|
32
|
+
const QUIKDOWN_STYLES = {
|
|
33
|
+
h1: 'font-size:2em;font-weight:600;margin:.67em 0;text-align:left',
|
|
34
|
+
h2: 'font-size:1.5em;font-weight:600;margin:.83em 0',
|
|
35
|
+
h3: 'font-size:1.25em;font-weight:600;margin:1em 0',
|
|
36
|
+
h4: 'font-size:1em;font-weight:600;margin:1.33em 0',
|
|
37
|
+
h5: 'font-size:.875em;font-weight:600;margin:1.67em 0',
|
|
38
|
+
h6: 'font-size:.85em;font-weight:600;margin:2em 0',
|
|
39
|
+
pre: 'background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0',
|
|
40
|
+
code: 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace',
|
|
41
|
+
blockquote: 'border-left:4px solid #ddd;margin-left:0;padding-left:1em',
|
|
42
|
+
table: 'border-collapse:collapse;width:100%;margin:1em 0',
|
|
43
|
+
th: 'border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left',
|
|
44
|
+
td: 'border:1px solid #ddd;padding:8px;text-align:left',
|
|
45
|
+
hr: 'border:none;border-top:1px solid #ddd;margin:1em 0',
|
|
46
|
+
img: 'max-width:100%;height:auto',
|
|
47
|
+
a: 'color:#06c;text-decoration:underline',
|
|
48
|
+
strong: 'font-weight:bold',
|
|
49
|
+
em: 'font-style:italic',
|
|
50
|
+
del: 'text-decoration:line-through',
|
|
51
|
+
ul: 'margin:.5em 0;padding-left:2em',
|
|
52
|
+
ol: 'margin:.5em 0;padding-left:2em',
|
|
53
|
+
li: 'margin:.25em 0',
|
|
54
|
+
// Task list specific styles
|
|
55
|
+
'task-item': 'list-style:none',
|
|
56
|
+
'task-checkbox': 'margin-right:.5em'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Factory function to create getAttr for a given context
|
|
60
|
+
function createGetAttr(inline_styles, styles) {
|
|
61
|
+
return function(tag, additionalStyle = '') {
|
|
68
62
|
if (inline_styles) {
|
|
69
|
-
const style = styles[tag]
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const style = styles[tag];
|
|
64
|
+
if (!style && !additionalStyle) return '';
|
|
65
|
+
const fullStyle = additionalStyle ? (style ? `${style};${additionalStyle}` : additionalStyle) : style;
|
|
66
|
+
return ` style="${fullStyle}"`;
|
|
72
67
|
} else {
|
|
73
|
-
return ` class="
|
|
68
|
+
return ` class="${CLASS_PREFIX}${tag}"`;
|
|
74
69
|
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function quikdown(markdown, options = {}) {
|
|
74
|
+
if (!markdown || typeof markdown !== 'string') {
|
|
75
|
+
return '';
|
|
75
76
|
}
|
|
77
|
+
|
|
78
|
+
const { fence_plugin, inline_styles = false } = options;
|
|
79
|
+
const styles = QUIKDOWN_STYLES; // Use module-level styles
|
|
80
|
+
const getAttr = createGetAttr(inline_styles, styles); // Create getAttr once
|
|
76
81
|
|
|
77
82
|
// Escape HTML entities to prevent XSS
|
|
78
83
|
function escapeHtml(text) {
|
|
79
|
-
|
|
80
|
-
'&': '&',
|
|
81
|
-
'<': '<',
|
|
82
|
-
'>': '>',
|
|
83
|
-
'"': '"',
|
|
84
|
-
"'": '''
|
|
85
|
-
};
|
|
86
|
-
return text.replace(/[&<>"']/g, m => map[m]);
|
|
84
|
+
return text.replace(/[&<>"']/g, m => ESC_MAP[m]);
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
// Sanitize URLs to prevent XSS attacks
|
|
@@ -93,7 +91,6 @@ function quikdown(markdown, options = {}) {
|
|
|
93
91
|
// If unsafe URLs are explicitly allowed, return as-is
|
|
94
92
|
if (allowUnsafe) return url;
|
|
95
93
|
|
|
96
|
-
// Trim and lowercase for checking
|
|
97
94
|
const trimmedUrl = url.trim();
|
|
98
95
|
const lowerUrl = trimmedUrl.toLowerCase();
|
|
99
96
|
|
|
@@ -122,19 +119,24 @@ function quikdown(markdown, options = {}) {
|
|
|
122
119
|
const inlineCodes = [];
|
|
123
120
|
|
|
124
121
|
// Extract fenced code blocks first (supports both ``` and ~~~)
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
// Match paired fences - ``` with ``` and ~~~ with ~~~
|
|
123
|
+
// Fence must be at start of line
|
|
124
|
+
html = html.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm, (match, fence, lang, code) => {
|
|
125
|
+
const placeholder = `${PLACEHOLDER_CB}${codeBlocks.length}§`;
|
|
126
|
+
|
|
127
|
+
// Trim the language specification
|
|
128
|
+
const langTrimmed = lang ? lang.trim() : '';
|
|
127
129
|
|
|
128
130
|
// If custom fence plugin is provided, use it
|
|
129
131
|
if (fence_plugin && typeof fence_plugin === 'function') {
|
|
130
132
|
codeBlocks.push({
|
|
131
|
-
lang:
|
|
133
|
+
lang: langTrimmed,
|
|
132
134
|
code: code.trimEnd(),
|
|
133
135
|
custom: true
|
|
134
136
|
});
|
|
135
137
|
} else {
|
|
136
138
|
codeBlocks.push({
|
|
137
|
-
lang:
|
|
139
|
+
lang: langTrimmed,
|
|
138
140
|
code: escapeHtml(code.trimEnd()),
|
|
139
141
|
custom: false
|
|
140
142
|
});
|
|
@@ -144,7 +146,7 @@ function quikdown(markdown, options = {}) {
|
|
|
144
146
|
|
|
145
147
|
// Extract inline code
|
|
146
148
|
html = html.replace(/`([^`]+)`/g, (match, code) => {
|
|
147
|
-
const placeholder =
|
|
149
|
+
const placeholder = `${PLACEHOLDER_IC}${inlineCodes.length}§`;
|
|
148
150
|
inlineCodes.push(escapeHtml(code));
|
|
149
151
|
return placeholder;
|
|
150
152
|
});
|
|
@@ -155,7 +157,7 @@ function quikdown(markdown, options = {}) {
|
|
|
155
157
|
// Phase 2: Process block elements
|
|
156
158
|
|
|
157
159
|
// Process tables
|
|
158
|
-
html = processTable(html,
|
|
160
|
+
html = processTable(html, getAttr);
|
|
159
161
|
|
|
160
162
|
// Process headings (supports optional trailing #'s)
|
|
161
163
|
html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
|
|
@@ -172,7 +174,7 @@ function quikdown(markdown, options = {}) {
|
|
|
172
174
|
html = html.replace(/^---+$/gm, `<hr${getAttr('hr')}>`);
|
|
173
175
|
|
|
174
176
|
// Process lists
|
|
175
|
-
html = processLists(html,
|
|
177
|
+
html = processLists(html, getAttr, inline_styles);
|
|
176
178
|
|
|
177
179
|
// Phase 3: Process inline elements
|
|
178
180
|
|
|
@@ -197,16 +199,18 @@ function quikdown(markdown, options = {}) {
|
|
|
197
199
|
return `${prefix}<a${getAttr('a')} href="${sanitizedUrl}" rel="noopener noreferrer">${url}</a>`;
|
|
198
200
|
});
|
|
199
201
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
// Process inline formatting (bold, italic, strikethrough)
|
|
203
|
+
const inlinePatterns = [
|
|
204
|
+
[/\*\*(.+?)\*\*/g, 'strong'],
|
|
205
|
+
[/__(.+?)__/g, 'strong'],
|
|
206
|
+
[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
|
|
207
|
+
[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
|
|
208
|
+
[/~~(.+?)~~/g, 'del']
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
inlinePatterns.forEach(([pattern, tag]) => {
|
|
212
|
+
html = html.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
|
|
213
|
+
});
|
|
210
214
|
|
|
211
215
|
// Line breaks (two spaces at end of line)
|
|
212
216
|
html = html.replace(/ $/gm, `<br${getAttr('br')}>`);
|
|
@@ -215,21 +219,26 @@ function quikdown(markdown, options = {}) {
|
|
|
215
219
|
html = html.replace(/\n\n+/g, '</p><p>');
|
|
216
220
|
html = '<p>' + html + '</p>';
|
|
217
221
|
|
|
218
|
-
// Clean up empty paragraphs and unwrap block elements
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
222
|
+
// Clean up empty paragraphs and unwrap block elements
|
|
223
|
+
const cleanupPatterns = [
|
|
224
|
+
[/<p><\/p>/g, ''],
|
|
225
|
+
[/<p>(<h[1-6][^>]*>)/g, '$1'],
|
|
226
|
+
[/(<\/h[1-6]>)<\/p>/g, '$1'],
|
|
227
|
+
[/<p>(<blockquote[^>]*>)/g, '$1'],
|
|
228
|
+
[/(<\/blockquote>)<\/p>/g, '$1'],
|
|
229
|
+
[/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1'],
|
|
230
|
+
[/(<\/ul>|<\/ol>)<\/p>/g, '$1'],
|
|
231
|
+
[/<p>(<hr[^>]*>)<\/p>/g, '$1'],
|
|
232
|
+
[/<p>(<table[^>]*>)/g, '$1'],
|
|
233
|
+
[/(<\/table>)<\/p>/g, '$1'],
|
|
234
|
+
[/<p>(<pre[^>]*>)/g, '$1'],
|
|
235
|
+
[/(<\/pre>)<\/p>/g, '$1'],
|
|
236
|
+
[new RegExp(`<p>(${PLACEHOLDER_CB}\\d+§)<\/p>`, 'g'), '$1']
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
cleanupPatterns.forEach(([pattern, replacement]) => {
|
|
240
|
+
html = html.replace(pattern, replacement);
|
|
241
|
+
});
|
|
233
242
|
|
|
234
243
|
// Phase 4: Restore code blocks and inline code
|
|
235
244
|
|
|
@@ -253,13 +262,13 @@ function quikdown(markdown, options = {}) {
|
|
|
253
262
|
replacement = `<pre${getAttr('pre')}><code${codeAttr}>${block.code}</code></pre>`;
|
|
254
263
|
}
|
|
255
264
|
|
|
256
|
-
const placeholder =
|
|
265
|
+
const placeholder = `${PLACEHOLDER_CB}${i}§`;
|
|
257
266
|
html = html.replace(placeholder, replacement);
|
|
258
267
|
});
|
|
259
268
|
|
|
260
269
|
// Restore inline code
|
|
261
270
|
inlineCodes.forEach((code, i) => {
|
|
262
|
-
const placeholder =
|
|
271
|
+
const placeholder = `${PLACEHOLDER_IC}${i}§`;
|
|
263
272
|
html = html.replace(placeholder, `<code${getAttr('code')}>${code}</code>`);
|
|
264
273
|
});
|
|
265
274
|
|
|
@@ -269,31 +278,21 @@ function quikdown(markdown, options = {}) {
|
|
|
269
278
|
/**
|
|
270
279
|
* Process inline markdown formatting
|
|
271
280
|
*/
|
|
272
|
-
function processInlineMarkdown(text,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
// Process italic (must not match bold markers)
|
|
289
|
-
text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `<em${getAttr('em')}>$1</em>`);
|
|
290
|
-
text = text.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `<em${getAttr('em')}>$1</em>`);
|
|
291
|
-
|
|
292
|
-
// Process strikethrough
|
|
293
|
-
text = text.replace(/~~(.+?)~~/g, `<del${getAttr('del')}>$1</del>`);
|
|
294
|
-
|
|
295
|
-
// Process inline code
|
|
296
|
-
text = text.replace(/`([^`]+)`/g, `<code${getAttr('code')}>$1</code>`);
|
|
281
|
+
function processInlineMarkdown(text, getAttr) {
|
|
282
|
+
|
|
283
|
+
// Process inline formatting patterns
|
|
284
|
+
const patterns = [
|
|
285
|
+
[/\*\*(.+?)\*\*/g, 'strong'],
|
|
286
|
+
[/__(.+?)__/g, 'strong'],
|
|
287
|
+
[/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, 'em'],
|
|
288
|
+
[/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, 'em'],
|
|
289
|
+
[/~~(.+?)~~/g, 'del'],
|
|
290
|
+
[/`([^`]+)`/g, 'code']
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
patterns.forEach(([pattern, tag]) => {
|
|
294
|
+
text = text.replace(pattern, `<${tag}${getAttr(tag)}>$1</${tag}>`);
|
|
295
|
+
});
|
|
297
296
|
|
|
298
297
|
return text;
|
|
299
298
|
}
|
|
@@ -301,7 +300,7 @@ function processInlineMarkdown(text, inline_styles, styles) {
|
|
|
301
300
|
/**
|
|
302
301
|
* Process markdown tables
|
|
303
302
|
*/
|
|
304
|
-
function processTable(text,
|
|
303
|
+
function processTable(text, getAttr) {
|
|
305
304
|
const lines = text.split('\n');
|
|
306
305
|
const result = [];
|
|
307
306
|
let inTable = false;
|
|
@@ -321,7 +320,7 @@ function processTable(text, inline_styles, styles) {
|
|
|
321
320
|
// Not a table line
|
|
322
321
|
if (inTable) {
|
|
323
322
|
// Process the accumulated table
|
|
324
|
-
const tableHtml = buildTable(tableLines,
|
|
323
|
+
const tableHtml = buildTable(tableLines, getAttr);
|
|
325
324
|
if (tableHtml) {
|
|
326
325
|
result.push(tableHtml);
|
|
327
326
|
} else {
|
|
@@ -337,7 +336,7 @@ function processTable(text, inline_styles, styles) {
|
|
|
337
336
|
|
|
338
337
|
// Handle table at end of text
|
|
339
338
|
if (inTable && tableLines.length > 0) {
|
|
340
|
-
const tableHtml = buildTable(tableLines,
|
|
339
|
+
const tableHtml = buildTable(tableLines, getAttr);
|
|
341
340
|
if (tableHtml) {
|
|
342
341
|
result.push(tableHtml);
|
|
343
342
|
} else {
|
|
@@ -351,17 +350,7 @@ function processTable(text, inline_styles, styles) {
|
|
|
351
350
|
/**
|
|
352
351
|
* Build an HTML table from markdown table lines
|
|
353
352
|
*/
|
|
354
|
-
function buildTable(lines,
|
|
355
|
-
// Helper to get attributes
|
|
356
|
-
function getAttr(tag, additionalStyle = '') {
|
|
357
|
-
if (inline_styles) {
|
|
358
|
-
const style = styles[tag] || '';
|
|
359
|
-
const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
|
|
360
|
-
return fullStyle ? ` style="${fullStyle}"` : '';
|
|
361
|
-
} else {
|
|
362
|
-
return ` class="quikdown-${tag}"`;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
353
|
+
function buildTable(lines, getAttr) {
|
|
365
354
|
|
|
366
355
|
if (lines.length < 2) return null;
|
|
367
356
|
|
|
@@ -401,8 +390,8 @@ function buildTable(lines, inline_styles, styles) {
|
|
|
401
390
|
// Handle pipes at start/end or not
|
|
402
391
|
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
|
|
403
392
|
cells.forEach((cell, i) => {
|
|
404
|
-
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align
|
|
405
|
-
const processedCell = processInlineMarkdown(cell.trim(),
|
|
393
|
+
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
|
|
394
|
+
const processedCell = processInlineMarkdown(cell.trim(), getAttr);
|
|
406
395
|
html += `<th${getAttr('th', alignStyle)}>${processedCell}</th>\n`;
|
|
407
396
|
});
|
|
408
397
|
html += '</tr>\n';
|
|
@@ -418,8 +407,8 @@ function buildTable(lines, inline_styles, styles) {
|
|
|
418
407
|
// Handle pipes at start/end or not
|
|
419
408
|
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|');
|
|
420
409
|
cells.forEach((cell, i) => {
|
|
421
|
-
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align
|
|
422
|
-
const processedCell = processInlineMarkdown(cell.trim(),
|
|
410
|
+
const alignStyle = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
|
|
411
|
+
const processedCell = processInlineMarkdown(cell.trim(), getAttr);
|
|
423
412
|
html += `<td${getAttr('td', alignStyle)}>${processedCell}</td>\n`;
|
|
424
413
|
});
|
|
425
414
|
html += '</tr>\n';
|
|
@@ -434,17 +423,7 @@ function buildTable(lines, inline_styles, styles) {
|
|
|
434
423
|
/**
|
|
435
424
|
* Process markdown lists (ordered and unordered)
|
|
436
425
|
*/
|
|
437
|
-
function processLists(text,
|
|
438
|
-
// Helper to get attributes
|
|
439
|
-
function getAttr(tag, additionalStyle = '') {
|
|
440
|
-
if (inline_styles) {
|
|
441
|
-
const style = styles[tag] || '';
|
|
442
|
-
const fullStyle = additionalStyle ? `${style}; ${additionalStyle}` : style;
|
|
443
|
-
return fullStyle ? ` style="${fullStyle}"` : '';
|
|
444
|
-
} else {
|
|
445
|
-
return ` class="quikdown-${tag}"`;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
426
|
+
function processLists(text, getAttr, inline_styles) {
|
|
448
427
|
|
|
449
428
|
const lines = text.split('\n');
|
|
450
429
|
const result = [];
|
|
@@ -468,10 +447,10 @@ function processLists(text, inline_styles, styles) {
|
|
|
468
447
|
const [, checked, taskContent] = taskMatch;
|
|
469
448
|
const isChecked = checked.toLowerCase() === 'x';
|
|
470
449
|
const checkboxAttr = inline_styles
|
|
471
|
-
? ' style="margin-right
|
|
472
|
-
:
|
|
450
|
+
? ' style="margin-right:.5em"'
|
|
451
|
+
: ` class="${CLASS_PREFIX}task-checkbox"`;
|
|
473
452
|
listItemContent = `<input type="checkbox"${checkboxAttr}${isChecked ? ' checked' : ''} disabled> ${taskContent}`;
|
|
474
|
-
taskListClass = inline_styles ? ' style="list-style:
|
|
453
|
+
taskListClass = inline_styles ? ' style="list-style:none"' : ` class="${CLASS_PREFIX}task-item"`;
|
|
475
454
|
}
|
|
476
455
|
|
|
477
456
|
// Close deeper levels
|
|
@@ -519,39 +498,56 @@ function processLists(text, inline_styles, styles) {
|
|
|
519
498
|
|
|
520
499
|
/**
|
|
521
500
|
* Emit CSS styles for quikdown elements
|
|
501
|
+
* @param {string} prefix - Optional class prefix (default: 'quikdown-')
|
|
502
|
+
* @param {string} theme - Optional theme: 'light' (default) or 'dark'
|
|
522
503
|
* @returns {string} CSS string with quikdown styles
|
|
523
504
|
*/
|
|
524
|
-
quikdown.emitStyles = function() {
|
|
525
|
-
const styles =
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
a: 'color: #0066cc; text-decoration: underline',
|
|
541
|
-
strong: 'font-weight: bold',
|
|
542
|
-
em: 'font-style: italic',
|
|
543
|
-
del: 'text-decoration: line-through',
|
|
544
|
-
ul: 'margin: 0.5em 0; padding-left: 2em',
|
|
545
|
-
ol: 'margin: 0.5em 0; padding-left: 2em',
|
|
546
|
-
li: 'margin: 0.25em 0',
|
|
547
|
-
'task-item': 'list-style: none',
|
|
548
|
-
'task-checkbox': 'margin-right: 0.5em'
|
|
505
|
+
quikdown.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
|
|
506
|
+
const styles = QUIKDOWN_STYLES;
|
|
507
|
+
|
|
508
|
+
// Define theme color overrides
|
|
509
|
+
const themeOverrides = {
|
|
510
|
+
dark: {
|
|
511
|
+
'#f4f4f4': '#2a2a2a', // pre background
|
|
512
|
+
'#f0f0f0': '#2a2a2a', // code background
|
|
513
|
+
'#f2f2f2': '#2a2a2a', // th background
|
|
514
|
+
'#ddd': '#3a3a3a', // borders
|
|
515
|
+
'#06c': '#6db3f2', // links
|
|
516
|
+
_textColor: '#e0e0e0'
|
|
517
|
+
},
|
|
518
|
+
light: {
|
|
519
|
+
_textColor: '#333' // Explicit text color for light theme
|
|
520
|
+
}
|
|
549
521
|
};
|
|
550
522
|
|
|
551
523
|
let css = '';
|
|
552
524
|
for (const [tag, style] of Object.entries(styles)) {
|
|
553
525
|
if (style) {
|
|
554
|
-
|
|
526
|
+
let themedStyle = style;
|
|
527
|
+
|
|
528
|
+
// Apply theme overrides if dark theme
|
|
529
|
+
if (theme === 'dark' && themeOverrides.dark) {
|
|
530
|
+
// Replace colors
|
|
531
|
+
for (const [oldColor, newColor] of Object.entries(themeOverrides.dark)) {
|
|
532
|
+
if (!oldColor.startsWith('_')) {
|
|
533
|
+
themedStyle = themedStyle.replace(new RegExp(oldColor, 'g'), newColor);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Add text color for certain elements in dark theme
|
|
538
|
+
const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];
|
|
539
|
+
if (needsTextColor.includes(tag)) {
|
|
540
|
+
themedStyle += `;color:${themeOverrides.dark._textColor}`;
|
|
541
|
+
}
|
|
542
|
+
} else if (theme === 'light' && themeOverrides.light) {
|
|
543
|
+
// Add explicit text color for light theme elements too
|
|
544
|
+
const needsTextColor = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'li', 'blockquote'];
|
|
545
|
+
if (needsTextColor.includes(tag)) {
|
|
546
|
+
themedStyle += `;color:${themeOverrides.light._textColor}`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
css += `.${prefix}${tag} { ${themedStyle} }\n`;
|
|
555
551
|
}
|
|
556
552
|
}
|
|
557
553
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quikdown - Lightweight Markdown Parser
|
|
3
|
+
* TypeScript definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module 'quikdown' {
|
|
7
|
+
/**
|
|
8
|
+
* Options for configuring the quikdown parser
|
|
9
|
+
*/
|
|
10
|
+
export interface QuikdownOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Custom renderer for fenced code blocks.
|
|
13
|
+
* Return undefined to use default rendering.
|
|
14
|
+
* @param content - The code block content (unescaped)
|
|
15
|
+
* @param language - The language identifier (or empty string)
|
|
16
|
+
* @returns HTML string or undefined for default rendering
|
|
17
|
+
*/
|
|
18
|
+
fence_plugin?: (content: string, language: string) => string | undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* If true, uses inline styles instead of CSS classes.
|
|
22
|
+
* Useful for emails or environments without CSS support.
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
inline_styles?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* If true, allows potentially unsafe URLs (javascript:, data:, etc).
|
|
29
|
+
* Only use with trusted content.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
allow_unsafe_urls?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse markdown to HTML
|
|
37
|
+
* @param markdown - The markdown source text
|
|
38
|
+
* @param options - Optional configuration
|
|
39
|
+
* @returns The rendered HTML string
|
|
40
|
+
*/
|
|
41
|
+
function quikdown(markdown: string, options?: QuikdownOptions): string;
|
|
42
|
+
|
|
43
|
+
namespace quikdown {
|
|
44
|
+
/**
|
|
45
|
+
* Generate CSS styles for quikdown classes with theme support
|
|
46
|
+
* @param prefix - CSS class prefix (default: 'quikdown-')
|
|
47
|
+
* @param theme - Theme name: 'light' (default) or 'dark'
|
|
48
|
+
* @returns CSS string with themed .quikdown-* styles
|
|
49
|
+
*/
|
|
50
|
+
export function emitStyles(prefix?: string, theme?: 'light' | 'dark'): string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a configured parser function with preset options
|
|
54
|
+
* @param options - Configuration to apply to all parsing
|
|
55
|
+
* @returns A parser function with the options pre-applied
|
|
56
|
+
*/
|
|
57
|
+
export function configure(options: QuikdownOptions): (markdown: string) => string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The version of quikdown
|
|
61
|
+
*/
|
|
62
|
+
export const version: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export = quikdown;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// For ES6 module imports
|
|
69
|
+
export default quikdown;
|
|
70
|
+
export { QuikdownOptions };
|