quikdown 1.0.2 → 1.0.4

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.
@@ -0,0 +1,677 @@
1
+ /**
2
+ * quikdown_bd - Bidirectional Markdown Parser
3
+ * @version 1.0.4
4
+ * @license BSD-2-Clause
5
+ * @copyright DeftIO 2025
6
+ */
7
+ 'use strict';
8
+
9
+ /**
10
+ * quikdown_bd - Bidirectional markdown/HTML converter
11
+ * Standalone version with round-trip conversion support
12
+ *
13
+ * Uses data-qd attributes to preserve original markdown syntax
14
+ * Enables HTML→Markdown conversion for quikdown-generated HTML
15
+ */
16
+
17
+ // Version - uses same version as core quikdown
18
+ const VERSION = '1.0.4';
19
+
20
+ // Helper to escape HTML (same as core)
21
+ const ESC_MAP = {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'};
22
+ function escapeHtml(text) {
23
+ return text.replace(/[&<>"']/g, m => ESC_MAP[m]);
24
+ }
25
+
26
+ // Modified getAttr that adds data-qd attributes
27
+ function createGetAttrBD(inline_styles, styles) {
28
+ return function(tag, additionalStyle = '', sourceMarker = '') {
29
+ let attrs = '';
30
+
31
+ // Add data-qd attribute if source marker provided
32
+ if (sourceMarker) {
33
+ attrs += ` data-qd="${escapeHtml(sourceMarker)}"`;
34
+ }
35
+
36
+ // Add style or class
37
+ if (inline_styles) {
38
+ const style = styles[tag];
39
+ if (style || additionalStyle) {
40
+ const fullStyle = additionalStyle ? (style ? `${style};${additionalStyle}` : additionalStyle) : style;
41
+ attrs += ` style="${fullStyle}"`;
42
+ }
43
+ } else {
44
+ attrs += ` class="quikdown-${tag}"`;
45
+ }
46
+
47
+ return attrs;
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Enhanced markdown parser with bidirectional support
53
+ * Wraps the core parser and adds data-qd attributes
54
+ */
55
+ function quikdown_bd(markdown, options = {}) {
56
+ if (!markdown || typeof markdown !== 'string') {
57
+ return '';
58
+ }
59
+
60
+ const { fence_plugin, inline_styles = false, bidirectional = true } = options;
61
+
62
+ // If not bidirectional mode, process without data-qd attributes
63
+ if (!bidirectional) {
64
+ // Process without bidirectional tracking
65
+ options.bidirectional = false;
66
+ }
67
+
68
+ // For bidirectional, we need to manually process with source tracking
69
+ // This is a custom implementation that adds data-qd attributes
70
+
71
+ const QUIKDOWN_STYLES = {
72
+ h1: 'font-size:2em;font-weight:600;margin:.67em 0;text-align:left',
73
+ h2: 'font-size:1.5em;font-weight:600;margin:.83em 0',
74
+ h3: 'font-size:1.25em;font-weight:600;margin:1em 0',
75
+ h4: 'font-size:1em;font-weight:600;margin:1.33em 0',
76
+ h5: 'font-size:.875em;font-weight:600;margin:1.67em 0',
77
+ h6: 'font-size:.85em;font-weight:600;margin:2em 0',
78
+ pre: 'background:#f4f4f4;padding:10px;border-radius:4px;overflow-x:auto;margin:1em 0',
79
+ code: 'background:#f0f0f0;padding:2px 4px;border-radius:3px;font-family:monospace',
80
+ blockquote: 'border-left:4px solid #ddd;margin-left:0;padding-left:1em',
81
+ table: 'border-collapse:collapse;width:100%;margin:1em 0',
82
+ th: 'border:1px solid #ddd;padding:8px;background-color:#f2f2f2;font-weight:bold;text-align:left',
83
+ td: 'border:1px solid #ddd;padding:8px;text-align:left',
84
+ hr: 'border:none;border-top:1px solid #ddd;margin:1em 0',
85
+ img: 'max-width:100%;height:auto',
86
+ a: 'color:#06c;text-decoration:underline',
87
+ strong: 'font-weight:bold',
88
+ em: 'font-style:italic',
89
+ del: 'text-decoration:line-through',
90
+ ul: 'margin:.5em 0;padding-left:2em',
91
+ ol: 'margin:.5em 0;padding-left:2em',
92
+ li: 'margin:.25em 0',
93
+ 'task-item': 'list-style:none',
94
+ 'task-checkbox': 'margin-right:.5em'
95
+ };
96
+
97
+ const getAttr = createGetAttrBD(inline_styles, QUIKDOWN_STYLES);
98
+
99
+ // Process markdown with source tracking
100
+ let html = markdown;
101
+
102
+ // Phase 1: Extract and protect code blocks
103
+ const codeBlocks = [];
104
+ const inlineCodes = [];
105
+
106
+ // Extract fenced code blocks
107
+ html = html.replace(/^(```|~~~)([^\n]*)\n([\s\S]*?)^\1$/gm, (match, fence, lang, code) => {
108
+ const placeholder = `§CB${codeBlocks.length}§`;
109
+ codeBlocks.push({
110
+ fence,
111
+ lang: lang.trim(),
112
+ code: escapeHtml(code.trimEnd()),
113
+ original: match
114
+ });
115
+ return placeholder;
116
+ });
117
+
118
+ // Extract inline code
119
+ html = html.replace(/`([^`]+)`/g, (match, code) => {
120
+ const placeholder = `§IC${inlineCodes.length}§`;
121
+ inlineCodes.push({
122
+ code: escapeHtml(code),
123
+ original: match
124
+ });
125
+ return placeholder;
126
+ });
127
+
128
+ // Escape HTML
129
+ html = escapeHtml(html);
130
+
131
+ // Process headings with source tracking
132
+ html = html.replace(/^(#{1,6})\s+(.+?)\s*#*$/gm, (match, hashes, content) => {
133
+ const level = hashes.length;
134
+ const sourceMarker = hashes;
135
+ return `<h${level}${getAttr('h' + level, '', sourceMarker)}>${content}</h${level}>`;
136
+ });
137
+
138
+ // Process bold/italic/strikethrough with source tracking
139
+ html = html.replace(/\*\*(.+?)\*\*/g, `<strong${getAttr('strong', '', '**')}>$1</strong>`);
140
+ html = html.replace(/__(.+?)__/g, `<strong${getAttr('strong', '', '__')}>$1</strong>`);
141
+ html = html.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `<em${getAttr('em', '', '*')}>$1</em>`);
142
+ html = html.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, `<em${getAttr('em', '', '_')}>$1</em>`);
143
+ html = html.replace(/~~(.+?)~~/g, `<del${getAttr('del', '', '~~')}>$1</del>`);
144
+
145
+ // Process blockquotes
146
+ html = html.replace(/^&gt;\s+(.+)$/gm, `<blockquote${getAttr('blockquote', '', '>')}>$1</blockquote>`);
147
+ html = html.replace(/<\/blockquote>\n<blockquote[^>]*>/g, '\n');
148
+
149
+ // Process horizontal rules
150
+ html = html.replace(/^---+$/gm, `<hr${getAttr('hr', '', '---')}>`);
151
+
152
+ // Process lists (simplified for now)
153
+ html = processListsBD(html, getAttr);
154
+
155
+ // Process links and images
156
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
157
+ return `<img${getAttr('img', '', '!')} src="${src}" alt="${alt}" data-qd-alt="${escapeHtml(alt)}" data-qd-src="${escapeHtml(src)}">`;
158
+ });
159
+
160
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
161
+ return `<a${getAttr('a', '', '[')} href="${href}" data-qd-text="${escapeHtml(text)}">${text}</a>`;
162
+ });
163
+
164
+ // Process tables
165
+ html = processTablesBD(html, getAttr);
166
+
167
+ // Line breaks
168
+ html = html.replace(/ $/gm, '<br data-qd=" ">');
169
+
170
+ // Paragraphs
171
+ html = html.replace(/\n\n+/g, '</p><p>');
172
+ html = '<p>' + html + '</p>';
173
+
174
+ // Clean up empty paragraphs and unwrap block elements
175
+ html = html.replace(/<p><\/p>/g, '');
176
+ html = html.replace(/<p>(<h[1-6][^>]*>)/g, '$1');
177
+ html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1');
178
+ html = html.replace(/<p>(<blockquote[^>]*>)/g, '$1');
179
+ html = html.replace(/(<\/blockquote>)<\/p>/g, '$1');
180
+ html = html.replace(/<p>(<ul[^>]*>|<ol[^>]*>)/g, '$1');
181
+ html = html.replace(/(<\/ul>|<\/ol>)<\/p>/g, '$1');
182
+ html = html.replace(/<p>(<hr[^>]*>)<\/p>/g, '$1');
183
+ html = html.replace(/<p>(<table[^>]*>)/g, '$1');
184
+ html = html.replace(/(<\/table>)<\/p>/g, '$1');
185
+
186
+ // Restore code blocks
187
+ codeBlocks.forEach((block, i) => {
188
+ const placeholder = `§CB${i}§`;
189
+ let replacement;
190
+
191
+ if (fence_plugin && typeof fence_plugin === 'function') {
192
+ replacement = fence_plugin(block.code, block.lang);
193
+ if (replacement === undefined) {
194
+ replacement = `<pre${getAttr('pre', '', block.fence)}><code data-qd-lang="${block.lang}">${block.code}</code></pre>`;
195
+ }
196
+ } else {
197
+ replacement = `<pre${getAttr('pre', '', block.fence)} data-qd-fence="${block.fence}" data-qd-lang="${block.lang}"><code>${block.code}</code></pre>`;
198
+ }
199
+
200
+ html = html.replace(placeholder, replacement);
201
+ });
202
+
203
+ // Restore inline codes
204
+ inlineCodes.forEach((item, i) => {
205
+ const placeholder = `§IC${i}§`;
206
+ html = html.replace(placeholder, `<code${getAttr('code', '', '`')}>${item.code}</code>`);
207
+ });
208
+
209
+ return html.trim();
210
+ }
211
+
212
+ // Process lists with source tracking
213
+ function processListsBD(text, getAttr, inline_styles) {
214
+ const lines = text.split('\n');
215
+ const result = [];
216
+ let listStack = [];
217
+
218
+ for (let i = 0; i < lines.length; i++) {
219
+ const line = lines[i];
220
+ const match = line.match(/^(\s*)([*\-+]|\d+\.)\s+(.+)$/);
221
+
222
+ if (match) {
223
+ const [, indent, marker, content] = match;
224
+ const level = Math.floor(indent.length / 2);
225
+ const isOrdered = /^\d+\./.test(marker);
226
+ const listType = isOrdered ? 'ol' : 'ul';
227
+ const sourceMarker = isOrdered ? '1.' : marker;
228
+
229
+ // Handle task lists
230
+ let listItemContent = content;
231
+ let taskAttrs = '';
232
+ const taskMatch = content.match(/^\[([x ])\]\s+(.*)$/i);
233
+ if (taskMatch && !isOrdered) {
234
+ const [, checked, taskContent] = taskMatch;
235
+ const isChecked = checked.toLowerCase() === 'x';
236
+ listItemContent = `<input type="checkbox"${getAttr('task-checkbox', '', '[')}${isChecked ? ' checked' : ''}> ${taskContent}`;
237
+ taskAttrs = getAttr('task-item', '', '- [ ]');
238
+ }
239
+
240
+ // Close deeper levels
241
+ while (listStack.length > level + 1) {
242
+ const list = listStack.pop();
243
+ result.push(`</${list.type}>`);
244
+ }
245
+
246
+ // Open new level if needed
247
+ if (listStack.length === level) {
248
+ listStack.push({ type: listType, level, marker: sourceMarker });
249
+ result.push(`<${listType}${getAttr(listType, '', sourceMarker)}>`);
250
+ }
251
+
252
+ const liAttr = taskAttrs || getAttr('li', '', sourceMarker);
253
+ result.push(`<li${liAttr}>${listItemContent}</li>`);
254
+ } else {
255
+ // Close all lists
256
+ while (listStack.length > 0) {
257
+ const list = listStack.pop();
258
+ result.push(`</${list.type}>`);
259
+ }
260
+ result.push(line);
261
+ }
262
+ }
263
+
264
+ // Close remaining lists
265
+ while (listStack.length > 0) {
266
+ const list = listStack.pop();
267
+ result.push(`</${list.type}>`);
268
+ }
269
+
270
+ return result.join('\n');
271
+ }
272
+
273
+ // Process tables with source tracking
274
+ function processTablesBD(text, getAttr) {
275
+ const lines = text.split('\n');
276
+ const result = [];
277
+ let inTable = false;
278
+ let tableLines = [];
279
+
280
+ for (let i = 0; i < lines.length; i++) {
281
+ const line = lines[i].trim();
282
+
283
+ if (line.includes('|')) {
284
+ if (!inTable) {
285
+ inTable = true;
286
+ tableLines = [];
287
+ }
288
+ tableLines.push(line);
289
+ } else {
290
+ if (inTable) {
291
+ const tableHtml = buildTableBD(tableLines, getAttr);
292
+ if (tableHtml) {
293
+ result.push(tableHtml);
294
+ } else {
295
+ result.push(...tableLines);
296
+ }
297
+ inTable = false;
298
+ tableLines = [];
299
+ }
300
+ result.push(lines[i]);
301
+ }
302
+ }
303
+
304
+ if (inTable && tableLines.length > 0) {
305
+ const tableHtml = buildTableBD(tableLines, getAttr);
306
+ if (tableHtml) {
307
+ result.push(tableHtml);
308
+ } else {
309
+ result.push(...tableLines);
310
+ }
311
+ }
312
+
313
+ return result.join('\n');
314
+ }
315
+
316
+ // Build table with source tracking
317
+ function buildTableBD(lines, getAttr) {
318
+ if (lines.length < 2) return null;
319
+
320
+ // Find separator
321
+ let separatorIndex = -1;
322
+ let alignments = [];
323
+
324
+ for (let i = 1; i < lines.length; i++) {
325
+ if (/^\|?[\s\-:|]+\|?$/.test(lines[i]) && lines[i].includes('-')) {
326
+ separatorIndex = i;
327
+ const cells = lines[i].replace(/^\|/, '').replace(/\|$/, '').split('|');
328
+ alignments = cells.map(cell => {
329
+ const trimmed = cell.trim();
330
+ if (trimmed.startsWith(':') && trimmed.endsWith(':')) return 'center';
331
+ if (trimmed.endsWith(':')) return 'right';
332
+ return 'left';
333
+ });
334
+ break;
335
+ }
336
+ }
337
+
338
+ if (separatorIndex === -1) return null;
339
+
340
+ let html = `<table${getAttr('table', '', '|')} data-qd-align="${alignments.join(',')}">\n`;
341
+
342
+ // Headers
343
+ if (separatorIndex > 0) {
344
+ html += `<thead${getAttr('thead', '', '|')}>\n<tr${getAttr('tr', '', '|')}>\n`;
345
+ const cells = lines[0].replace(/^\|/, '').replace(/\|$/, '').split('|');
346
+ cells.forEach((cell, i) => {
347
+ const align = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
348
+ html += `<th${getAttr('th', align, '|')} data-qd-align="${alignments[i] || 'left'}">${escapeHtml(cell.trim())}</th>\n`;
349
+ });
350
+ html += '</tr>\n</thead>\n';
351
+ }
352
+
353
+ // Body
354
+ const bodyLines = lines.slice(separatorIndex + 1);
355
+ if (bodyLines.length > 0) {
356
+ html += `<tbody${getAttr('tbody', '', '|')}>\n`;
357
+ bodyLines.forEach(line => {
358
+ html += `<tr${getAttr('tr', '', '|')}>\n`;
359
+ const cells = line.replace(/^\|/, '').replace(/\|$/, '').split('|');
360
+ cells.forEach((cell, i) => {
361
+ const align = alignments[i] && alignments[i] !== 'left' ? `text-align:${alignments[i]}` : '';
362
+ html += `<td${getAttr('td', align, '|')} data-qd-align="${alignments[i] || 'left'}">${escapeHtml(cell.trim())}</td>\n`;
363
+ });
364
+ html += '</tr>\n';
365
+ });
366
+ html += '</tbody>\n';
367
+ }
368
+
369
+ html += '</table>';
370
+ return html;
371
+ }
372
+
373
+ /**
374
+ * Convert HTML back to Markdown by walking the DOM tree
375
+ * Uses data-qd attributes when available, falls back to canonical forms
376
+ * Assumes browser environment with DOM API available
377
+ */
378
+ quikdown_bd.toMarkdown = function(htmlOrElement) {
379
+ // Accept either HTML string or DOM element
380
+ let container;
381
+ if (typeof htmlOrElement === 'string') {
382
+ container = document.createElement('div');
383
+ container.innerHTML = htmlOrElement;
384
+ } else if (htmlOrElement instanceof Element) {
385
+ container = htmlOrElement;
386
+ } else {
387
+ return '';
388
+ }
389
+
390
+ // Walk the DOM tree and reconstruct markdown
391
+ function walkNode(node, parentContext = {}) {
392
+ if (node.nodeType === Node.TEXT_NODE) {
393
+ // Return text content, preserving whitespace where needed
394
+ return node.textContent;
395
+ }
396
+
397
+ if (node.nodeType !== Node.ELEMENT_NODE) {
398
+ return '';
399
+ }
400
+
401
+ const tag = node.tagName.toLowerCase();
402
+ const dataQd = node.getAttribute('data-qd');
403
+ const styles = window.getComputedStyle ? window.getComputedStyle(node) : {};
404
+
405
+ // Process children with context
406
+ let childContent = '';
407
+ for (let child of node.childNodes) {
408
+ childContent += walkNode(child, { parentTag: tag, ...parentContext });
409
+ }
410
+
411
+ // Determine markdown based on element and attributes
412
+ switch (tag) {
413
+ case 'h1':
414
+ case 'h2':
415
+ case 'h3':
416
+ case 'h4':
417
+ case 'h5':
418
+ case 'h6':
419
+ const level = parseInt(tag[1]);
420
+ const prefix = dataQd || '#'.repeat(level);
421
+ return `${prefix} ${childContent.trim()}\n\n`;
422
+
423
+ case 'strong':
424
+ case 'b':
425
+ // Check if it's bold through style too
426
+ if (styles.fontWeight === 'bold' || styles.fontWeight >= 700 || tag === 'strong' || tag === 'b') {
427
+ const boldMarker = dataQd || '**';
428
+ return `${boldMarker}${childContent}${boldMarker}`;
429
+ }
430
+ return childContent;
431
+
432
+ case 'em':
433
+ case 'i':
434
+ // Check for italic through style
435
+ if (styles.fontStyle === 'italic' || tag === 'em' || tag === 'i') {
436
+ const emMarker = dataQd || '*';
437
+ return `${emMarker}${childContent}${emMarker}`;
438
+ }
439
+ return childContent;
440
+
441
+ case 'del':
442
+ case 's':
443
+ case 'strike':
444
+ const delMarker = dataQd || '~~';
445
+ return `${delMarker}${childContent}${delMarker}`;
446
+
447
+ case 'code':
448
+ // Skip if inside pre (handled by pre)
449
+ if (parentContext.parentTag === 'pre') {
450
+ return childContent;
451
+ }
452
+ const codeMarker = dataQd || '`';
453
+ return `${codeMarker}${childContent}${codeMarker}`;
454
+
455
+ case 'pre':
456
+ const fence = node.getAttribute('data-qd-fence') || dataQd || '```';
457
+ const lang = node.getAttribute('data-qd-lang') || '';
458
+ // Look for code element child
459
+ const codeEl = node.querySelector('code');
460
+ const codeContent = codeEl ? codeEl.textContent : childContent;
461
+ return `${fence}${lang}\n${codeContent.trimEnd()}\n${fence}\n\n`;
462
+
463
+ case 'blockquote':
464
+ const quoteMarker = dataQd || '>';
465
+ const lines = childContent.trim().split('\n');
466
+ return lines.map(line => `${quoteMarker} ${line}`).join('\n') + '\n\n';
467
+
468
+ case 'hr':
469
+ const hrMarker = dataQd || '---';
470
+ return `${hrMarker}\n\n`;
471
+
472
+ case 'br':
473
+ const brMarker = dataQd || ' ';
474
+ return `${brMarker}\n`;
475
+
476
+ case 'a':
477
+ const linkText = node.getAttribute('data-qd-text') || childContent.trim();
478
+ const href = node.getAttribute('href') || '';
479
+ // Check for autolinks
480
+ if (linkText === href && !dataQd) {
481
+ return `<${href}>`;
482
+ }
483
+ return `[${linkText}](${href})`;
484
+
485
+ case 'img':
486
+ const alt = node.getAttribute('data-qd-alt') || node.getAttribute('alt') || '';
487
+ const src = node.getAttribute('data-qd-src') || node.getAttribute('src') || '';
488
+ const imgMarker = dataQd || '!';
489
+ return `${imgMarker}[${alt}](${src})`;
490
+
491
+ case 'ul':
492
+ case 'ol':
493
+ return walkList(node, tag === 'ol') + '\n';
494
+
495
+ case 'li':
496
+ // Handled by list processor
497
+ return childContent;
498
+
499
+ case 'table':
500
+ return walkTable(node) + '\n\n';
501
+
502
+ case 'p':
503
+ // Check if it's actually a paragraph or just a wrapper
504
+ if (childContent.trim()) {
505
+ return childContent.trim() + '\n\n';
506
+ }
507
+ return '';
508
+
509
+ case 'div':
510
+ // Check if it's a mermaid container
511
+ if (node.classList && node.classList.contains('mermaid-container')) {
512
+ const fence = node.getAttribute('data-qd-fence') || '```';
513
+ const lang = node.getAttribute('data-qd-lang') || 'mermaid';
514
+ // Look for the source element
515
+ const sourceElement = node.querySelector('.mermaid-source');
516
+ if (sourceElement) {
517
+ // Decode HTML entities
518
+ const temp = document.createElement('div');
519
+ temp.innerHTML = sourceElement.innerHTML;
520
+ const code = temp.textContent;
521
+ return `${fence}${lang}\n${code}\n${fence}\n\n`;
522
+ }
523
+ // Fallback: try to extract from the mermaid element
524
+ const mermaidElement = node.querySelector('.mermaid');
525
+ if (mermaidElement && mermaidElement.textContent.includes('graph')) {
526
+ return `${fence}${lang}\n${mermaidElement.textContent.trim()}\n${fence}\n\n`;
527
+ }
528
+ }
529
+ // Check if it's a standalone mermaid diagram (legacy)
530
+ if (node.classList && node.classList.contains('mermaid')) {
531
+ const fence = node.getAttribute('data-qd-fence') || '```';
532
+ const lang = node.getAttribute('data-qd-lang') || 'mermaid';
533
+ const code = node.textContent.trim();
534
+ return `${fence}${lang}\n${code}\n${fence}\n\n`;
535
+ }
536
+ // Pass through other divs
537
+ return childContent;
538
+
539
+ case 'span':
540
+ // Pass through container elements
541
+ return childContent;
542
+
543
+ default:
544
+ return childContent;
545
+ }
546
+ }
547
+
548
+ // Walk list elements
549
+ function walkList(listNode, isOrdered, depth = 0) {
550
+ let result = '';
551
+ let index = 1;
552
+ const indent = ' '.repeat(depth);
553
+
554
+ for (let child of listNode.children) {
555
+ if (child.tagName !== 'LI') continue;
556
+
557
+ const dataQd = child.getAttribute('data-qd');
558
+ let marker = dataQd || (isOrdered ? `${index}.` : '-');
559
+
560
+ // Check for task list checkbox
561
+ const checkbox = child.querySelector('input[type="checkbox"]');
562
+ if (checkbox) {
563
+ const checked = checkbox.checked ? 'x' : ' ';
564
+ marker = '-';
565
+ // Get text without the checkbox
566
+ let text = '';
567
+ for (let node of child.childNodes) {
568
+ if (node.nodeType === Node.TEXT_NODE) {
569
+ text += node.textContent;
570
+ } else if (node.tagName && node.tagName !== 'INPUT') {
571
+ text += walkNode(node);
572
+ }
573
+ }
574
+ result += `${indent}${marker} [${checked}] ${text.trim()}\n`;
575
+ } else {
576
+ let itemContent = '';
577
+
578
+ for (let node of child.childNodes) {
579
+ if (node.tagName === 'UL' || node.tagName === 'OL') {
580
+ itemContent += walkList(node, node.tagName === 'OL', depth + 1);
581
+ } else {
582
+ itemContent += walkNode(node);
583
+ }
584
+ }
585
+
586
+ result += `${indent}${marker} ${itemContent.trim()}\n`;
587
+ }
588
+
589
+ index++;
590
+ }
591
+
592
+ return result;
593
+ }
594
+
595
+ // Walk table elements
596
+ function walkTable(table) {
597
+ let result = '';
598
+ const alignData = table.getAttribute('data-qd-align');
599
+ const alignments = alignData ? alignData.split(',') : [];
600
+
601
+ // Process header
602
+ const thead = table.querySelector('thead');
603
+ if (thead) {
604
+ const headerRow = thead.querySelector('tr');
605
+ if (headerRow) {
606
+ const headers = [];
607
+ for (let th of headerRow.querySelectorAll('th')) {
608
+ headers.push(th.textContent.trim());
609
+ }
610
+ result += '| ' + headers.join(' | ') + ' |\n';
611
+
612
+ // Add separator with alignment
613
+ const separators = headers.map((_, i) => {
614
+ const align = alignments[i] || th.getAttribute('data-qd-align') || 'left';
615
+ if (align === 'center') return ':---:';
616
+ if (align === 'right') return '---:';
617
+ return '---';
618
+ });
619
+ result += '| ' + separators.join(' | ') + ' |\n';
620
+ }
621
+ }
622
+
623
+ // Process body
624
+ const tbody = table.querySelector('tbody');
625
+ if (tbody) {
626
+ for (let row of tbody.querySelectorAll('tr')) {
627
+ const cells = [];
628
+ for (let td of row.querySelectorAll('td')) {
629
+ cells.push(td.textContent.trim());
630
+ }
631
+ if (cells.length > 0) {
632
+ result += '| ' + cells.join(' | ') + ' |\n';
633
+ }
634
+ }
635
+ }
636
+
637
+ return result.trim();
638
+ }
639
+
640
+ // Process the DOM tree
641
+ let markdown = walkNode(container);
642
+
643
+ // Clean up
644
+ markdown = markdown.replace(/\n{3,}/g, '\n\n'); // Remove excessive newlines
645
+ markdown = markdown.trim();
646
+
647
+ return markdown;
648
+ };
649
+
650
+ // Add emitStyles method (same as core)
651
+ quikdown_bd.emitStyles = function(prefix = 'quikdown-', theme = 'light') {
652
+ // This would generate CSS based on the styles
653
+ // For now, returning empty string as placeholder
654
+ // In production, this would generate the full CSS
655
+ return '';
656
+ };
657
+
658
+ // Configure method
659
+ quikdown_bd.configure = function(options) {
660
+ return function(markdown) {
661
+ return quikdown_bd(markdown, options);
662
+ };
663
+ };
664
+
665
+ // Version property
666
+ quikdown_bd.version = VERSION;
667
+
668
+ // Export for both module and browser
669
+ if (typeof module !== 'undefined' && module.exports) {
670
+ module.exports = quikdown_bd;
671
+ }
672
+
673
+ if (typeof window !== 'undefined') {
674
+ window.quikdown_bd = quikdown_bd;
675
+ }
676
+
677
+ module.exports = quikdown_bd;