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,388 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { useLinks } from "./useLinks";
3
+ import { createTestEditor, TestEditorResult } from "../../../test/helpers/editorTestUtils";
4
+
5
+ // Mock getBoundingClientRect for Range since jsdom doesn't implement it
6
+ Range.prototype.getBoundingClientRect = vi.fn(() => ({
7
+ x: 0,
8
+ y: 0,
9
+ width: 100,
10
+ height: 20,
11
+ top: 0,
12
+ right: 100,
13
+ bottom: 20,
14
+ left: 0,
15
+ toJSON: () => ({})
16
+ }));
17
+
18
+ describe("useLinks", () => {
19
+ let editor: TestEditorResult;
20
+ let onContentChange: ReturnType<typeof vi.fn>;
21
+
22
+ afterEach(() => {
23
+ if (editor) {
24
+ editor.destroy();
25
+ }
26
+ vi.restoreAllMocks();
27
+ });
28
+
29
+ function createLinks() {
30
+ return useLinks({
31
+ contentRef: editor.contentRef,
32
+ onContentChange
33
+ });
34
+ }
35
+
36
+ describe("insertLink", () => {
37
+ describe("with selected text", () => {
38
+ beforeEach(() => {
39
+ onContentChange = vi.fn();
40
+ });
41
+
42
+ it("wraps selected text in anchor tag with prompted URL", () => {
43
+ editor = createTestEditor("<p>Hello world</p>");
44
+ vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
45
+ const links = createLinks();
46
+ editor.selectInBlock(0, 0, 5); // Select "Hello"
47
+
48
+ links.insertLink();
49
+
50
+ expect(editor.getHtml()).toBe('<p><a href="https://example.com" target="_blank" rel="noopener noreferrer">Hello</a> world</p>');
51
+ expect(onContentChange).toHaveBeenCalled();
52
+ });
53
+
54
+ it("wraps middle text in anchor tag", () => {
55
+ editor = createTestEditor("<p>Hello beautiful world</p>");
56
+ vi.spyOn(window, "prompt").mockReturnValue("https://test.com");
57
+ const links = createLinks();
58
+ editor.selectInBlock(0, 6, 15); // Select "beautiful"
59
+
60
+ links.insertLink();
61
+
62
+ expect(editor.getHtml()).toBe('<p>Hello <a href="https://test.com" target="_blank" rel="noopener noreferrer">beautiful</a> world</p>');
63
+ });
64
+
65
+ it("trims whitespace from URL", () => {
66
+ editor = createTestEditor("<p>Hello world</p>");
67
+ vi.spyOn(window, "prompt").mockReturnValue(" https://example.com ");
68
+ const links = createLinks();
69
+ editor.selectInBlock(0, 0, 5);
70
+
71
+ links.insertLink();
72
+
73
+ expect(editor.getHtml()).toBe('<p><a href="https://example.com" target="_blank" rel="noopener noreferrer">Hello</a> world</p>');
74
+ });
75
+
76
+ it("does nothing when user cancels prompt", () => {
77
+ editor = createTestEditor("<p>Hello world</p>");
78
+ vi.spyOn(window, "prompt").mockReturnValue(null);
79
+ const links = createLinks();
80
+ editor.selectInBlock(0, 0, 5);
81
+
82
+ links.insertLink();
83
+
84
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
85
+ expect(onContentChange).not.toHaveBeenCalled();
86
+ });
87
+
88
+ it("does nothing when user enters empty URL", () => {
89
+ editor = createTestEditor("<p>Hello world</p>");
90
+ vi.spyOn(window, "prompt").mockReturnValue("");
91
+ const links = createLinks();
92
+ editor.selectInBlock(0, 0, 5);
93
+
94
+ links.insertLink();
95
+
96
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
97
+ expect(onContentChange).not.toHaveBeenCalled();
98
+ });
99
+
100
+ it("does nothing when user enters whitespace-only URL", () => {
101
+ editor = createTestEditor("<p>Hello world</p>");
102
+ vi.spyOn(window, "prompt").mockReturnValue(" ");
103
+ const links = createLinks();
104
+ editor.selectInBlock(0, 0, 5);
105
+
106
+ links.insertLink();
107
+
108
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
109
+ expect(onContentChange).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it("wraps content and link is properly created", () => {
113
+ editor = createTestEditor("<p>Hello world</p>");
114
+ vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
115
+ const links = createLinks();
116
+ editor.selectInBlock(0, 0, 5);
117
+
118
+ links.insertLink();
119
+
120
+ // Verify the link was created with correct text
121
+ const linkEl = editor.container.querySelector("a");
122
+ expect(linkEl).not.toBeNull();
123
+ expect(linkEl?.textContent).toBe("Hello");
124
+ expect(linkEl?.getAttribute("href")).toBe("https://example.com");
125
+ });
126
+ });
127
+
128
+ describe("without selection (cursor only)", () => {
129
+ beforeEach(() => {
130
+ onContentChange = vi.fn();
131
+ });
132
+
133
+ it("inserts link with URL as text when no selection", () => {
134
+ editor = createTestEditor("<p>Hello world</p>");
135
+ vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
136
+ const links = createLinks();
137
+ editor.setCursorInBlock(0, 6); // After "Hello "
138
+
139
+ links.insertLink();
140
+
141
+ expect(editor.getHtml()).toContain('<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>');
142
+ expect(onContentChange).toHaveBeenCalled();
143
+ });
144
+
145
+ it("does nothing when user cancels prompt", () => {
146
+ editor = createTestEditor("<p>Hello world</p>");
147
+ vi.spyOn(window, "prompt").mockReturnValue(null);
148
+ const links = createLinks();
149
+ editor.setCursorInBlock(0, 6);
150
+
151
+ links.insertLink();
152
+
153
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
154
+ expect(onContentChange).not.toHaveBeenCalled();
155
+ });
156
+
157
+ it("does nothing when user enters empty URL", () => {
158
+ editor = createTestEditor("<p>Hello world</p>");
159
+ vi.spyOn(window, "prompt").mockReturnValue("");
160
+ const links = createLinks();
161
+ editor.setCursorInBlock(0, 6);
162
+
163
+ links.insertLink();
164
+
165
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
166
+ expect(onContentChange).not.toHaveBeenCalled();
167
+ });
168
+
169
+ it("inserts link and link is properly created", () => {
170
+ editor = createTestEditor("<p>Hello world</p>");
171
+ vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
172
+ const links = createLinks();
173
+ editor.setCursorInBlock(0, 6);
174
+
175
+ links.insertLink();
176
+
177
+ // Verify the link was created with URL as text
178
+ const linkEl = editor.container.querySelector("a");
179
+ expect(linkEl).not.toBeNull();
180
+ expect(linkEl?.textContent).toBe("https://example.com");
181
+ expect(linkEl?.getAttribute("href")).toBe("https://example.com");
182
+ });
183
+ });
184
+
185
+ describe("editing existing link", () => {
186
+ beforeEach(() => {
187
+ onContentChange = vi.fn();
188
+ });
189
+
190
+ it("prompts with current URL prefilled when cursor is in link", () => {
191
+ editor = createTestEditor('<p><a href="https://old.com">Hello</a> world</p>');
192
+ const promptSpy = vi.spyOn(window, "prompt").mockReturnValue("https://new.com");
193
+ const links = createLinks();
194
+ const linkEl = editor.container.querySelector("a");
195
+ editor.setCursor(linkEl!.firstChild!, 3); // Inside "Hello"
196
+
197
+ links.insertLink();
198
+
199
+ expect(promptSpy).toHaveBeenCalledWith("Edit link URL:", "https://old.com");
200
+ });
201
+
202
+ it("updates URL when user provides new URL", () => {
203
+ editor = createTestEditor('<p><a href="https://old.com">Hello</a> world</p>');
204
+ vi.spyOn(window, "prompt").mockReturnValue("https://new.com");
205
+ const links = createLinks();
206
+ const linkEl = editor.container.querySelector("a");
207
+ editor.setCursor(linkEl!.firstChild!, 3);
208
+
209
+ links.insertLink();
210
+
211
+ expect(editor.getHtml()).toBe('<p><a href="https://new.com">Hello</a> world</p>');
212
+ expect(onContentChange).toHaveBeenCalled();
213
+ });
214
+
215
+ it("removes link when user enters empty URL", () => {
216
+ editor = createTestEditor('<p><a href="https://example.com">Hello</a> world</p>');
217
+ vi.spyOn(window, "prompt").mockReturnValue("");
218
+ const links = createLinks();
219
+ const linkEl = editor.container.querySelector("a");
220
+ editor.setCursor(linkEl!.firstChild!, 3);
221
+
222
+ links.insertLink();
223
+
224
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
225
+ expect(onContentChange).toHaveBeenCalled();
226
+ });
227
+
228
+ it("does nothing when user cancels prompt on existing link", () => {
229
+ editor = createTestEditor('<p><a href="https://example.com">Hello</a> world</p>');
230
+ vi.spyOn(window, "prompt").mockReturnValue(null);
231
+ const links = createLinks();
232
+ const linkEl = editor.container.querySelector("a");
233
+ editor.setCursor(linkEl!.firstChild!, 3);
234
+
235
+ links.insertLink();
236
+
237
+ expect(editor.getHtml()).toBe('<p><a href="https://example.com">Hello</a> world</p>');
238
+ expect(onContentChange).not.toHaveBeenCalled();
239
+ });
240
+
241
+ it("handles link with empty href attribute", () => {
242
+ editor = createTestEditor('<p><a href="">Hello</a> world</p>');
243
+ const promptSpy = vi.spyOn(window, "prompt").mockReturnValue("https://new.com");
244
+ const links = createLinks();
245
+ const linkEl = editor.container.querySelector("a");
246
+ editor.setCursor(linkEl!.firstChild!, 3);
247
+
248
+ links.insertLink();
249
+
250
+ expect(promptSpy).toHaveBeenCalledWith("Edit link URL:", "");
251
+ expect(editor.getHtml()).toBe('<p><a href="https://new.com">Hello</a> world</p>');
252
+ });
253
+ });
254
+ });
255
+
256
+ describe("isInLink", () => {
257
+ beforeEach(() => {
258
+ onContentChange = vi.fn();
259
+ });
260
+
261
+ it("returns true when cursor is inside a link", () => {
262
+ editor = createTestEditor('<p><a href="https://example.com">Hello</a> world</p>');
263
+ const links = createLinks();
264
+ const linkEl = editor.container.querySelector("a");
265
+ editor.setCursor(linkEl!.firstChild!, 3);
266
+
267
+ expect(links.isInLink()).toBe(true);
268
+ });
269
+
270
+ it("returns false when cursor is outside a link", () => {
271
+ editor = createTestEditor('<p><a href="https://example.com">Hello</a> world</p>');
272
+ const links = createLinks();
273
+ editor.setCursorInBlock(0, 8); // In " world"
274
+
275
+ expect(links.isInLink()).toBe(false);
276
+ });
277
+
278
+ it("returns false when there are no links in content", () => {
279
+ editor = createTestEditor("<p>Hello world</p>");
280
+ const links = createLinks();
281
+ editor.setCursorInBlock(0, 5);
282
+
283
+ expect(links.isInLink()).toBe(false);
284
+ });
285
+
286
+ it("returns false when contentRef is null", () => {
287
+ editor = createTestEditor("<p>Hello world</p>");
288
+ const links = useLinks({
289
+ contentRef: { value: null },
290
+ onContentChange
291
+ });
292
+
293
+ expect(links.isInLink()).toBe(false);
294
+ });
295
+
296
+ it("returns false when no selection exists", () => {
297
+ editor = createTestEditor('<p><a href="https://example.com">Hello</a> world</p>');
298
+ const links = createLinks();
299
+
300
+ // Clear any selection
301
+ window.getSelection()?.removeAllRanges();
302
+
303
+ expect(links.isInLink()).toBe(false);
304
+ });
305
+ });
306
+
307
+ describe("edge cases", () => {
308
+ beforeEach(() => {
309
+ onContentChange = vi.fn();
310
+ });
311
+
312
+ it("does nothing when contentRef is null", () => {
313
+ editor = createTestEditor("<p>Hello world</p>");
314
+ const links = useLinks({
315
+ contentRef: { value: null },
316
+ onContentChange
317
+ });
318
+
319
+ links.insertLink();
320
+
321
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
322
+ expect(onContentChange).not.toHaveBeenCalled();
323
+ });
324
+
325
+ it("does nothing when selection is outside content area", () => {
326
+ editor = createTestEditor("<p>Hello world</p>");
327
+ const links = createLinks();
328
+
329
+ // Create a selection outside the editor
330
+ const externalDiv = document.createElement("div");
331
+ externalDiv.textContent = "External text";
332
+ document.body.appendChild(externalDiv);
333
+
334
+ const range = document.createRange();
335
+ range.selectNodeContents(externalDiv);
336
+ const sel = window.getSelection();
337
+ sel?.removeAllRanges();
338
+ sel?.addRange(range);
339
+
340
+ links.insertLink();
341
+
342
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
343
+ expect(onContentChange).not.toHaveBeenCalled();
344
+
345
+ externalDiv.remove();
346
+ });
347
+
348
+ it("does nothing when no selection exists", () => {
349
+ editor = createTestEditor("<p>Hello world</p>");
350
+ const links = createLinks();
351
+
352
+ // Clear any selection
353
+ window.getSelection()?.removeAllRanges();
354
+
355
+ links.insertLink();
356
+
357
+ expect(editor.getHtml()).toBe("<p>Hello world</p>");
358
+ expect(onContentChange).not.toHaveBeenCalled();
359
+ });
360
+
361
+ it("handles nested elements within selection", () => {
362
+ editor = createTestEditor("<p><strong>Hello</strong> world</p>");
363
+ vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
364
+ const links = createLinks();
365
+ editor.selectInBlock(0, 0, 5); // Select "Hello" (inside strong)
366
+
367
+ links.insertLink();
368
+
369
+ // The link should wrap the strong element
370
+ expect(editor.getHtml()).toContain("https://example.com");
371
+ expect(onContentChange).toHaveBeenCalled();
372
+ });
373
+ });
374
+
375
+ describe("return type", () => {
376
+ beforeEach(() => {
377
+ onContentChange = vi.fn();
378
+ editor = createTestEditor("<p>test</p>");
379
+ });
380
+
381
+ it("returns insertLink and isInLink functions", () => {
382
+ const links = createLinks();
383
+
384
+ expect(typeof links.insertLink).toBe("function");
385
+ expect(typeof links.isInLink).toBe("function");
386
+ });
387
+ });
388
+ });