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