quasar-ui-danx 0.5.0 → 0.5.2
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/.claude/settings.local.json +8 -0
- package/dist/danx.es.js +16119 -10641
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +202 -123
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +8 -1
- package/src/components/Utility/Buttons/ActionButton.vue +15 -5
- package/src/components/Utility/Code/CodeViewer.vue +41 -16
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +3 -1
- package/src/components/Utility/Code/LanguageBadge.vue +278 -5
- package/src/components/Utility/Code/MarkdownContent.vue +31 -163
- package/src/components/Utility/Code/index.ts +3 -0
- package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
- package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
- package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
- package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
- package/src/components/Utility/Markdown/MarkdownEditor.vue +233 -0
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +296 -0
- package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
- package/src/components/Utility/Markdown/TablePopover.vue +420 -0
- package/src/components/Utility/Markdown/index.ts +11 -0
- package/src/components/Utility/Markdown/types.ts +27 -0
- package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
- package/src/composables/markdown/features/useBlockquotes.ts +248 -0
- package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +805 -0
- package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
- package/src/composables/markdown/features/useContextMenu.ts +444 -0
- package/src/composables/markdown/features/useFocusTracking.ts +116 -0
- package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
- package/src/composables/markdown/features/useHeadings.ts +290 -0
- package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
- package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
- package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
- package/src/composables/markdown/features/useLinks.spec.ts +388 -0
- package/src/composables/markdown/features/useLinks.ts +374 -0
- package/src/composables/markdown/features/useLists.spec.ts +834 -0
- package/src/composables/markdown/features/useLists.ts +747 -0
- package/src/composables/markdown/features/usePopoverManager.ts +181 -0
- package/src/composables/markdown/features/useTables.spec.ts +1601 -0
- package/src/composables/markdown/features/useTables.ts +1107 -0
- package/src/composables/markdown/index.ts +16 -0
- package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
- package/src/composables/markdown/useMarkdownEditor.ts +1077 -0
- package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
- package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
- package/src/composables/markdown/useMarkdownSelection.ts +219 -0
- package/src/composables/markdown/useMarkdownSync.ts +549 -0
- package/src/composables/useCodeFormat.ts +17 -10
- package/src/composables/useCodeViewerEditor.spec.ts +655 -0
- package/src/composables/useCodeViewerEditor.ts +174 -20
- package/src/helpers/formats/highlightCSS.ts +236 -0
- package/src/helpers/formats/highlightHTML.ts +483 -0
- package/src/helpers/formats/highlightJavaScript.ts +346 -0
- package/src/helpers/formats/highlightSyntax.ts +15 -4
- package/src/helpers/formats/index.ts +3 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +425 -0
- package/src/helpers/formats/markdown/index.ts +7 -0
- package/src/helpers/formats/markdown/linePatterns.spec.ts +498 -0
- package/src/helpers/formats/markdown/linePatterns.ts +172 -0
- package/src/styles/danx.scss +3 -3
- package/src/styles/index.scss +5 -5
- package/src/styles/themes/danx/code.scss +257 -1
- package/src/styles/themes/danx/index.scss +10 -10
- package/src/styles/themes/danx/markdown.scss +59 -0
- package/src/test/helpers/editorTestUtils.spec.ts +296 -0
- package/src/test/helpers/editorTestUtils.ts +253 -0
- package/src/test/helpers/index.ts +1 -0
- package/src/test/highlighters.test.ts +153 -0
- package/src/test/setup.test.ts +12 -0
- package/src/test/setup.ts +12 -0
- package/src/types/widgets.d.ts +2 -2
- package/vite.config.js +5 -1
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML to Markdown converter
|
|
3
|
+
* Converts HTML content back to markdown source
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { convertHeading, isHeadingElement } from "./convertHeadings";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Characters that have special meaning in markdown and may need escaping
|
|
10
|
+
*/
|
|
11
|
+
const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}[\]()#+\-.!])/g;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Escape markdown special characters in text
|
|
15
|
+
* @param text - Plain text that may contain special characters
|
|
16
|
+
* @returns Text with special characters escaped
|
|
17
|
+
*/
|
|
18
|
+
export function escapeMarkdownChars(text: string): string {
|
|
19
|
+
return text.replace(MARKDOWN_SPECIAL_CHARS, "\\$1");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Strip zero-width spaces from text content
|
|
24
|
+
* These are inserted by the inline formatting toggle to break out of formatting context
|
|
25
|
+
*/
|
|
26
|
+
function stripZeroWidthSpaces(text: string): string {
|
|
27
|
+
return text.replace(/\u200B/g, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Process inline content (text with inline formatting)
|
|
32
|
+
* Handles nested inline elements like bold, italic, code, links
|
|
33
|
+
*/
|
|
34
|
+
function processInlineContent(element: Element): string {
|
|
35
|
+
const parts: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const child of Array.from(element.childNodes)) {
|
|
38
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
39
|
+
// Strip zero-width spaces from text nodes
|
|
40
|
+
parts.push(stripZeroWidthSpaces(child.textContent || ""));
|
|
41
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
42
|
+
const el = child as Element;
|
|
43
|
+
const tagName = el.tagName.toLowerCase();
|
|
44
|
+
const content = processInlineContent(el);
|
|
45
|
+
|
|
46
|
+
// Skip empty formatting elements
|
|
47
|
+
if (!content && ["strong", "b", "em", "i", "code", "del", "s", "mark", "sup", "sub"].includes(tagName)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
switch (tagName) {
|
|
52
|
+
case "strong":
|
|
53
|
+
case "b":
|
|
54
|
+
parts.push(`**${content}**`);
|
|
55
|
+
break;
|
|
56
|
+
case "em":
|
|
57
|
+
case "i":
|
|
58
|
+
parts.push(`*${content}*`);
|
|
59
|
+
break;
|
|
60
|
+
case "code":
|
|
61
|
+
parts.push(`\`${el.textContent || ""}\``);
|
|
62
|
+
break;
|
|
63
|
+
case "a": {
|
|
64
|
+
const href = el.getAttribute("href") || "";
|
|
65
|
+
parts.push(`[${content}](${href})`);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "img": {
|
|
69
|
+
const src = el.getAttribute("src") || "";
|
|
70
|
+
const alt = el.getAttribute("alt") || "";
|
|
71
|
+
parts.push(``);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "del":
|
|
75
|
+
case "s":
|
|
76
|
+
parts.push(`~~${content}~~`);
|
|
77
|
+
break;
|
|
78
|
+
case "mark":
|
|
79
|
+
parts.push(`==${content}==`);
|
|
80
|
+
break;
|
|
81
|
+
case "sup":
|
|
82
|
+
parts.push(`^${content}^`);
|
|
83
|
+
break;
|
|
84
|
+
case "sub":
|
|
85
|
+
parts.push(`~${content}~`);
|
|
86
|
+
break;
|
|
87
|
+
case "br":
|
|
88
|
+
parts.push(" \n");
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
parts.push(content);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parts.join("");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Process list items with proper markers
|
|
101
|
+
*/
|
|
102
|
+
function processListItems(listElement: Element, marker: string): string {
|
|
103
|
+
const items: string[] = [];
|
|
104
|
+
let index = 1;
|
|
105
|
+
|
|
106
|
+
for (const child of Array.from(listElement.children)) {
|
|
107
|
+
if (child.tagName.toLowerCase() === "li") {
|
|
108
|
+
const prefix = marker === "1." ? `${index}. ` : `${marker} `;
|
|
109
|
+
const content = processInlineContent(child);
|
|
110
|
+
|
|
111
|
+
// Check for nested lists
|
|
112
|
+
const nestedUl = child.querySelector("ul");
|
|
113
|
+
const nestedOl = child.querySelector("ol");
|
|
114
|
+
|
|
115
|
+
if (nestedUl || nestedOl) {
|
|
116
|
+
// Get text content before nested list
|
|
117
|
+
const textParts: string[] = [];
|
|
118
|
+
for (const node of Array.from(child.childNodes)) {
|
|
119
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
120
|
+
textParts.push(node.textContent || "");
|
|
121
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
122
|
+
const el = node as Element;
|
|
123
|
+
if (el.tagName.toLowerCase() !== "ul" && el.tagName.toLowerCase() !== "ol") {
|
|
124
|
+
textParts.push(processInlineContent(el));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
items.push(`${prefix}${textParts.join("").trim()}`);
|
|
129
|
+
|
|
130
|
+
// Process nested list with indentation
|
|
131
|
+
if (nestedUl) {
|
|
132
|
+
const nestedItems = processListItems(nestedUl, "-").split("\n").filter(Boolean);
|
|
133
|
+
items.push(...nestedItems.map(item => ` ${item}`));
|
|
134
|
+
}
|
|
135
|
+
if (nestedOl) {
|
|
136
|
+
const nestedItems = processListItems(nestedOl, "1.").split("\n").filter(Boolean);
|
|
137
|
+
items.push(...nestedItems.map(item => ` ${item}`));
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
items.push(`${prefix}${content}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
index++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return items.join("\n") + "\n\n";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Process table element to markdown
|
|
152
|
+
*/
|
|
153
|
+
function processTable(table: Element): string {
|
|
154
|
+
const rows: string[][] = [];
|
|
155
|
+
const alignments: string[] = [];
|
|
156
|
+
|
|
157
|
+
// Process thead
|
|
158
|
+
const thead = table.querySelector("thead");
|
|
159
|
+
if (thead) {
|
|
160
|
+
const headerRow = thead.querySelector("tr");
|
|
161
|
+
if (headerRow) {
|
|
162
|
+
const cells: string[] = [];
|
|
163
|
+
for (const th of Array.from(headerRow.querySelectorAll("th"))) {
|
|
164
|
+
cells.push(processInlineContent(th).trim());
|
|
165
|
+
// Detect alignment from style or class
|
|
166
|
+
const style = th.getAttribute("style") || "";
|
|
167
|
+
if (style.includes("text-align: center")) {
|
|
168
|
+
alignments.push(":---:");
|
|
169
|
+
} else if (style.includes("text-align: right")) {
|
|
170
|
+
alignments.push("---:");
|
|
171
|
+
} else {
|
|
172
|
+
alignments.push("---");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
rows.push(cells);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Process tbody
|
|
180
|
+
const tbody = table.querySelector("tbody") || table;
|
|
181
|
+
for (const tr of Array.from(tbody.querySelectorAll("tr"))) {
|
|
182
|
+
if (thead && tr.parentElement === thead) continue;
|
|
183
|
+
const cells: string[] = [];
|
|
184
|
+
for (const td of Array.from(tr.querySelectorAll("td, th"))) {
|
|
185
|
+
cells.push(processInlineContent(td).trim());
|
|
186
|
+
}
|
|
187
|
+
if (cells.length > 0) {
|
|
188
|
+
rows.push(cells);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (rows.length === 0) return "";
|
|
193
|
+
|
|
194
|
+
// Build markdown table
|
|
195
|
+
const lines: string[] = [];
|
|
196
|
+
|
|
197
|
+
// Header row
|
|
198
|
+
if (rows.length > 0) {
|
|
199
|
+
lines.push(`| ${rows[0].join(" | ")} |`);
|
|
200
|
+
// Separator with alignments
|
|
201
|
+
if (alignments.length > 0) {
|
|
202
|
+
lines.push(`| ${alignments.join(" | ")} |`);
|
|
203
|
+
} else {
|
|
204
|
+
lines.push(`| ${rows[0].map(() => "---").join(" | ")} |`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Data rows
|
|
209
|
+
for (let i = 1; i < rows.length; i++) {
|
|
210
|
+
lines.push(`| ${rows[i].join(" | ")} |`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return lines.join("\n") + "\n\n";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Convert a single node to markdown
|
|
218
|
+
*/
|
|
219
|
+
function processNode(node: Node): string {
|
|
220
|
+
const parts: string[] = [];
|
|
221
|
+
|
|
222
|
+
for (const child of Array.from(node.childNodes)) {
|
|
223
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
224
|
+
// Strip zero-width spaces from text nodes
|
|
225
|
+
parts.push(stripZeroWidthSpaces(child.textContent || ""));
|
|
226
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
227
|
+
const element = child as Element;
|
|
228
|
+
const tagName = element.tagName.toLowerCase();
|
|
229
|
+
|
|
230
|
+
// Handle headings
|
|
231
|
+
if (isHeadingElement(element)) {
|
|
232
|
+
parts.push(convertHeading(element));
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
switch (tagName) {
|
|
237
|
+
// Paragraphs
|
|
238
|
+
case "p":
|
|
239
|
+
parts.push(`${processInlineContent(element)}\n\n`);
|
|
240
|
+
break;
|
|
241
|
+
|
|
242
|
+
// Line breaks
|
|
243
|
+
case "br":
|
|
244
|
+
parts.push(" \n");
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
// Bold
|
|
248
|
+
case "strong":
|
|
249
|
+
case "b": {
|
|
250
|
+
const content = processInlineContent(element);
|
|
251
|
+
if (content) {
|
|
252
|
+
parts.push(`**${content}**`);
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Italic
|
|
258
|
+
case "em":
|
|
259
|
+
case "i": {
|
|
260
|
+
const content = processInlineContent(element);
|
|
261
|
+
if (content) {
|
|
262
|
+
parts.push(`*${content}*`);
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Inline code
|
|
268
|
+
case "code":
|
|
269
|
+
if (element.parentElement?.tagName.toLowerCase() !== "pre") {
|
|
270
|
+
parts.push(`\`${element.textContent || ""}\``);
|
|
271
|
+
} else {
|
|
272
|
+
parts.push(element.textContent || "");
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
|
|
276
|
+
// Code blocks
|
|
277
|
+
case "pre": {
|
|
278
|
+
const codeElement = element.querySelector("code");
|
|
279
|
+
const code = codeElement?.textContent || element.textContent || "";
|
|
280
|
+
const langClass = codeElement?.className.match(/language-(\w+)/);
|
|
281
|
+
const lang = langClass ? langClass[1] : "";
|
|
282
|
+
parts.push(`\`\`\`${lang}\n${code}\n\`\`\`\n\n`);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Blockquotes
|
|
287
|
+
case "blockquote": {
|
|
288
|
+
const content = processNode(element).trim();
|
|
289
|
+
const quotedLines = content.split("\n").map(line => `> ${line}`).join("\n");
|
|
290
|
+
parts.push(`${quotedLines}\n\n`);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Unordered lists
|
|
295
|
+
case "ul":
|
|
296
|
+
parts.push(processListItems(element, "-"));
|
|
297
|
+
break;
|
|
298
|
+
|
|
299
|
+
// Ordered lists
|
|
300
|
+
case "ol":
|
|
301
|
+
parts.push(processListItems(element, "1."));
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
// List items (handled by processListItems)
|
|
305
|
+
case "li":
|
|
306
|
+
parts.push(processInlineContent(element));
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
// Links
|
|
310
|
+
case "a": {
|
|
311
|
+
const href = element.getAttribute("href") || "";
|
|
312
|
+
const text = processInlineContent(element);
|
|
313
|
+
parts.push(`[${text}](${href})`);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Images
|
|
318
|
+
case "img": {
|
|
319
|
+
const src = element.getAttribute("src") || "";
|
|
320
|
+
const alt = element.getAttribute("alt") || "";
|
|
321
|
+
parts.push(``);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Horizontal rules
|
|
326
|
+
case "hr":
|
|
327
|
+
parts.push("---\n\n");
|
|
328
|
+
break;
|
|
329
|
+
|
|
330
|
+
// Strikethrough
|
|
331
|
+
case "del":
|
|
332
|
+
case "s": {
|
|
333
|
+
const content = processInlineContent(element);
|
|
334
|
+
if (content) {
|
|
335
|
+
parts.push(`~~${content}~~`);
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Highlight
|
|
341
|
+
case "mark": {
|
|
342
|
+
const content = processInlineContent(element);
|
|
343
|
+
if (content) {
|
|
344
|
+
parts.push(`==${content}==`);
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Superscript
|
|
350
|
+
case "sup": {
|
|
351
|
+
const content = processInlineContent(element);
|
|
352
|
+
if (content) {
|
|
353
|
+
parts.push(`^${content}^`);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Subscript
|
|
359
|
+
case "sub": {
|
|
360
|
+
const content = processInlineContent(element);
|
|
361
|
+
if (content) {
|
|
362
|
+
parts.push(`~${content}~`);
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Divs - check for code block wrapper first
|
|
368
|
+
case "div": {
|
|
369
|
+
// Handle code block wrapper structure
|
|
370
|
+
if (element.hasAttribute("data-code-block-id")) {
|
|
371
|
+
const mountPoint = element.querySelector(".code-viewer-mount-point");
|
|
372
|
+
const content = mountPoint?.getAttribute("data-content") || "";
|
|
373
|
+
const language = mountPoint?.getAttribute("data-language") || "";
|
|
374
|
+
parts.push(`\`\`\`${language}\n${content}\n\`\`\`\n\n`);
|
|
375
|
+
} else {
|
|
376
|
+
parts.push(processNode(element));
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Spans - just process children
|
|
382
|
+
case "span":
|
|
383
|
+
parts.push(processNode(element));
|
|
384
|
+
break;
|
|
385
|
+
|
|
386
|
+
// Tables
|
|
387
|
+
case "table":
|
|
388
|
+
parts.push(processTable(element));
|
|
389
|
+
break;
|
|
390
|
+
|
|
391
|
+
default:
|
|
392
|
+
// Unknown elements - just get text content
|
|
393
|
+
parts.push(processNode(element));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return parts.join("");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Convert HTML content to markdown
|
|
403
|
+
* @param html - HTML string or HTMLElement
|
|
404
|
+
* @returns Markdown string
|
|
405
|
+
*/
|
|
406
|
+
export function htmlToMarkdown(html: string | HTMLElement): string {
|
|
407
|
+
let container: HTMLElement;
|
|
408
|
+
|
|
409
|
+
if (typeof html === "string") {
|
|
410
|
+
// Create a temporary element to parse the HTML
|
|
411
|
+
container = document.createElement("div");
|
|
412
|
+
container.innerHTML = html;
|
|
413
|
+
} else {
|
|
414
|
+
container = html;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const markdown = processNode(container);
|
|
418
|
+
|
|
419
|
+
// Clean up extra whitespace - normalize multiple newlines to max 2
|
|
420
|
+
// Also strip any remaining zero-width spaces as a safety net
|
|
421
|
+
return stripZeroWidthSpaces(markdown).replace(/\n{3,}/g, "\n\n").trim();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Re-export heading utilities
|
|
425
|
+
export { convertHeading, isHeadingElement, getHeadingLevel } from "./convertHeadings";
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
* - Footnotes ([^id] with [^id]: content definitions)
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
+
// Re-export HTML to Markdown converter
|
|
34
|
+
export { htmlToMarkdown, escapeMarkdownChars } from "./htmlToMarkdown";
|
|
35
|
+
export { convertHeading, isHeadingElement, getHeadingLevel } from "./htmlToMarkdown/convertHeadings";
|
|
36
|
+
|
|
37
|
+
// Re-export line pattern detection
|
|
38
|
+
export * from "./linePatterns";
|
|
39
|
+
|
|
33
40
|
// Re-export types
|
|
34
41
|
export type {
|
|
35
42
|
MarkdownRenderOptions,
|