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.
Files changed (81) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/dist/danx.es.js +16119 -10641
  3. package/dist/danx.es.js.map +1 -1
  4. package/dist/danx.umd.js +202 -123
  5. package/dist/danx.umd.js.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +8 -1
  8. package/src/components/Utility/Buttons/ActionButton.vue +15 -5
  9. package/src/components/Utility/Code/CodeViewer.vue +41 -16
  10. package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
  11. package/src/components/Utility/Code/CodeViewerFooter.vue +3 -1
  12. package/src/components/Utility/Code/LanguageBadge.vue +278 -5
  13. package/src/components/Utility/Code/MarkdownContent.vue +31 -163
  14. package/src/components/Utility/Code/index.ts +3 -0
  15. package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
  16. package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
  17. package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
  18. package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
  19. package/src/components/Utility/Markdown/MarkdownEditor.vue +233 -0
  20. package/src/components/Utility/Markdown/MarkdownEditorContent.vue +296 -0
  21. package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
  22. package/src/components/Utility/Markdown/TablePopover.vue +420 -0
  23. package/src/components/Utility/Markdown/index.ts +11 -0
  24. package/src/components/Utility/Markdown/types.ts +27 -0
  25. package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
  26. package/src/components/Utility/index.ts +1 -0
  27. package/src/composables/index.ts +1 -0
  28. package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
  29. package/src/composables/markdown/features/useBlockquotes.ts +248 -0
  30. package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
  31. package/src/composables/markdown/features/useCodeBlocks.spec.ts +805 -0
  32. package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
  33. package/src/composables/markdown/features/useContextMenu.ts +444 -0
  34. package/src/composables/markdown/features/useFocusTracking.ts +116 -0
  35. package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
  36. package/src/composables/markdown/features/useHeadings.ts +290 -0
  37. package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
  38. package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
  39. package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
  40. package/src/composables/markdown/features/useLinks.spec.ts +388 -0
  41. package/src/composables/markdown/features/useLinks.ts +374 -0
  42. package/src/composables/markdown/features/useLists.spec.ts +834 -0
  43. package/src/composables/markdown/features/useLists.ts +747 -0
  44. package/src/composables/markdown/features/usePopoverManager.ts +181 -0
  45. package/src/composables/markdown/features/useTables.spec.ts +1601 -0
  46. package/src/composables/markdown/features/useTables.ts +1107 -0
  47. package/src/composables/markdown/index.ts +16 -0
  48. package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
  49. package/src/composables/markdown/useMarkdownEditor.ts +1077 -0
  50. package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
  51. package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
  52. package/src/composables/markdown/useMarkdownSelection.ts +219 -0
  53. package/src/composables/markdown/useMarkdownSync.ts +549 -0
  54. package/src/composables/useCodeFormat.ts +17 -10
  55. package/src/composables/useCodeViewerEditor.spec.ts +655 -0
  56. package/src/composables/useCodeViewerEditor.ts +174 -20
  57. package/src/helpers/formats/highlightCSS.ts +236 -0
  58. package/src/helpers/formats/highlightHTML.ts +483 -0
  59. package/src/helpers/formats/highlightJavaScript.ts +346 -0
  60. package/src/helpers/formats/highlightSyntax.ts +15 -4
  61. package/src/helpers/formats/index.ts +3 -0
  62. package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
  63. package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
  64. package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +425 -0
  65. package/src/helpers/formats/markdown/index.ts +7 -0
  66. package/src/helpers/formats/markdown/linePatterns.spec.ts +498 -0
  67. package/src/helpers/formats/markdown/linePatterns.ts +172 -0
  68. package/src/styles/danx.scss +3 -3
  69. package/src/styles/index.scss +5 -5
  70. package/src/styles/themes/danx/code.scss +257 -1
  71. package/src/styles/themes/danx/index.scss +10 -10
  72. package/src/styles/themes/danx/markdown.scss +59 -0
  73. package/src/test/helpers/editorTestUtils.spec.ts +296 -0
  74. package/src/test/helpers/editorTestUtils.ts +253 -0
  75. package/src/test/helpers/index.ts +1 -0
  76. package/src/test/highlighters.test.ts +153 -0
  77. package/src/test/setup.test.ts +12 -0
  78. package/src/test/setup.ts +12 -0
  79. package/src/types/widgets.d.ts +2 -2
  80. package/vite.config.js +5 -1
  81. package/vitest.config.ts +19 -0
