quasar-ui-danx 0.5.1 → 0.5.3
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 +13869 -12976
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +159 -151
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Utility/Buttons/ActionButton.vue +15 -5
- package/src/components/Utility/Code/CodeViewer.vue +10 -2
- package/src/components/Utility/Code/CodeViewerFooter.vue +2 -0
- package/src/components/Utility/Code/MarkdownContent.vue +31 -163
- package/src/components/Utility/Files/FilePreview.vue +2 -2
- package/src/components/Utility/Markdown/MarkdownEditor.vue +7 -2
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +69 -8
- package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +59 -33
- package/src/composables/markdown/features/useLinks.spec.ts +29 -10
- package/src/composables/markdown/useMarkdownEditor.ts +16 -7
- package/src/composables/useCodeFormat.ts +17 -10
- package/src/composables/useCodeViewerCollapse.ts +7 -0
- package/src/composables/useFileNavigation.ts +5 -1
- 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/index.ts +42 -4
- package/src/helpers/formats/markdown/linePatterns.spec.ts +7 -4
- package/src/helpers/formats/markdown/parseInline.ts +26 -13
- package/src/helpers/routes.ts +3 -1
- 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 +81 -0
- package/src/test/highlighters.test.ts +153 -0
- package/src/types/widgets.d.ts +2 -2
- package/vite.config.js +5 -1
|
@@ -26,6 +26,29 @@ describe("useCodeBlocks", () => {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Helper to verify code block wrapper structure
|
|
31
|
+
* The new implementation creates wrapper divs with mount points instead of <pre><code>
|
|
32
|
+
*/
|
|
33
|
+
function expectCodeBlockWrapper(content: string, language = "") {
|
|
34
|
+
const wrapper = editor.container.querySelector(".code-block-wrapper");
|
|
35
|
+
expect(wrapper).not.toBeNull();
|
|
36
|
+
expect(wrapper?.getAttribute("contenteditable")).toBe("false");
|
|
37
|
+
expect(wrapper?.hasAttribute("data-code-block-id")).toBe(true);
|
|
38
|
+
|
|
39
|
+
const mountPoint = wrapper?.querySelector(".code-viewer-mount-point");
|
|
40
|
+
expect(mountPoint).not.toBeNull();
|
|
41
|
+
expect(mountPoint?.getAttribute("data-content")).toBe(content);
|
|
42
|
+
expect(mountPoint?.getAttribute("data-language")).toBe(language);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Helper to check if a code block wrapper exists
|
|
47
|
+
*/
|
|
48
|
+
function hasCodeBlockWrapper(): boolean {
|
|
49
|
+
return editor.container.querySelector(".code-block-wrapper") !== null;
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
describe("toggleCodeBlock", () => {
|
|
30
53
|
beforeEach(() => {
|
|
31
54
|
onContentChange = vi.fn();
|
|
@@ -38,7 +61,7 @@ describe("useCodeBlocks", () => {
|
|
|
38
61
|
|
|
39
62
|
codeBlocks.toggleCodeBlock();
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
expectCodeBlockWrapper("Hello world");
|
|
42
65
|
expect(onContentChange).toHaveBeenCalled();
|
|
43
66
|
});
|
|
44
67
|
|
|
@@ -49,7 +72,7 @@ describe("useCodeBlocks", () => {
|
|
|
49
72
|
|
|
50
73
|
codeBlocks.toggleCodeBlock();
|
|
51
74
|
|
|
52
|
-
|
|
75
|
+
expectCodeBlockWrapper("Hello world");
|
|
53
76
|
expect(onContentChange).toHaveBeenCalled();
|
|
54
77
|
});
|
|
55
78
|
|
|
@@ -60,7 +83,7 @@ describe("useCodeBlocks", () => {
|
|
|
60
83
|
|
|
61
84
|
codeBlocks.toggleCodeBlock();
|
|
62
85
|
|
|
63
|
-
|
|
86
|
+
expectCodeBlockWrapper("Hello world");
|
|
64
87
|
expect(onContentChange).toHaveBeenCalled();
|
|
65
88
|
});
|
|
66
89
|
|
|
@@ -71,7 +94,7 @@ describe("useCodeBlocks", () => {
|
|
|
71
94
|
|
|
72
95
|
codeBlocks.toggleCodeBlock();
|
|
73
96
|
|
|
74
|
-
|
|
97
|
+
expectCodeBlockWrapper("Hello world");
|
|
75
98
|
expect(onContentChange).toHaveBeenCalled();
|
|
76
99
|
});
|
|
77
100
|
|
|
@@ -97,7 +120,7 @@ describe("useCodeBlocks", () => {
|
|
|
97
120
|
|
|
98
121
|
codeBlocks.toggleCodeBlock();
|
|
99
122
|
|
|
100
|
-
|
|
123
|
+
expectCodeBlockWrapper("const x = 1;");
|
|
101
124
|
});
|
|
102
125
|
|
|
103
126
|
it("preserves content when toggling from code block", () => {
|
|
@@ -128,7 +151,7 @@ describe("useCodeBlocks", () => {
|
|
|
128
151
|
|
|
129
152
|
codeBlocks.toggleCodeBlock();
|
|
130
153
|
|
|
131
|
-
|
|
154
|
+
expectCodeBlockWrapper("");
|
|
132
155
|
});
|
|
133
156
|
|
|
134
157
|
it("does not convert list items directly", () => {
|
|
@@ -152,17 +175,18 @@ describe("useCodeBlocks", () => {
|
|
|
152
175
|
onContentChange = vi.fn();
|
|
153
176
|
});
|
|
154
177
|
|
|
155
|
-
it("
|
|
178
|
+
it("does not convert just ``` without language (requires language identifier)", () => {
|
|
179
|
+
// Implementation intentionally requires at least one character in language identifier
|
|
180
|
+
// to avoid triggering on just "```" before user finishes typing the language
|
|
156
181
|
editor = createTestEditor("<p>```</p>");
|
|
157
182
|
const codeBlocks = createCodeBlocks();
|
|
158
183
|
editor.setCursorInBlock(0, 3);
|
|
159
184
|
|
|
160
185
|
const result = codeBlocks.checkAndConvertCodeBlockPattern();
|
|
161
186
|
|
|
162
|
-
expect(result).toBe(
|
|
163
|
-
|
|
164
|
-
expect(
|
|
165
|
-
expect(onContentChange).toHaveBeenCalled();
|
|
187
|
+
expect(result).toBe(false);
|
|
188
|
+
expect(editor.getHtml()).toBe("<p>```</p>");
|
|
189
|
+
expect(onContentChange).not.toHaveBeenCalled();
|
|
166
190
|
});
|
|
167
191
|
|
|
168
192
|
it("converts ```javascript to code block with language", () => {
|
|
@@ -173,7 +197,7 @@ describe("useCodeBlocks", () => {
|
|
|
173
197
|
const result = codeBlocks.checkAndConvertCodeBlockPattern();
|
|
174
198
|
|
|
175
199
|
expect(result).toBe(true);
|
|
176
|
-
|
|
200
|
+
expectCodeBlockWrapper("", "javascript");
|
|
177
201
|
expect(onContentChange).toHaveBeenCalled();
|
|
178
202
|
});
|
|
179
203
|
|
|
@@ -185,7 +209,7 @@ describe("useCodeBlocks", () => {
|
|
|
185
209
|
const result = codeBlocks.checkAndConvertCodeBlockPattern();
|
|
186
210
|
|
|
187
211
|
expect(result).toBe(true);
|
|
188
|
-
|
|
212
|
+
expectCodeBlockWrapper("", "python");
|
|
189
213
|
});
|
|
190
214
|
|
|
191
215
|
it("converts ```typescript to code block with language", () => {
|
|
@@ -196,7 +220,7 @@ describe("useCodeBlocks", () => {
|
|
|
196
220
|
const result = codeBlocks.checkAndConvertCodeBlockPattern();
|
|
197
221
|
|
|
198
222
|
expect(result).toBe(true);
|
|
199
|
-
|
|
223
|
+
expectCodeBlockWrapper("", "typescript");
|
|
200
224
|
});
|
|
201
225
|
|
|
202
226
|
it("returns false when no pattern is present", () => {
|
|
@@ -237,7 +261,7 @@ describe("useCodeBlocks", () => {
|
|
|
237
261
|
expect(onContentChange).not.toHaveBeenCalled();
|
|
238
262
|
});
|
|
239
263
|
|
|
240
|
-
it("
|
|
264
|
+
it("converts heading with code fence pattern", () => {
|
|
241
265
|
editor = createTestEditor("<h1>```javascript</h1>");
|
|
242
266
|
const codeBlocks = createCodeBlocks();
|
|
243
267
|
editor.setCursorInBlock(0, 13);
|
|
@@ -246,7 +270,7 @@ describe("useCodeBlocks", () => {
|
|
|
246
270
|
|
|
247
271
|
// Headings ARE convertible blocks, so this should convert
|
|
248
272
|
expect(result).toBe(true);
|
|
249
|
-
|
|
273
|
+
expectCodeBlockWrapper("", "javascript");
|
|
250
274
|
});
|
|
251
275
|
|
|
252
276
|
it("converts DIV elements (browser default)", () => {
|
|
@@ -257,7 +281,7 @@ describe("useCodeBlocks", () => {
|
|
|
257
281
|
const result = codeBlocks.checkAndConvertCodeBlockPattern();
|
|
258
282
|
|
|
259
283
|
expect(result).toBe(true);
|
|
260
|
-
|
|
284
|
+
expectCodeBlockWrapper("", "rust");
|
|
261
285
|
});
|
|
262
286
|
|
|
263
287
|
it("does not convert list items", () => {
|
|
@@ -275,25 +299,19 @@ describe("useCodeBlocks", () => {
|
|
|
275
299
|
expect(onContentChange).not.toHaveBeenCalled();
|
|
276
300
|
});
|
|
277
301
|
|
|
278
|
-
it("
|
|
302
|
+
it("creates wrapper structure after conversion", () => {
|
|
279
303
|
editor = createTestEditor("<p>```javascript</p>");
|
|
280
304
|
const codeBlocks = createCodeBlocks();
|
|
281
305
|
editor.setCursorInBlock(0, 13);
|
|
282
306
|
|
|
283
307
|
codeBlocks.checkAndConvertCodeBlockPattern();
|
|
284
308
|
|
|
285
|
-
// Verify
|
|
286
|
-
|
|
287
|
-
expect(sel).not.toBeNull();
|
|
288
|
-
expect(sel?.rangeCount).toBe(1);
|
|
289
|
-
|
|
290
|
-
const range = sel?.getRangeAt(0);
|
|
291
|
-
const code = editor.container.querySelector("code");
|
|
309
|
+
// Verify wrapper structure is created
|
|
310
|
+
expectCodeBlockWrapper("", "javascript");
|
|
292
311
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
expect(range?.startOffset).toBe(1);
|
|
312
|
+
// Verify the wrapper has an id that can be used for mounting
|
|
313
|
+
const wrapper = editor.container.querySelector(".code-block-wrapper");
|
|
314
|
+
expect(wrapper?.getAttribute("data-code-block-id")).toBeTruthy();
|
|
297
315
|
});
|
|
298
316
|
|
|
299
317
|
it("cursor anchor is stripped during markdown conversion", () => {
|
|
@@ -761,7 +779,15 @@ describe("useCodeBlocks", () => {
|
|
|
761
779
|
|
|
762
780
|
codeBlocks.toggleCodeBlock();
|
|
763
781
|
|
|
764
|
-
|
|
782
|
+
// Verify only the second block was converted
|
|
783
|
+
const html = editor.getHtml();
|
|
784
|
+
expect(html).toContain("<p>First</p>");
|
|
785
|
+
expect(html).toContain("<p>Third</p>");
|
|
786
|
+
expect(html).toContain("code-block-wrapper");
|
|
787
|
+
|
|
788
|
+
// Verify the wrapper has the correct content
|
|
789
|
+
const mountPoint = editor.container.querySelector(".code-viewer-mount-point");
|
|
790
|
+
expect(mountPoint?.getAttribute("data-content")).toBe("Second");
|
|
765
791
|
});
|
|
766
792
|
|
|
767
793
|
it("handles multiline content in code block", () => {
|
|
@@ -771,9 +797,9 @@ describe("useCodeBlocks", () => {
|
|
|
771
797
|
|
|
772
798
|
codeBlocks.toggleCodeBlock();
|
|
773
799
|
|
|
774
|
-
// Content should be preserved
|
|
775
|
-
const
|
|
776
|
-
expect(
|
|
800
|
+
// Content should be preserved in the data-content attribute
|
|
801
|
+
const mountPoint = editor.container.querySelector(".code-viewer-mount-point");
|
|
802
|
+
expect(mountPoint?.getAttribute("data-content")).toBe("Line 1\nLine 2\nLine 3");
|
|
777
803
|
});
|
|
778
804
|
});
|
|
779
805
|
});
|
|
@@ -2,6 +2,19 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
|
2
2
|
import { useLinks } from "./useLinks";
|
|
3
3
|
import { createTestEditor, TestEditorResult } from "../../../test/helpers/editorTestUtils";
|
|
4
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
|
+
|
|
5
18
|
describe("useLinks", () => {
|
|
6
19
|
let editor: TestEditorResult;
|
|
7
20
|
let onContentChange: ReturnType<typeof vi.fn>;
|
|
@@ -34,7 +47,7 @@ describe("useLinks", () => {
|
|
|
34
47
|
|
|
35
48
|
links.insertLink();
|
|
36
49
|
|
|
37
|
-
expect(editor.getHtml()).toBe('<p><a href="https://example.com">Hello</a> world</p>');
|
|
50
|
+
expect(editor.getHtml()).toBe('<p><a href="https://example.com" target="_blank" rel="noopener noreferrer">Hello</a> world</p>');
|
|
38
51
|
expect(onContentChange).toHaveBeenCalled();
|
|
39
52
|
});
|
|
40
53
|
|
|
@@ -46,7 +59,7 @@ describe("useLinks", () => {
|
|
|
46
59
|
|
|
47
60
|
links.insertLink();
|
|
48
61
|
|
|
49
|
-
expect(editor.getHtml()).toBe('<p>Hello <a href="https://test.com">beautiful</a> world</p>');
|
|
62
|
+
expect(editor.getHtml()).toBe('<p>Hello <a href="https://test.com" target="_blank" rel="noopener noreferrer">beautiful</a> world</p>');
|
|
50
63
|
});
|
|
51
64
|
|
|
52
65
|
it("trims whitespace from URL", () => {
|
|
@@ -57,7 +70,7 @@ describe("useLinks", () => {
|
|
|
57
70
|
|
|
58
71
|
links.insertLink();
|
|
59
72
|
|
|
60
|
-
expect(editor.getHtml()).toBe('<p><a href="https://example.com">Hello</a> world</p>');
|
|
73
|
+
expect(editor.getHtml()).toBe('<p><a href="https://example.com" target="_blank" rel="noopener noreferrer">Hello</a> world</p>');
|
|
61
74
|
});
|
|
62
75
|
|
|
63
76
|
it("does nothing when user cancels prompt", () => {
|
|
@@ -96,7 +109,7 @@ describe("useLinks", () => {
|
|
|
96
109
|
expect(onContentChange).not.toHaveBeenCalled();
|
|
97
110
|
});
|
|
98
111
|
|
|
99
|
-
it("
|
|
112
|
+
it("wraps content and link is properly created", () => {
|
|
100
113
|
editor = createTestEditor("<p>Hello world</p>");
|
|
101
114
|
vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
|
|
102
115
|
const links = createLinks();
|
|
@@ -104,8 +117,11 @@ describe("useLinks", () => {
|
|
|
104
117
|
|
|
105
118
|
links.insertLink();
|
|
106
119
|
|
|
107
|
-
|
|
108
|
-
|
|
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");
|
|
109
125
|
});
|
|
110
126
|
});
|
|
111
127
|
|
|
@@ -122,7 +138,7 @@ describe("useLinks", () => {
|
|
|
122
138
|
|
|
123
139
|
links.insertLink();
|
|
124
140
|
|
|
125
|
-
expect(editor.getHtml()).toContain('<a href="https://example.com">https://example.com</a>');
|
|
141
|
+
expect(editor.getHtml()).toContain('<a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>');
|
|
126
142
|
expect(onContentChange).toHaveBeenCalled();
|
|
127
143
|
});
|
|
128
144
|
|
|
@@ -150,7 +166,7 @@ describe("useLinks", () => {
|
|
|
150
166
|
expect(onContentChange).not.toHaveBeenCalled();
|
|
151
167
|
});
|
|
152
168
|
|
|
153
|
-
it("
|
|
169
|
+
it("inserts link and link is properly created", () => {
|
|
154
170
|
editor = createTestEditor("<p>Hello world</p>");
|
|
155
171
|
vi.spyOn(window, "prompt").mockReturnValue("https://example.com");
|
|
156
172
|
const links = createLinks();
|
|
@@ -158,8 +174,11 @@ describe("useLinks", () => {
|
|
|
158
174
|
|
|
159
175
|
links.insertLink();
|
|
160
176
|
|
|
161
|
-
|
|
162
|
-
|
|
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");
|
|
163
182
|
});
|
|
164
183
|
});
|
|
165
184
|
|
|
@@ -323,10 +323,15 @@ export function useMarkdownEditor(options: UseMarkdownEditorOptions): UseMarkdow
|
|
|
323
323
|
codeBlocks.toggleCodeBlock();
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
//
|
|
327
|
-
const charCount =
|
|
328
|
-
|
|
329
|
-
|
|
326
|
+
// Character count - updated on each input since textContent is not reactive
|
|
327
|
+
const charCount = ref(0);
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Update the character count from the current content
|
|
331
|
+
*/
|
|
332
|
+
function updateCharCount(): void {
|
|
333
|
+
charCount.value = contentRef.value?.textContent?.length || 0;
|
|
334
|
+
}
|
|
330
335
|
|
|
331
336
|
// Reactive hotkey definitions for UI
|
|
332
337
|
const hotkeyDefinitions = computed(() => {
|
|
@@ -738,6 +743,9 @@ export function useMarkdownEditor(options: UseMarkdownEditorOptions): UseMarkdow
|
|
|
738
743
|
* Note: Code fence patterns (```) are only converted on Enter key press, not on input
|
|
739
744
|
*/
|
|
740
745
|
function onInput(): void {
|
|
746
|
+
// Update character count immediately for responsive UI
|
|
747
|
+
updateCharCount();
|
|
748
|
+
|
|
741
749
|
// Check for heading pattern (e.g., "# " -> H1)
|
|
742
750
|
let converted = headings.checkAndConvertHeadingPattern();
|
|
743
751
|
|
|
@@ -860,8 +868,8 @@ export function useMarkdownEditor(options: UseMarkdownEditorOptions): UseMarkdow
|
|
|
860
868
|
function onKeyDown(event: KeyboardEvent): void {
|
|
861
869
|
// Don't handle events that originate from inside code block wrappers
|
|
862
870
|
// (CodeViewer handles its own keyboard events like Ctrl+A for select-all)
|
|
863
|
-
const target = event.target as HTMLElement;
|
|
864
|
-
const isInCodeBlock = target
|
|
871
|
+
const target = event.target as HTMLElement | null;
|
|
872
|
+
const isInCodeBlock = target?.closest("[data-code-block-id]");
|
|
865
873
|
|
|
866
874
|
// SPECIAL CASE: Handle Ctrl+Alt+L for code block language cycling
|
|
867
875
|
// This is a fallback in case the CodeViewer's handler doesn't receive the event
|
|
@@ -873,7 +881,7 @@ export function useMarkdownEditor(options: UseMarkdownEditorOptions): UseMarkdow
|
|
|
873
881
|
event.stopPropagation();
|
|
874
882
|
|
|
875
883
|
// Find the code block ID and cycle its language
|
|
876
|
-
const wrapper = target
|
|
884
|
+
const wrapper = target?.closest("[data-code-block-id]");
|
|
877
885
|
const codeBlockId = wrapper?.getAttribute("data-code-block-id");
|
|
878
886
|
|
|
879
887
|
if (codeBlockId) {
|
|
@@ -1007,6 +1015,7 @@ export function useMarkdownEditor(options: UseMarkdownEditorOptions): UseMarkdow
|
|
|
1007
1015
|
nextTick(() => {
|
|
1008
1016
|
if (contentRef.value) {
|
|
1009
1017
|
contentRef.value.innerHTML = sync.renderedHtml.value;
|
|
1018
|
+
updateCharCount();
|
|
1010
1019
|
}
|
|
1011
1020
|
});
|
|
1012
1021
|
}
|
|
@@ -2,7 +2,10 @@ import { computed, ref, Ref } from "vue";
|
|
|
2
2
|
import { parse as parseYAML, stringify as yamlStringify } from "yaml";
|
|
3
3
|
import { fJSON, parseMarkdownJSON, parseMarkdownYAML } from "../helpers/formats/parsers";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Version for HMR cache busting
|
|
6
|
+
export const USE_CODE_FORMAT_VERSION = "1.0.4";
|
|
7
|
+
|
|
8
|
+
export type CodeFormat = "json" | "yaml" | "text" | "markdown" | "html" | "css" | "javascript";
|
|
6
9
|
|
|
7
10
|
export interface UseCodeFormatOptions {
|
|
8
11
|
initialFormat?: CodeFormat;
|
|
@@ -58,8 +61,8 @@ export function useCodeFormat(options: UseCodeFormatOptions = {}): UseCodeFormat
|
|
|
58
61
|
function formatValueToString(value: object | string | null, targetFormat: CodeFormat = format.value): string {
|
|
59
62
|
if (!value) return "";
|
|
60
63
|
|
|
61
|
-
// Text and
|
|
62
|
-
if (targetFormat === "text" || targetFormat === "markdown") {
|
|
64
|
+
// Text, markdown, and code formats (CSS, JavaScript, HTML) - just return as-is
|
|
65
|
+
if (targetFormat === "text" || targetFormat === "markdown" || targetFormat === "css" || targetFormat === "javascript" || targetFormat === "html") {
|
|
63
66
|
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -79,11 +82,12 @@ export function useCodeFormat(options: UseCodeFormatOptions = {}): UseCodeFormat
|
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
// Validate string content for a format
|
|
85
|
+
// v2: Added CSS/JavaScript/HTML as always-valid formats
|
|
82
86
|
function validateContent(content: string, targetFormat: CodeFormat = format.value): boolean {
|
|
83
87
|
if (!content) return true;
|
|
84
88
|
|
|
85
|
-
// Text and
|
|
86
|
-
if (targetFormat === "text" || targetFormat === "markdown") return true;
|
|
89
|
+
// Text, markdown, and code formats are always valid
|
|
90
|
+
if (targetFormat === "text" || targetFormat === "markdown" || targetFormat === "css" || targetFormat === "javascript" || targetFormat === "html") return true;
|
|
87
91
|
|
|
88
92
|
try {
|
|
89
93
|
if (targetFormat === "json") {
|
|
@@ -98,11 +102,12 @@ export function useCodeFormat(options: UseCodeFormatOptions = {}): UseCodeFormat
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
// Validate and return error details if invalid
|
|
105
|
+
// v2: Added CSS/JavaScript/HTML as always-valid formats
|
|
101
106
|
function validateContentWithError(content: string, targetFormat: CodeFormat = format.value): ValidationError | null {
|
|
102
107
|
if (!content) return null;
|
|
103
108
|
|
|
104
|
-
// Text and
|
|
105
|
-
if (targetFormat === "text" || targetFormat === "markdown") return null;
|
|
109
|
+
// Text, markdown, and code formats are always valid
|
|
110
|
+
if (targetFormat === "text" || targetFormat === "markdown" || targetFormat === "css" || targetFormat === "javascript" || targetFormat === "html") return null;
|
|
106
111
|
|
|
107
112
|
try {
|
|
108
113
|
if (targetFormat === "json") {
|
|
@@ -144,16 +149,18 @@ export function useCodeFormat(options: UseCodeFormatOptions = {}): UseCodeFormat
|
|
|
144
149
|
|
|
145
150
|
// Initialize with value if provided
|
|
146
151
|
if (options.initialValue) {
|
|
147
|
-
|
|
152
|
+
const formatted = formatValueToString(options.initialValue, format.value);
|
|
153
|
+
rawContent.value = formatted;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
// Computed: parsed object from raw content
|
|
151
157
|
const parsedValue = computed(() => parseContent(rawContent.value));
|
|
152
158
|
|
|
153
159
|
// Computed: formatted string
|
|
154
|
-
// For text and
|
|
160
|
+
// v3: For text, markdown, and code formats (CSS, JavaScript, HTML), return rawContent directly without parsing
|
|
155
161
|
const formattedContent = computed(() => {
|
|
156
|
-
|
|
162
|
+
const isStringFormat = format.value === "text" || format.value === "markdown" || format.value === "css" || format.value === "javascript" || format.value === "html";
|
|
163
|
+
if (isStringFormat) {
|
|
157
164
|
return rawContent.value;
|
|
158
165
|
}
|
|
159
166
|
return formatValueToString(parsedValue.value, format.value);
|
|
@@ -54,6 +54,13 @@ export function useCodeViewerCollapse(options: UseCodeViewerCollapseOptions): Us
|
|
|
54
54
|
const maxLength = 100;
|
|
55
55
|
let preview = "";
|
|
56
56
|
|
|
57
|
+
// For text and markdown formats, just show the first line - no parsing
|
|
58
|
+
if (format.value === "text" || format.value === "markdown") {
|
|
59
|
+
const firstLine = content.split("\n")[0].trim();
|
|
60
|
+
preview = firstLine.length > maxLength ? firstLine.slice(0, maxLength) + "..." : firstLine;
|
|
61
|
+
return preview;
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
if (format.value === "json") {
|
|
58
65
|
// For JSON, show compact inline format
|
|
59
66
|
try {
|
|
@@ -10,7 +10,11 @@ export function useFileNavigation(initialFile: Ref<UploadedFile | null>, initial
|
|
|
10
10
|
const currentFile = ref<UploadedFile | null>(initialFile.value);
|
|
11
11
|
const relatedFiles = ref<UploadedFile[]>(initialRelatedFiles.value);
|
|
12
12
|
const parentStack = ref<FileNavigationParent[]>([]);
|
|
13
|
-
const currentIndex = ref(
|
|
13
|
+
const currentIndex = ref(
|
|
14
|
+
initialFile.value
|
|
15
|
+
? Math.max(0, initialRelatedFiles.value.findIndex(f => f.id === initialFile.value!.id))
|
|
16
|
+
: 0
|
|
17
|
+
);
|
|
14
18
|
|
|
15
19
|
// Computed properties
|
|
16
20
|
const hasParent = computed(() => parentStack.value.length > 0);
|