quasar-ui-danx 0.5.0 → 0.5.1

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.
Files changed (63) hide show
  1. package/dist/danx.es.js +12797 -8181
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +192 -120
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +8 -1
  7. package/src/components/Utility/Code/CodeViewer.vue +31 -14
  8. package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
  9. package/src/components/Utility/Code/CodeViewerFooter.vue +1 -1
  10. package/src/components/Utility/Code/LanguageBadge.vue +278 -5
  11. package/src/components/Utility/Code/index.ts +3 -0
  12. package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
  13. package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
  14. package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
  15. package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
  16. package/src/components/Utility/Markdown/MarkdownEditor.vue +228 -0
  17. package/src/components/Utility/Markdown/MarkdownEditorContent.vue +235 -0
  18. package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
  19. package/src/components/Utility/Markdown/TablePopover.vue +420 -0
  20. package/src/components/Utility/Markdown/index.ts +11 -0
  21. package/src/components/Utility/Markdown/types.ts +27 -0
  22. package/src/components/Utility/index.ts +1 -0
  23. package/src/composables/index.ts +1 -0
  24. package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
  25. package/src/composables/markdown/features/useBlockquotes.ts +248 -0
  26. package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
  27. package/src/composables/markdown/features/useCodeBlocks.spec.ts +779 -0
  28. package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
  29. package/src/composables/markdown/features/useContextMenu.ts +444 -0
  30. package/src/composables/markdown/features/useFocusTracking.ts +116 -0
  31. package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
  32. package/src/composables/markdown/features/useHeadings.ts +290 -0
  33. package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
  34. package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
  35. package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
  36. package/src/composables/markdown/features/useLinks.spec.ts +369 -0
  37. package/src/composables/markdown/features/useLinks.ts +374 -0
  38. package/src/composables/markdown/features/useLists.spec.ts +834 -0
  39. package/src/composables/markdown/features/useLists.ts +747 -0
  40. package/src/composables/markdown/features/usePopoverManager.ts +181 -0
  41. package/src/composables/markdown/features/useTables.spec.ts +1601 -0
  42. package/src/composables/markdown/features/useTables.ts +1107 -0
  43. package/src/composables/markdown/index.ts +16 -0
  44. package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
  45. package/src/composables/markdown/useMarkdownEditor.ts +1068 -0
  46. package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
  47. package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
  48. package/src/composables/markdown/useMarkdownSelection.ts +219 -0
  49. package/src/composables/markdown/useMarkdownSync.ts +549 -0
  50. package/src/composables/useCodeViewerEditor.spec.ts +655 -0
  51. package/src/composables/useCodeViewerEditor.ts +174 -20
  52. package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
  53. package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
  54. package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +412 -0
  55. package/src/helpers/formats/markdown/index.ts +7 -0
  56. package/src/helpers/formats/markdown/linePatterns.spec.ts +495 -0
  57. package/src/helpers/formats/markdown/linePatterns.ts +172 -0
  58. package/src/test/helpers/editorTestUtils.spec.ts +296 -0
  59. package/src/test/helpers/editorTestUtils.ts +253 -0
  60. package/src/test/helpers/index.ts +1 -0
  61. package/src/test/setup.test.ts +12 -0
  62. package/src/test/setup.ts +12 -0
  63. package/vitest.config.ts +19 -0
@@ -0,0 +1,412 @@
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(`![${alt}](${src})`);
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(`![${alt}](${src})`);
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 and other containers - just process children
368
+ case "div":
369
+ case "span":
370
+ parts.push(processNode(element));
371
+ break;
372
+
373
+ // Tables
374
+ case "table":
375
+ parts.push(processTable(element));
376
+ break;
377
+
378
+ default:
379
+ // Unknown elements - just get text content
380
+ parts.push(processNode(element));
381
+ }
382
+ }
383
+ }
384
+
385
+ return parts.join("");
386
+ }
387
+
388
+ /**
389
+ * Convert HTML content to markdown
390
+ * @param html - HTML string or HTMLElement
391
+ * @returns Markdown string
392
+ */
393
+ export function htmlToMarkdown(html: string | HTMLElement): string {
394
+ let container: HTMLElement;
395
+
396
+ if (typeof html === "string") {
397
+ // Create a temporary element to parse the HTML
398
+ container = document.createElement("div");
399
+ container.innerHTML = html;
400
+ } else {
401
+ container = html;
402
+ }
403
+
404
+ const markdown = processNode(container);
405
+
406
+ // Clean up extra whitespace - normalize multiple newlines to max 2
407
+ // Also strip any remaining zero-width spaces as a safety net
408
+ return stripZeroWidthSpaces(markdown).replace(/\n{3,}/g, "\n\n").trim();
409
+ }
410
+
411
+ // Re-export heading utilities
412
+ 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,