quasar-ui-danx 0.4.94 → 0.4.99

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 (37) hide show
  1. package/dist/danx.es.js +24432 -22819
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +130 -119
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +1 -1
  7. package/src/components/Utility/Buttons/ActionButton.vue +11 -3
  8. package/src/components/Utility/Code/CodeViewer.vue +219 -0
  9. package/src/components/Utility/Code/CodeViewerCollapsed.vue +34 -0
  10. package/src/components/Utility/Code/CodeViewerFooter.vue +53 -0
  11. package/src/components/Utility/Code/LanguageBadge.vue +122 -0
  12. package/src/components/Utility/Code/MarkdownContent.vue +251 -0
  13. package/src/components/Utility/Code/index.ts +5 -0
  14. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +134 -38
  15. package/src/components/Utility/Files/CarouselHeader.vue +24 -0
  16. package/src/components/Utility/Files/FileMetadataDialog.vue +69 -0
  17. package/src/components/Utility/Files/FilePreview.vue +124 -162
  18. package/src/components/Utility/Files/index.ts +1 -0
  19. package/src/components/Utility/index.ts +1 -0
  20. package/src/composables/index.ts +5 -0
  21. package/src/composables/useCodeFormat.ts +199 -0
  22. package/src/composables/useCodeViewerCollapse.ts +125 -0
  23. package/src/composables/useCodeViewerEditor.ts +420 -0
  24. package/src/composables/useFilePreview.ts +119 -0
  25. package/src/composables/useTranscodeLoader.ts +68 -0
  26. package/src/helpers/filePreviewHelpers.ts +31 -0
  27. package/src/helpers/formats/highlightSyntax.ts +327 -0
  28. package/src/helpers/formats/index.ts +3 -1
  29. package/src/helpers/formats/renderMarkdown.ts +338 -0
  30. package/src/helpers/objectStore.ts +10 -2
  31. package/src/styles/danx.scss +3 -0
  32. package/src/styles/themes/danx/code.scss +158 -0
  33. package/src/styles/themes/danx/index.scss +2 -0
  34. package/src/styles/themes/danx/markdown.scss +145 -0
  35. package/src/styles/themes/danx/scrollbar.scss +125 -0
  36. package/src/svg/GoogleDocsIcon.vue +88 -0
  37. package/src/svg/index.ts +1 -0
