quasar-ui-danx 0.4.95 → 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 (33) hide show
  1. package/dist/danx.es.js +24452 -22880
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +133 -122
  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 +118 -166
  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/formats/highlightSyntax.ts +327 -0
  27. package/src/helpers/formats/index.ts +3 -1
  28. package/src/helpers/formats/renderMarkdown.ts +338 -0
  29. package/src/styles/danx.scss +3 -0
  30. package/src/styles/themes/danx/code.scss +158 -0
  31. package/src/styles/themes/danx/index.scss +2 -0
  32. package/src/styles/themes/danx/markdown.scss +145 -0
  33. package/src/styles/themes/danx/scrollbar.scss +125 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.95",
3
+ "version": "0.4.99",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -78,14 +78,16 @@ import {
78
78
  FaSolidPlay as PlayIcon,
79
79
  FaSolidPlus as CreateIcon,
80
80
  FaSolidStop as StopIcon,
81
- FaSolidTrash as TrashIcon
81
+ FaSolidTrash as TrashIcon,
82
+ FaSolidUsers as UsersIcon,
83
+ FaSolidXmark as CloseIcon
82
84
  } from "danx-icon";
83
85
  import { computed, ref } from "vue";
84
86
  import { ActionTarget, ResourceAction } from "../../../types";
85
87
 