@@ -0,0 +1,483 @@
1
+ /**
2
+ * Lightweight syntax highlighting for HTML
3
+ * Returns HTML string with syntax highlighting spans
4
+ * Supports embedded CSS (<style>) and JavaScript (<script>) highlighting
5
+ */
6
+
7
+ import { highlightCSS } from "./highlightCSS";
8
+ import { highlightJavaScript } from "./highlightJavaScript";
9
+
10
+ /**
11
+ * Escape HTML entities to prevent XSS
12
+ */
13
+ function escapeHtml(text: string): string {
14
+ return text
15
+ .replace(/&/g, "&amp;")
16
+ .replace(/</g, "&lt;")
17
+ .replace(/>/g, "&gt;")
18
+ .replace(/"/g, "&quot;")
19
+ .replace(/'/g, "&#039;");
20
+ }
21
+
22
+ /**
23
+ * HTML parsing states
24
+ */
25
+ type HTMLState = "text" | "tag-open" | "tag-name" | "attribute-name" | "attribute-equals" | "attribute-value" | "tag-close" | "comment" | "doctype";
26
+
27
+ /**
28
+ * Highlight HTML syntax by tokenizing character-by-character
29
+ * Delegates to CSS and JavaScript highlighters for embedded content
30
+ */
31
+ export function highlightHTML(code: string): string {
32
+ if (!code) return "";
33
+
34
+ const result: string[] = [];
35
+ let i = 0;
36
+ let state: HTMLState = "text";
37
+ let buffer = "";
38
+ let currentTagName = "";
39
+ let inClosingTag = false;
40
+ let quoteChar = "";
41
+
42
+ /**
43
+ * Flush the current buffer with appropriate highlighting
44
+ */
45
+ function flushBuffer(className?: string): void {
46
+ if (!buffer) return;
47
+
48
+ if (className) {
49
+ result.push(`<span class="${className}">${escapeHtml(buffer)}</span>`);
50
+ } else {
51
+ result.push(escapeHtml(buffer));
52
+ }
53
+ buffer = "";
54
+ }
55
+
56
+ /**
57
+ * Find the closing tag for style or script
58
+ * Returns the index of the closing tag or -1 if not found
59
+ */
60
+ function findClosingTag(tagName: string, startIndex: number): number {
61
+ const closePattern = new RegExp(`<\\s*/\\s*${tagName}\\s*>`, "i");
62
+ const remaining = code.slice(startIndex);
63
+ const match = remaining.match(closePattern);
64
+ if (match && match.index !== undefined) {
65
+ return startIndex + match.index;
66
+ }
67
+ return -1;
68
+ }
69
+
70
+ while (i < code.length) {
71
+ const char = code[i];
72
+
73
+ // Handle comments: <!-- ... -->
74
+ if (state === "text" && code.slice(i, i + 4) === "<!--") {
75
+ flushBuffer();
76
+ const startIndex = i;
77
+ i += 4; // Skip <!--
78
+
79
+ // Find closing -->
80
+ while (i < code.length) {
81
+ if (code.slice(i, i + 3) === "-->") {
82
+ i += 3; // Include -->
83
+ break;
84
+ }
85
+ i++;
86
+ }
87
+
88
+ const comment = code.slice(startIndex, i);
89
+ result.push(`<span class="syntax-comment">${escapeHtml(comment)}</span>`);
90
+ continue;
91
+ }
92
+
93
+ // Handle DOCTYPE: <!DOCTYPE ...>
94
+ if (state === "text" && code.slice(i, i + 9).toUpperCase() === "<!DOCTYPE") {
95
+ flushBuffer();
96
+ const startIndex = i;
97
+
98
+ // Find closing >
99
+ while (i < code.length && code[i] !== ">") {
100
+ i++;
101
+ }
102
+ if (code[i] === ">") i++; // Include >
103
+
104
+ const doctype = code.slice(startIndex, i);
105
+ result.push(`<span class="syntax-doctype">${escapeHtml(doctype)}</span>`);
106
+ continue;
107
+ }
108
+
109
+ // Handle CDATA sections: <![CDATA[ ... ]]>
110
+ if (state === "text" && code.slice(i, i + 9) === "<![CDATA[") {
111
+ flushBuffer();
112
+ const startIndex = i;
113
+ i += 9; // Skip <![CDATA[
114
+
115
+ // Find closing ]]>
116
+ while (i < code.length) {
117
+ if (code.slice(i, i + 3) === "]]>") {
118
+ i += 3; // Include ]]>
119
+ break;
120
+ }
121
+ i++;
122
+ }
123
+
124
+ const cdata = code.slice(startIndex, i);
125
+ result.push(`<span class="syntax-comment">${escapeHtml(cdata)}</span>`);
126
+ continue;
127
+ }
128
+
129
+ // Handle tag opening: <
130
+ if (state === "text" && char === "<") {
131
+ flushBuffer();
132
+ buffer = "<";
133
+ i++;
134
+
135
+ // Check for closing tag
136
+ if (code[i] === "/") {
137
+ inClosingTag = true;
138
+ buffer += "/";
139
+ i++;
140
+ } else {
141
+ inClosingTag = false;
142
+ }
143
+
144
+ state = "tag-name";
145
+ currentTagName = "";
146
+ continue;
147
+ }
148
+
149
+ // Handle tag name
150
+ if (state === "tag-name") {
151
+ if (/[a-zA-Z0-9-]/.test(char)) {
152
+ buffer += char;
153
+ currentTagName += char.toLowerCase();
154
+ i++;
155
+ continue;
156
+ }
157
+
158
+ // Tag name complete
159
+ flushBuffer("syntax-tag");
160
+
161
+ // Check for style or script tags (only opening tags)
162
+ if (!inClosingTag && (currentTagName === "style" || currentTagName === "script")) {
163
+ // Find the end of the opening tag
164
+ let tagEndIndex = i;
165
+ while (tagEndIndex < code.length && code[tagEndIndex] !== ">") {
166
+ tagEndIndex++;
167
+ }
168
+ tagEndIndex++; // Include >
169
+
170
+ // Highlight the rest of the opening tag (attributes)
171
+ const tagRemainder = code.slice(i, tagEndIndex);
172
+ result.push(highlightHTMLAttributes(tagRemainder));
173
+ i = tagEndIndex;
174
+
175
+ // Find the closing tag
176
+ const closingTagIndex = findClosingTag(currentTagName, i);
177
+
178
+ if (closingTagIndex !== -1) {
179
+ // Extract and highlight the embedded content
180
+ const embeddedContent = code.slice(i, closingTagIndex);
181
+
182
+ if (currentTagName === "style") {
183
+ result.push(highlightCSS(embeddedContent));
184
+ } else if (currentTagName === "script") {
185
+ result.push(highlightJavaScript(embeddedContent));
186
+ }
187
+
188
+ i = closingTagIndex;
189
+ }
190
+
191
+ state = "text";
192
+ continue;
193
+ }
194
+
195
+ if (/\s/.test(char)) {
196
+ result.push(escapeHtml(char));
197
+ i++;
198
+ state = "attribute-name";
199
+ } else if (char === ">") {
200
+ result.push(`<span class="syntax-tag">${escapeHtml(char)}</span>`);
201
+ i++;
202
+ state = "text";
203
+ } else if (char === "/" && code[i + 1] === ">") {
204
+ result.push(`<span class="syntax-tag">/&gt;</span>`);
205
+ i += 2;
206
+ state = "text";
207
+ } else {
208
+ state = "attribute-name";
209
+ }
210
+ continue;
211
+ }
212
+
213
+ // Handle attribute name
214
+ if (state === "attribute-name") {
215
+ if (/\s/.test(char)) {
216
+ flushBuffer("syntax-attribute");
217
+ result.push(escapeHtml(char));
218
+ i++;
219
+ continue;
220
+ }
221
+
222
+ if (char === "=") {
223
+ flushBuffer("syntax-attribute");
224
+ result.push(`<span class="syntax-punctuation">=</span>`);
225
+ i++;
226
+ state = "attribute-equals";
227
+ continue;
228
+ }
229
+
230
+ if (char === ">") {
231
+ flushBuffer("syntax-attribute");
232
+ result.push(`<span class="syntax-tag">&gt;</span>`);
233
+ i++;
234
+ state = "text";
235
+ continue;
236
+ }
237
+
238
+ if (char === "/" && code[i + 1] === ">") {
239
+ flushBuffer("syntax-attribute");
240
+ result.push(`<span class="syntax-tag">/&gt;</span>`);
241
+ i += 2;
242
+ state = "text";
243
+ continue;
244
+ }
245
+
246
+ if (/[a-zA-Z0-9\-_:@.]/.test(char)) {
247
+ buffer += char;
248
+ i++;
249
+ continue;
250
+ }
251
+
252
+ // Unknown character in attribute context
253
+ flushBuffer("syntax-attribute");
254
+ result.push(escapeHtml(char));
255
+ i++;
256
+ continue;
257
+ }
258
+
259
+ // Handle after equals sign (before attribute value)
260
+ if (state === "attribute-equals") {
261
+ if (/\s/.test(char)) {
262
+ result.push(escapeHtml(char));
263
+ i++;
264
+ continue;
265
+ }
266
+
267
+ if (char === '"' || char === "'") {
268
+ quoteChar = char;
269
+ buffer = char;
270
+ i++;
271
+ state = "attribute-value";
272
+ continue;
273
+ }
274
+
275
+ // Unquoted attribute value
276
+ if (char !== ">" && char !== "/") {
277
+ buffer = "";
278
+ state = "attribute-value";
279
+ quoteChar = "";
280
+ continue;
281
+ }
282
+
283
+ // No value, go back to attribute name state
284
+ state = "attribute-name";
285
+ continue;
286
+ }
287
+
288
+ // Handle attribute value
289
+ if (state === "attribute-value") {
290
+ if (quoteChar) {
291
+ // Quoted attribute value
292
+ buffer += char;
293
+ i++;
294
+
295
+ if (char === quoteChar) {
296
+ flushBuffer("syntax-string");
297
+ state = "attribute-name";
298
+ quoteChar = "";
299
+ }
300
+ continue;
301
+ } else {
302
+ // Unquoted attribute value
303
+ if (/\s/.test(char) || char === ">" || (char === "/" && code[i + 1] === ">")) {
304
+ flushBuffer("syntax-string");
305
+ state = "attribute-name";
306
+ continue;
307
+ }
308
+
309
+ buffer += char;
310
+ i++;
311
+ continue;
312
+ }
313
+ }
314
+
315
+ // Default text handling
316
+ if (state === "text") {
317
+ if (char === "<") {
318
+ // Will be handled at the top of the loop
319
+ continue;
320
+ }
321
+
322
+ buffer += char;
323
+ i++;
324
+ } else {
325
+ // Unknown state, just advance
326
+ buffer += char;
327
+ i++;
328
+ }
329
+ }
330
+
331
+ // Flush any remaining buffer
332
+ if (buffer) {
333
+ if (state === "text") {
334
+ flushBuffer();
335
+ } else if (state === "tag-name") {
336
+ flushBuffer("syntax-tag");
337
+ } else if (state === "attribute-name") {
338
+ flushBuffer("syntax-attribute");
339
+ } else if (state === "attribute-value") {
340
+ flushBuffer("syntax-string");
341
+ } else {
342
+ flushBuffer();
343
+ }
344
+ }
345
+
346
+ return result.join("");
347
+ }
348
+
349
+ /**
350
+ * Highlight HTML attributes (everything between tag name and >)
351
+ * This is a helper for processing the remainder of a tag after the name
352
+ */
353
+ function highlightHTMLAttributes(code: string): string {
354
+ if (!code) return "";
355
+
356
+ const result: string[] = [];
357
+ let i = 0;
358
+ let state: "space" | "attribute-name" | "equals" | "value" = "space";
359
+ let buffer = "";
360
+ let quoteChar = "";
361
+
362
+ while (i < code.length) {
363
+ const char = code[i];
364
+
365
+ // Handle end of tag
366
+ if (char === ">" || (char === "/" && code[i + 1] === ">")) {
367
+ // Flush buffer
368
+ if (buffer) {
369
+ if (state === "attribute-name") {
370
+ result.push(`<span class="syntax-attribute">${escapeHtml(buffer)}</span>`);
371
+ } else if (state === "value") {
372
+ result.push(`<span class="syntax-string">${escapeHtml(buffer)}</span>`);
373
+ } else {
374
+ result.push(escapeHtml(buffer));
375
+ }
376
+ buffer = "";
377
+ }
378
+
379
+ if (char === "/" && code[i + 1] === ">") {
380
+ result.push(`<span class="syntax-tag">/&gt;</span>`);
381
+ i += 2;
382
+ } else {
383
+ result.push(`<span class="syntax-tag">&gt;</span>`);
384
+ i++;
385
+ }
386
+ continue;
387
+ }
388
+
389
+ // Handle whitespace
390
+ if (/\s/.test(char) && state !== "value") {
391
+ if (buffer && state === "attribute-name") {
392
+ result.push(`<span class="syntax-attribute">${escapeHtml(buffer)}</span>`);
393
+ buffer = "";
394
+ }
395
+ result.push(escapeHtml(char));
396
+ i++;
397
+ state = "space";
398
+ continue;
399
+ }
400
+
401
+ // Handle equals sign
402
+ if (char === "=" && state !== "value") {
403
+ if (buffer) {
404
+ result.push(`<span class="syntax-attribute">${escapeHtml(buffer)}</span>`);
405
+ buffer = "";
406
+ }
407
+ result.push(`<span class="syntax-punctuation">=</span>`);
408
+ i++;
409
+ state = "equals";
410
+ continue;
411
+ }
412
+
413
+ // Handle quote start
414
+ if ((char === '"' || char === "'") && (state === "equals" || state === "space")) {
415
+ quoteChar = char;
416
+ buffer = char;
417
+ i++;
418
+ state = "value";
419
+ continue;
420
+ }
421
+
422
+ // Handle quoted value
423
+ if (state === "value" && quoteChar) {
424
+ buffer += char;
425
+ if (char === quoteChar) {
426
+ result.push(`<span class="syntax-string">${escapeHtml(buffer)}</span>`);
427
+ buffer = "";
428
+ quoteChar = "";
429
+ state = "space";
430
+ }
431
+ i++;
432
+ continue;
433
+ }
434
+
435
+ // Handle unquoted value
436
+ if (state === "value" && !quoteChar) {
437
+ if (/\s/.test(char)) {
438
+ result.push(`<span class="syntax-string">${escapeHtml(buffer)}</span>`);
439
+ buffer = "";
440
+ result.push(escapeHtml(char));
441
+ state = "space";
442
+ i++;
443
+ continue;
444
+ }
445
+ buffer += char;
446
+ i++;
447
+ continue;
448
+ }
449
+
450
+ // Attribute name
451
+ if (state === "space" || state === "attribute-name") {
452
+ buffer += char;
453
+ state = "attribute-name";
454
+ i++;
455
+ continue;
456
+ }
457
+
458
+ // Unquoted value after equals
459
+ if (state === "equals") {
460
+ buffer += char;
461
+ state = "value";
462
+ i++;
463
+ continue;
464
+ }
465
+
466
+ // Default
467
+ result.push(escapeHtml(char));
468
+ i++;
469
+ }
470
+
471
+ // Flush remaining buffer
472
+ if (buffer) {
473
+ if (state === "attribute-name") {
474
+ result.push(`<span class="syntax-attribute">${escapeHtml(buffer)}</span>`);
475
+ } else if (state === "value") {
476
+ result.push(`<span class="syntax-string">${escapeHtml(buffer)}</span>`);
477
+ } else {
478
+ result.push(escapeHtml(buffer));
479
+ }
480
+ }
481
+
482
+ return result.join("");
483
+ }