@@ -0,0 +1,68 @@
1
+ import { onMounted, Ref, ref, watch } from "vue";
2
+ import { danxOptions } from "../config";
3
+ import { UploadedFile } from "../types";
4
+
5
+ export interface UseTranscodeLoaderOptions {
6
+ file: Ref<UploadedFile | null | undefined>;
7
+ }
8
+
9
+ export interface UseTranscodeLoaderReturn {
10
+ isLoading: Ref<boolean>;
11
+ loadTranscodes: () => Promise<void>;
12
+ }
13
+
14
+ /**
15
+ * Composable for loading transcodes for a file
16
+ * Automatically loads transcodes on mount and when the file changes
17
+ */
18
+ export function useTranscodeLoader(options: UseTranscodeLoaderOptions): UseTranscodeLoaderReturn {
19
+ const { file } = options;
20
+ const isLoading = ref(false);
21
+
22
+ function shouldLoadTranscodes(): boolean {
23
+ if (!file.value?.id) return false;
24
+ if (isLoading.value) return false;
25
+ if (!danxOptions.value.fileUpload?.refreshFile) return false;
26
+
27
+ // Only load if transcodes is explicitly null, undefined, or an empty array
28
+ const transcodes = file.value.transcodes;
29
+ return transcodes === null || transcodes === undefined || (Array.isArray(transcodes) && transcodes.length === 0);
30
+ }
31
+
32
+ async function loadTranscodes() {
33
+ if (!shouldLoadTranscodes()) return;
34
+
35
+ isLoading.value = true;
36
+
37
+ try {
38
+ const refreshFile = danxOptions.value.fileUpload?.refreshFile;
39
+ if (refreshFile && file.value?.id) {
40
+ const refreshedFile = await refreshFile(file.value.id);
41
+
42
+ // Update the file object with the loaded transcodes
43
+ if (refreshedFile.transcodes && file.value) {
44
+ file.value.transcodes = refreshedFile.transcodes;
45
+ }
46
+ }
47
+ } catch (error) {
48
+ console.error("Failed to load transcodes:", error);
49
+ } finally {
50
+ isLoading.value = false;
51
+ }
52
+ }
53
+
54
+ // Load transcodes when component mounts
55
+ onMounted(() => {
56
+ loadTranscodes();
57
+ });
58
+
59
+ // Watch for file changes and reload transcodes if needed
60
+ watch(() => file.value?.id, () => {
61
+ loadTranscodes();
62
+ });
63
+
64
+ return {
65
+ isLoading,
66
+ loadTranscodes
67
+ };
68
+ }
@@ -105,3 +105,34 @@ export function getThumbUrl(file: UploadedFile): string {
105
105
  export function getOptimizedUrl(file: UploadedFile): string {
106
106
  return file.optimized?.url || file.blobUrl || file.url || "";
107
107
  }
108
+
109
+ /**
110
+ * Check if file is an external link that should open in a new tab
111
+ * (e.g., Google Docs, external URLs that can't be previewed inline)
112
+ */
113
+ export function isExternalLinkFile(file: UploadedFile): boolean {
114
+ const url = file.url || "";
115
+ const mime = getMimeType(file);
116
+
117
+ // Google Docs/Sheets/Slides URLs
118
+ if (url.includes("docs.google.com") || url.includes("drive.google.com")) {
119
+ return true;
120
+ }
121
+
122
+ // Google Doc MIME types
123
+ if (mime.startsWith("application/vnd.google-apps.")) {
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * Check if file can be previewed inline (image, video, text, pdf)
132
+ */
133
+ export function canPreviewInline(file: UploadedFile): boolean {
134
+ if (isExternalLinkFile(file)) {
135
+ return false;
136
+ }
137
+ return isImage(file) || isVideo(file) || isText(file) || isPdf(file) || !!file.thumb?.url;
138
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Lightweight syntax highlighting for JSON and YAML
3
+ * Returns HTML string with syntax highlighting spans
4
+ */
5
+
6
+ export type HighlightFormat = "json" | "yaml" | "text" | "markdown";
7
+
8
+ export interface HighlightOptions {
9
+ format: HighlightFormat;
10
+ }
11
+
12
+ /**
13
+ * Escape HTML entities to prevent XSS
14
+ */
15
+ function escapeHtml(text: string): string {
16
+ return text
17
+ .replace(/&/g, "&amp;")
18
+ .replace(/</g, "&lt;")
19
+ .replace(/>/g, "&gt;")
20
+ .replace(/"/g, "&quot;")
21
+ .replace(/'/g, "&#039;");
22
+ }
23
+
24
+ /**
25
+ * Highlight JSON syntax by tokenizing first, then applying highlights
26
+ * This prevents issues with regex replacing content inside already-matched strings
27
+ */
28
+ export function highlightJSON(code: string): string {
29
+ if (!code) return "";
30
+
31
+ const result: string[] = [];
32
+ let i = 0;
33
+
34
+ while (i < code.length) {
35
+ const char = code[i];
36
+
37
+ // String (starts with ")
38
+ if (char === '"') {
39
+ const startIndex = i;
40
+ i++; // skip opening quote
41
+
42
+ // Find the closing quote, handling escape sequences
43
+ while (i < code.length) {
44
+ if (code[i] === '\\' && i + 1 < code.length) {
45
+ i += 2; // skip escaped character
46
+ } else if (code[i] === '"') {
47
+ i++; // include closing quote
48
+ break;
49
+ } else {
50
+ i++;
51
+ }
52
+ }
53
+
54
+ const str = code.slice(startIndex, i);
55
+ const escapedStr = escapeHtml(str);
56
+
57
+ // Check if this is a key (followed by colon)
58
+ const afterString = code.slice(i).match(/^(\s*):/);
59
+ if (afterString) {
60
+ result.push(`<span class="syntax-key">${escapedStr}</span>`);
61
+ // Add the whitespace and colon
62
+ result.push(`<span class="syntax-punctuation">${escapeHtml(afterString[1])}:</span>`);
63
+ i += afterString[0].length;
64
+ } else {
65
+ // It's a string value
66
+ result.push(`<span class="syntax-string">${escapedStr}</span>`);
67
+ }
68
+ continue;
69
+ }
70
+
71
+ // Number (starts with digit or minus followed by digit)
72
+ if (/[-\d]/.test(char)) {
73
+ const numMatch = code.slice(i).match(/^-?\d+(\.\d+)?([eE][+-]?\d+)?/);
74
+ if (numMatch) {
75
+ result.push(`<span class="syntax-number">${escapeHtml(numMatch[0])}</span>`);
76
+ i += numMatch[0].length;
77
+ continue;
78
+ }
79
+ }
80
+
81
+ // Boolean true
82
+ if (code.slice(i, i + 4) === 'true') {
83
+ result.push(`<span class="syntax-boolean">true</span>`);
84
+ i += 4;
85
+ continue;
86
+ }
87
+
88
+ // Boolean false
89
+ if (code.slice(i, i + 5) === 'false') {
90
+ result.push(`<span class="syntax-boolean">false</span>`);
91
+ i += 5;
92
+ continue;
93
+ }
94
+
95
+ // Null
96
+ if (code.slice(i, i + 4) === 'null') {
97
+ result.push(`<span class="syntax-null">null</span>`);
98
+ i += 4;
99
+ continue;
100
+ }
101
+
102
+ // Punctuation
103
+ if (/[{}\[\],:]/.test(char)) {
104
+ result.push(`<span class="syntax-punctuation">${escapeHtml(char)}</span>`);
105
+ i++;
106
+ continue;
107
+ }
108
+
109
+ // Whitespace and other characters
110
+ result.push(escapeHtml(char));
111
+ i++;
112
+ }
113
+
114
+ return result.join('');
115
+ }
116
+
117
+ /**
118
+ * Highlight a YAML value based on its type
119
+ */
120
+ function highlightYAMLValue(value: string): string {
121
+ if (!value) return value;
122
+
123
+ // Quoted string (complete)
124
+ if (/^(&quot;.*&quot;|&#039;.*&#039;)$/.test(value) || /^["'].*["']$/.test(value)) {
125
+ return `<span class="syntax-string">${value}</span>`;
126
+ }
127
+ // Number (strict format: integers, decimals, scientific notation)
128
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(value)) {
129
+ return `<span class="syntax-number">${value}</span>`;
130
+ }
131
+ // Boolean
132
+ if (/^(true|false)$/i.test(value)) {
133
+ return `<span class="syntax-boolean">${value}</span>`;
134
+ }
135
+ // Null
136
+ if (/^(null|~)$/i.test(value)) {
137
+ return `<span class="syntax-null">${value}</span>`;
138
+ }
139
+ // Block scalar indicators - don't wrap, handle continuation separately
140
+ if (/^[|>][-+]?\d*$/.test(value)) {
141
+ return `<span class="syntax-punctuation">${value}</span>`;
142
+ }
143
+ // Unquoted string
144
+ return `<span class="syntax-string">${value}</span>`;
145
+ }
146
+
147
+ /**
148
+ * Check if a line is a continuation of a multi-line string
149
+ * (indented content following a block scalar or inside a quoted string)
150
+ */
151
+ function getIndentLevel(line: string): number {
152
+ const match = line.match(/^(\s*)/);
153
+ return match ? match[1].length : 0;
154
+ }
155
+
156
+ /**
157
+ * Highlight YAML syntax with multi-line string support
158
+ */
159
+ export function highlightYAML(code: string): string {
160
+ if (!code) return "";
161
+
162
+ const lines = code.split("\n");
163
+ const highlightedLines: string[] = [];
164
+
165
+ // State tracking for multi-line constructs
166
+ let inBlockScalar = false;
167
+ let blockScalarIndent = 0;
168
+ let inQuotedString = false;
169
+ let quoteChar = "";
170
+ let inUnquotedMultiline = false;
171
+ let unquotedMultilineKeyIndent = 0;
172
+
173
+ for (let i = 0; i < lines.length; i++) {
174
+ const line = lines[i];
175
+ const escaped = escapeHtml(line);
176
+ const currentIndent = getIndentLevel(line);
177
+ const trimmedLine = line.trim();
178
+
179
+ // Handle block scalar continuation (| or > style)
180
+ if (inBlockScalar) {
181
+ // Block scalar ends when we hit a line with less or equal indentation (and content)
182
+ if (trimmedLine && currentIndent <= blockScalarIndent) {
183
+ inBlockScalar = false;
184
+ // Fall through to normal processing
185
+ } else {
186
+ // This is a continuation line - highlight as string
187
+ highlightedLines.push(`<span class="syntax-string">${escaped}</span>`);
188
+ continue;
189
+ }
190
+ }
191
+
192
+ // Handle quoted string continuation
193
+ if (inQuotedString) {
194
+ // Check if this line closes the quote
195
+ const escapedQuote = quoteChar === '"' ? '&quot;' : '&#039;';
196
+
197
+ // Count unescaped quotes in this line
198
+ let closed = false;
199
+ let searchLine = escaped;
200
+ while (searchLine.includes(escapedQuote)) {
201
+ const idx = searchLine.indexOf(escapedQuote);
202
+ // Check if preceded by backslash (escaped)
203
+ if (idx > 0 && searchLine[idx - 1] === '\\') {
204
+ searchLine = searchLine.slice(idx + escapedQuote.length);
205
+ continue;
206
+ }
207
+ closed = true;
208
+ break;
209
+ }
210
+
211
+ if (closed) {
212
+ // Find position of closing quote
213
+ const closeIdx = escaped.indexOf(escapedQuote);
214
+ const beforeClose = escaped.slice(0, closeIdx + escapedQuote.length);
215
+ const afterClose = escaped.slice(closeIdx + escapedQuote.length);
216
+
217
+ highlightedLines.push(`<span class="syntax-string">${beforeClose}</span>${afterClose}`);
218
+ inQuotedString = false;
219
+ } else {
220
+ // Still in quoted string
221
+ highlightedLines.push(`<span class="syntax-string">${escaped}</span>`);
222
+ }
223
+ continue;
224
+ }
225
+
226
+ // Handle unquoted multi-line string continuation
227
+ if (inUnquotedMultiline) {
228
+ // Unquoted multiline ends when we hit a line with <= indentation to the key
229
+ // or when the line contains a colon (new key-value pair)
230
+ if (trimmedLine && currentIndent <= unquotedMultilineKeyIndent) {
231
+ inUnquotedMultiline = false;
232
+ // Fall through to normal processing
233
+ } else if (trimmedLine) {
234
+ // This is a continuation line - highlight as string
235
+ highlightedLines.push(`<span class="syntax-string">${escaped}</span>`);
236
+ continue;
237
+ } else {
238
+ // Empty line within multiline - keep it
239
+ highlightedLines.push(escaped);
240
+ continue;
241
+ }
242
+ }
243
+
244
+ // Comments
245
+ if (/^\s*#/.test(line)) {
246
+ highlightedLines.push(`<span class="syntax-punctuation">${escaped}</span>`);
247
+ continue;
248
+ }
249
+
250
+ // Key-value pairs
251
+ const keyValueMatch = escaped.match(/^(\s*)([^:]+?)(:)(\s*)(.*)$/);
252
+ if (keyValueMatch) {
253
+ const [, indent, key, colon, space, value] = keyValueMatch;
254
+
255
+ // Check for block scalar start
256
+ if (/^[|>][-+]?\d*$/.test(value.trim())) {
257
+ inBlockScalar = true;
258
+ blockScalarIndent = currentIndent;
259
+ const highlightedValue = `<span class="syntax-punctuation">${value}</span>`;
260
+ highlightedLines.push(`${indent}<span class="syntax-key">${key}</span><span class="syntax-punctuation">${colon}</span>${space}${highlightedValue}`);
261
+ continue;
262
+ }
263
+
264
+ // Check for start of multi-line quoted string
265
+ const startsWithQuote = /^(&quot;|&#039;|"|')/.test(value);
266
+ const endsWithQuote = /(&quot;|&#039;|"|')$/.test(value);
267
+
268
+ if (startsWithQuote && !endsWithQuote && value.length > 1) {
269
+ // Multi-line quoted string starts here
270
+ inQuotedString = true;
271
+ quoteChar = value.startsWith('&quot;') || value.startsWith('"') ? '"' : "'";
272
+ highlightedLines.push(`${indent}<span class="syntax-key">${key}</span><span class="syntax-punctuation">${colon}</span>${space}<span class="syntax-string">${value}</span>`);
273
+ continue;
274
+ }
275
+
276
+ // Check for start of unquoted multi-line string
277
+ // If value is an unquoted string and next line is more indented, it's a multiline
278
+ if (value && !startsWithQuote && i + 1 < lines.length) {
279
+ const nextLine = lines[i + 1];
280
+ const nextIndent = getIndentLevel(nextLine);
281
+ const nextTrimmed = nextLine.trim();
282
+ // Next line must be more indented than current key and not be a new key-value or array item
283
+ if (nextTrimmed && nextIndent > currentIndent && !nextTrimmed.includes(':') && !nextTrimmed.startsWith('-')) {
284
+ inUnquotedMultiline = true;
285
+ unquotedMultilineKeyIndent = currentIndent;
286
+ highlightedLines.push(`${indent}<span class="syntax-key">${key}</span><span class="syntax-punctuation">${colon}</span>${space}<span class="syntax-string">${value}</span>`);
287
+ continue;
288
+ }
289
+ }
290
+
291
+ // Normal single-line value
292
+ const highlightedValue = highlightYAMLValue(value);
293
+ highlightedLines.push(`${indent}<span class="syntax-key">${key}</span><span class="syntax-punctuation">${colon}</span>${space}${highlightedValue}`);
294
+ continue;
295
+ }
296
+
297
+ // Array items (lines starting with -)
298
+ const arrayMatch = escaped.match(/^(\s*)(-)(\s*)(.*)$/);
299
+ if (arrayMatch) {
300
+ const [, indent, dash, space, value] = arrayMatch;
301
+ const highlightedValue = value ? highlightYAMLValue(value) : "";
302
+ highlightedLines.push(`${indent}<span class="syntax-punctuation">${dash}</span>${space}${highlightedValue}`);
303
+ continue;
304
+ }
305
+
306
+ highlightedLines.push(escaped);
307
+ }
308
+
309
+ return highlightedLines.join("\n");
310
+ }
311
+
312
+ /**
313
+ * Highlight code based on format
314
+ */
315
+ export function highlightSyntax(code: string, options: HighlightOptions): string {
316
+ if (!code) return "";
317
+
318
+ switch (options.format) {
319
+ case "json":
320
+ return highlightJSON(code);
321
+ case "yaml":
322
+ return highlightYAML(code);
323
+ case "text":
324
+ default:
325
+ return escapeHtml(code);
326
+ }
327
+ }
@@ -1,4 +1,6 @@
1
1
  export * from "./datetime";
2
+ export * from "./highlightSyntax";
2
3
  export * from "./numbers";
3
- export * from "./strings";
4
4
  export * from "./parsers";
5
+ export * from "./renderMarkdown";
6
+ export * from "./strings";