86
88
  export interface ActionButtonProps {
87
- type?: "save" | "trash" | "back" | "create" | "edit" | "copy" | "folder" | "document" | "play" | "stop" | "pause" | "refresh" | "restart" | "confirm" | "cancel" | "export" | "import" | "minus" | "merge" | "check" | "clock" | "view" | "database";
88
- color?: "red" | "blue" | "blue-invert" | "sky" | "sky-invert" | "green" | "green-invert" | "lime" | "white" | "gray" | "slate" | "slate-invert" | "yellow" | "orange" | "purple" | "teal" | "teal-invert";
89
+ type?: "save" | "trash" | "back" | "create" | "edit" | "copy" | "folder" | "document" | "play" | "stop" | "pause" | "refresh" | "restart" | "confirm" | "cancel" | "export" | "import" | "minus" | "merge" | "check" | "clock" | "view" | "database" | "users" | "close";
90
+ color?: "red" | "blue" | "blue-invert" | "sky" | "sky-invert" | "green" | "green-invert" | "lime" | "white" | "gray" | "slate" | "slate-invert" | "yellow" | "orange" | "amber" | "purple" | "teal" | "teal-invert";
89
91
  size?: "xxs" | "xs" | "sm" | "md" | "lg";
90
92
  icon?: object | string;
91
93
  iconClass?: string;
@@ -179,6 +181,8 @@ const colorClass = computed(() => {
179
181
  return "text-yellow-300 bg-yellow-800 hover:bg-yellow-700";
180
182
  case "orange":
181
183
  return "text-orange-400 bg-orange-900 hover:bg-orange-800";
184
+ case "amber":
185
+ return "text-amber-900 bg-amber-300 hover:bg-amber-400";
182
186
  case "gray":
183
187
  return "text-slate-200 bg-slate-800 hover:bg-slate-900";
184
188
  case "slate":
@@ -242,6 +246,10 @@ const typeOptions = computed(() => {
242
246
  return { icon: ViewIcon };
243
247
  case "database":
244
248
  return { icon: DatabaseIcon };
249
+ case "users":
250
+ return { icon: UsersIcon };
251
+ case "close":
252
+ return { icon: CloseIcon };
245
253
  default:
246
254
  return { icon: EditIcon };
247
255
  }
@@ -0,0 +1,219 @@
1
+ <template>
2
+ <div
3
+ class="dx-code-viewer group flex flex-col"
4
+ :class="{ 'is-collapsed': isCollapsed }"
5
+ >
6
+ <FieldLabel
7
+ v-if="label"
8
+ class="mb-2 text-sm flex-shrink-0"
9
+ :label="label"
10
+ />
11
+
12
+ <!-- Collapsed view - inline preview -->
13
+ <CodeViewerCollapsed
14
+ v-if="collapsible && isCollapsed"
15
+ :preview="collapsedPreview"
16
+ :format="currentFormat"
17
+ :available-formats="currentFormat === 'json' || currentFormat === 'yaml' ? ['json', 'yaml'] : currentFormat === 'text' || currentFormat === 'markdown' ? ['text', 'markdown'] : []"
18
+ @expand="toggleCollapse"
19
+ @format-change="onFormatChange"
20
+ />
21
+
22
+ <!-- Expanded view - full code viewer -->
23
+ <template v-else>
24
+ <div class="code-wrapper relative flex flex-col flex-1 min-h-0">
25
+ <!-- Language badge - shows popout format options on hover -->
26
+ <LanguageBadge
27
+ :format="currentFormat"
28
+ :available-formats="currentFormat === 'json' || currentFormat === 'yaml' ? ['json', 'yaml'] : currentFormat === 'text' || currentFormat === 'markdown' ? ['text', 'markdown'] : []"
29
+ :toggleable="true"
30
+ @click.stop
31
+ @change="onFormatChange"
32
+ />
33
+
34
+ <!-- Collapse button (when collapsible and expanded) -->
35
+ <div
36
+ v-if="collapsible"
37
+ class="collapse-toggle absolute top-0 left-0 p-1 cursor-pointer z-10 text-gray-500 hover:text-gray-300"
38
+ @click="toggleCollapse"
39
+ >
40
+ <CollapseIcon class="w-3 h-3" />
41
+ </div>
42
+
43
+ <!-- Clickable header to collapse when expanded -->
44
+ <div
45
+ v-if="collapsible"
46
+ class="collapse-header"
47
+ @click="toggleCollapse"
48
+ />
49
+
50
+ <!-- Code display - readonly with syntax highlighting (non-markdown formats) -->
51
+ <pre
52
+ v-if="!editor.isEditing.value && currentFormat !== 'markdown'"
53
+ class="code-content dx-scrollbar flex-1 min-h-0"
54
+ :class="[editorClass, { 'is-collapsible': collapsible }]"
55
+ ><code :class="'language-' + currentFormat" v-html="editor.highlightedContent.value"></code></pre>
56
+
57
+ <!-- Markdown display - rendered HTML -->
58
+ <MarkdownContent
59
+ v-else-if="currentFormat === 'markdown' && !editor.isEditing.value"
60
+ :content="markdownSource"
61
+ :default-code-format="defaultCodeFormat"
62
+ class="code-content dx-scrollbar flex-1 min-h-0"
63
+ :class="[editorClass, { 'is-collapsible': collapsible }]"
64
+ />
65
+
66
+ <!-- Code editor - contenteditable, content set imperatively to avoid cursor reset -->
67
+ <pre
68
+ v-else
69
+ ref="codeRef"
70
+ class="code-content dx-scrollbar flex-1 min-h-0 is-editable"
71
+ :class="[editorClass, 'language-' + currentFormat, { 'is-collapsible': collapsible }]"
72
+ contenteditable="true"
73
+ @input="editor.onContentEditableInput"
74
+ @blur="editor.onContentEditableBlur"
75
+ @keydown="editor.onKeyDown"
76
+ ></pre>
77
+
78
+ <!-- Footer with char count and edit toggle -->
79
+ <CodeViewerFooter
80
+ :char-count="editor.charCount.value"
81
+ :validation-error="editor.validationError.value"
82
+ :can-edit="canEdit && currentFormat !== 'markdown'"
83
+ :is-editing="editor.isEditing.value"
84
+ @toggle-edit="editor.toggleEdit"
85
+ />
86
+ </div>
87
+ </template>
88
+ </div>
89
+ </template>
90
+
91
+ <script setup lang="ts">
92
+ import { FaSolidChevronDown as CollapseIcon } from "danx-icon";
93
+ import { computed, ref, toRef, watch } from "vue";
94
+ import { useCodeFormat, CodeFormat } from "../../../composables/useCodeFormat";
95
+ import { useCodeViewerCollapse } from "../../../composables/useCodeViewerCollapse";
96
+ import { useCodeViewerEditor } from "../../../composables/useCodeViewerEditor";
97
+ import FieldLabel from "../../ActionTable/Form/Fields/FieldLabel.vue";
98
+ import CodeViewerCollapsed from "./CodeViewerCollapsed.vue";
99
+ import CodeViewerFooter from "./CodeViewerFooter.vue";
100
+ import LanguageBadge from "./LanguageBadge.vue";
101
+ import MarkdownContent from "./MarkdownContent.vue";
102
+
103
+ export interface CodeViewerProps {
104
+ modelValue?: object | string | null;
105
+ format?: CodeFormat;
106
+ label?: string;
107
+ editorClass?: string;
108
+ canEdit?: boolean;
109
+ editable?: boolean;
110
+ collapsible?: boolean;
111
+ defaultCollapsed?: boolean;
112
+ defaultCodeFormat?: "json" | "yaml";
113
+ }
114
+
115
+ const props = withDefaults(defineProps<CodeViewerProps>(), {
116
+ modelValue: null,
117
+ format: "yaml",
118
+ label: "",
119
+ editorClass: "",
120
+ canEdit: false,
121
+ editable: false,
122
+ collapsible: false,
123
+ defaultCollapsed: true
124
+ });
125
+
126
+ const emit = defineEmits<{
127
+ "update:modelValue": [value: object | string | null];
128
+ "update:format": [format: CodeFormat];
129
+ "update:editable": [editable: boolean];
130
+ }>();
131
+
132
+ // Initialize composable with current props
133
+ const codeFormat = useCodeFormat({
134
+ initialFormat: props.format,
135
+ initialValue: props.modelValue
136
+ });
137
+
138
+ // Local state
139
+ const currentFormat = ref<CodeFormat>(props.format);
140
+ const codeRef = ref<HTMLPreElement | null>(null);
141
+
142
+ // Collapsed state (for collapsible mode)
143
+ const isCollapsed = ref(props.collapsible && props.defaultCollapsed);
144
+
145
+ // Watch for changes to defaultCollapsed prop
146
+ watch(() => props.defaultCollapsed, (newValue) => {
147
+ if (props.collapsible) {
148
+ isCollapsed.value = newValue;
149
+ }
150
+ });
151
+
152
+ // Toggle collapsed state
153
+ function toggleCollapse() {
154
+ isCollapsed.value = !isCollapsed.value;
155
+ }
156
+
157
+ // Sync composable format with current format
158
+ watch(currentFormat, (newFormat) => {
159
+ codeFormat.setFormat(newFormat);
160
+ });
161
+
162
+ // Watch for external format changes
163
+ watch(() => props.format, (newFormat) => {
164
+ currentFormat.value = newFormat;
165
+ });
166
+
167
+ // Watch for external value changes
168
+ watch(() => props.modelValue, () => {
169
+ codeFormat.setValue(props.modelValue);
170
+ editor.syncEditingContentFromValue();
171
+ });
172
+
173
+ // Initialize editor composable
174
+ const editor = useCodeViewerEditor({
175
+ codeRef,
176
+ codeFormat,
177
+ currentFormat,
178
+ canEdit: toRef(props, "canEdit"),
179
+ editable: toRef(props, "editable"),
180
+ onEmitModelValue: (value) => emit("update:modelValue", value),
181
+ onEmitEditable: (editable) => emit("update:editable", editable)
182
+ });
183
+
184
+ // Sync internal editable state with prop
185
+ watch(() => props.editable, (newValue) => {
186
+ editor.syncEditableFromProp(newValue);
187
+ });
188
+
189
+ // Initialize collapse composable
190
+ const { collapsedPreview } = useCodeViewerCollapse({
191
+ modelValue: toRef(props, "modelValue"),
192
+ format: currentFormat,
193
+ displayContent: editor.displayContent,
194
+ codeFormat
195
+ });
196
+
197
+ // Switch between JSON and YAML formats
198
+ function onFormatChange(newFormat: CodeFormat) {
199
+ currentFormat.value = newFormat;
200
+ emit("update:format", newFormat);
201
+ editor.updateEditingContentOnFormatChange();
202
+ }
203
+
204
+ // Get the raw markdown content for MarkdownContent component
205
+ const markdownSource = computed(() => {
206
+ if (typeof props.modelValue === "string") {
207
+ return props.modelValue;
208
+ }
209
+ return editor.displayContent.value;
210
+ });
211
+
212
+ // Expose isValid for external consumers
213
+ const isValid = computed(() => editor.isValid.value);
214
+
215
+ // Expose for parent components that may need to check validity
216
+ defineExpose({ isValid });
217
+ </script>
218
+
219
+ <!-- Styles moved to global theme: src/styles/themes/danx/code.scss -->
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div
3
+ class="code-collapsed relative flex items-center cursor-pointer"
4
+ @click="$emit('expand')"
5
+ >
6
+ <CollapseExpandIcon class="w-3 h-3 mr-2 flex-shrink-0 text-gray-500" />
7
+ <code class="code-collapsed-preview flex-1 min-w-0 truncate" v-html="preview" />
8
+
9
+ <!-- Language badge - stop propagation to prevent expand when clicking -->
10
+ <LanguageBadge
11
+ :format="format"
12
+ :available-formats="availableFormats"
13
+ :toggleable="availableFormats.length > 1"
14
+ @click.stop
15
+ @change="(fmt) => $emit('format-change', fmt)"
16
+ />
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { FaSolidChevronRight as CollapseExpandIcon } from "danx-icon";
22
+ import LanguageBadge from "./LanguageBadge.vue";
23
+
24
+ defineProps<{
25
+ preview: string;
26
+ format: string;
27
+ availableFormats?: string[];
28
+ }>();
29
+
30
+ defineEmits<{
31
+ expand: [];
32
+ "format-change": [format: string];
33
+ }>();
34
+ </script>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <div
3
+ class="code-footer flex items-center justify-between px-2 py-1 flex-shrink-0"
4
+ :class="{ 'has-error': hasError }"
5
+ >
6
+ <div class="text-xs flex-1 min-w-0" :class="hasError ? 'text-red-400' : 'text-gray-500'">
7
+ <template v-if="validationError">
8
+ <span class="font-medium">
9
+ Error<template v-if="validationError.line"> (line {{ validationError.line }})</template>:
10
+ </span>
11
+ <span class="truncate">{{ validationError.message }}</span>
12
+ </template>
13
+ <template v-else>
14
+ {{ charCount.toLocaleString() }} chars
15
+ </template>
16
+ </div>
17
+ <!-- Edit toggle button -->
18
+ <QBtn
19
+ v-if="canEdit"
20
+ flat
21
+ dense
22
+ round
23
+ size="sm"
24
+ class="text-gray-500 hover:text-gray-700"
25
+ :class="{ 'text-sky-500 hover:text-sky-600': isEditing }"
26
+ @click="$emit('toggle-edit')"
27
+ >
28
+ <EditIcon class="w-3.5 h-3.5" />
29
+ <QTooltip>{{ isEditing ? 'Exit edit mode' : 'Edit content' }}</QTooltip>
30
+ </QBtn>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { FaSolidPencil as EditIcon } from "danx-icon";
36
+ import { computed } from "vue";
37
+ import { ValidationError } from "../../../composables/useCodeFormat";
38
+
39
+ export interface CodeViewerFooterProps {
40
+ charCount: number;
41
+ validationError: ValidationError | null;
42
+ canEdit: boolean;
43
+ isEditing: boolean;
44
+ }
45
+
46
+ const props = defineProps<CodeViewerFooterProps>();
47
+
48
+ defineEmits<{
49
+ "toggle-edit": [];
50
+ }>();
51
+
52
+ const hasError = computed(() => props.validationError !== null);
53
+ </script>
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <div
3
+ class="dx-language-badge-container"
4
+ :class="{ 'is-toggleable': toggleable && availableFormats.length > 1 }"
5
+ @mouseenter="showOptions = true"
6
+ @mouseleave="showOptions = false"
7
+ >
8
+ <!-- Other format options (slide out to the left) -->
9
+ <transition name="slide-left">
10
+ <div
11
+ v-if="showOptions && toggleable && otherFormats.length > 0"
12
+ class="dx-language-options"
13
+ >
14
+ <div
15
+ v-for="fmt in otherFormats"
16
+ :key="fmt"
17
+ class="dx-language-option"
18
+ @click.stop="$emit('change', fmt)"
19
+ >
20
+ {{ fmt.toUpperCase() }}
21
+ </div>
22
+ </div>
23
+ </transition>
24
+
25
+ <!-- Current format badge (stays in place) -->
26
+ <div class="dx-language-badge" :class="{ 'is-active': showOptions && otherFormats.length > 0 }">
27
+ {{ format.toUpperCase() }}
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import { computed, ref } from "vue";
34
+
35
+ export interface LanguageBadgeProps {
36
+ format: string;
37
+ availableFormats?: string[];
38
+ toggleable?: boolean;
39
+ }
40
+
41
+ const props = withDefaults(defineProps<LanguageBadgeProps>(), {
42
+ availableFormats: () => [],
43
+ toggleable: true
44
+ });
45
+
46
+ defineEmits<{
47
+ change: [format: string];
48
+ }>();
49
+
50
+ const showOptions = ref(false);
51
+
52
+ // Get formats other than the current one
53
+ const otherFormats = computed(() => {
54
+ return props.availableFormats.filter(f => f !== props.format);
55
+ });
56
+ </script>
57
+
58
+ <style lang="scss">
59
+ .dx-language-badge-container {
60
+ position: absolute;
61
+ top: 0;
62
+ right: 0;
63
+ display: flex;
64
+ align-items: center;
65
+ z-index: 10;
66
+
67
+ &.is-toggleable {
68
+ cursor: pointer;
69
+ }
70
+ }
71
+
72
+ .dx-language-options {
73
+ display: flex;
74
+ align-items: center;
75
+ }
76
+
77
+ .dx-language-option {
78
+ padding: 2px 8px;
79
+ font-size: 0.7em;
80
+ background: rgba(0, 0, 0, 0.5);
81
+ color: rgba(255, 255, 255, 0.7);
82
+ text-transform: uppercase;
83
+ cursor: pointer;
84
+ transition: all 0.2s;
85
+ border-right: 1px solid rgba(255, 255, 255, 0.1);
86
+
87
+ &:hover {
88
+ background: rgba(0, 0, 0, 0.7);
89
+ color: rgba(255, 255, 255, 0.95);
90
+ }
91
+
92
+ &:first-child {
93
+ border-radius: 6px 0 0 6px;
94
+ }
95
+ }
96
+
97
+ .dx-language-badge {
98
+ padding: 2px 8px;
99
+ font-size: 0.7em;
100
+ border-radius: 0 6px 0 6px;
101
+ background: rgba(255, 255, 255, 0.1);
102
+ color: rgba(255, 255, 255, 0.6);
103
+ text-transform: uppercase;
104
+ transition: all 0.2s;
105
+
106
+ &.is-active {
107
+ border-radius: 0 6px 0 0;
108
+ }
109
+ }
110
+
111
+ // Slide animation for options
112
+ .slide-left-enter-active,
113
+ .slide-left-leave-active {
114
+ transition: all 0.2s ease;
115
+ }
116
+
117
+ .slide-left-enter-from,
118
+ .slide-left-leave-to {
119
+ opacity: 0;
120
+ transform: translateX(10px);
121
+ }
122
+ </style>