@cloudron/pankow 4.3.6 → 4.4.1

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/components/BottomBar.vue +1 -1
  2. package/components/BreadCrumb.vue +3 -3
  3. package/components/Button.vue +6 -0
  4. package/components/ClipboardAction.vue +1 -1
  5. package/components/DateTimeInput.vue +2 -1
  6. package/components/Dialog.vue +4 -2
  7. package/components/DirectoryView.vue +3 -16
  8. package/components/DirectoryViewGridItem.vue +8 -71
  9. package/components/DirectoryViewListItem.vue +6 -64
  10. package/components/EmailInput.vue +2 -1
  11. package/components/FileUploader.vue +2 -2
  12. package/components/MaskedInput.vue +2 -1
  13. package/components/Menu.vue +1 -1
  14. package/components/MenuItem.vue +1 -1
  15. package/components/MenuItemLink.vue +1 -1
  16. package/components/MultiSelect.vue +1 -1
  17. package/components/Notification.vue +4 -2
  18. package/components/NumberInput.vue +2 -1
  19. package/components/OfflineBanner.vue +1 -1
  20. package/components/PasswordInput.vue +1 -1
  21. package/components/Popover.vue +1 -1
  22. package/components/ProgressBar.vue +1 -1
  23. package/components/SaveIndicator.vue +1 -1
  24. package/components/SideBar.vue +2 -2
  25. package/components/SingleSelect.vue +1 -1
  26. package/components/Spinner.vue +1 -1
  27. package/components/Switch.vue +1 -1
  28. package/components/TabView.vue +42 -6
  29. package/components/TableView.vue +6 -0
  30. package/components/TagInput.vue +1 -1
  31. package/components/TextInput.vue +2 -1
  32. package/components/TextInputRaw.vue +2 -1
  33. package/components/TreeView.vue +1 -1
  34. package/components/TreeViewNode.vue +6 -0
  35. package/package.json +2 -2
  36. package/viewers/GenericViewer.vue +1 -1
  37. package/vite-plugin.js +12 -2
@@ -4,7 +4,7 @@
4
4
  <template>
5
5
  <div class="pankow-bottom-bar">
6
6
  <slot>
7
- © 2024 &nbsp; <a href="https://cloudron.io" rel="noopener noreferrer" target="_blank">Cloudron</a> &nbsp; &nbsp; &nbsp; <a href="https://forum.cloudron.io" rel="noopener noreferrer" target="_blank">Forum <i class="fa fa-comments"></i></a>
7
+ © 2024 &nbsp; <a href="https://cloudron.io" rel="noopener noreferrer" target="_blank">Cloudron</a> &nbsp; &nbsp; &nbsp; <a href="https://forum.cloudron.io" rel="noopener noreferrer" target="_blank" aria-label="Forum">Forum <i class="fa fa-comments" aria-hidden="true"></i></a>
8
8
  </slot>
9
9
  </div>
10
10
  </template>
@@ -25,13 +25,13 @@ function onClick() {
25
25
  </script>
26
26
 
27
27
  <template>
28
- <div class="pankow-breadcrumb">
28
+ <div class="pankow-breadcrumb" aria-label="Breadcrumb">
29
29
  <a class="pankow-breadcrumb-item" :href="home.route" @click="home.action ? home.action(home, $event) : null" @keydown.enter="home.action ? home.action(home, $event) : null" tabindex="0">
30
30
  <Icon :class="home.icon"/> {{ home.label }}
31
31
  </a>
32
- <span v-for="item in items">
32
+ <span v-for="(item, index) in items">
33
33
  <Icon icon="fa-solid fa-chevron-right" class="pankow-breadcrumb-separator"/>
34
- <a class="pankow-breadcrumb-item" :href="item.route" @click="item.action ? item.action(home, $event) : null" @keydown.enter="item.action ? item.action(home, $event) : null" tabindex="0">
34
+ <a class="pankow-breadcrumb-item" :href="item.route" @click="item.action ? item.action(home, $event) : null" @keydown.enter="item.action ? item.action(home, $event) : null" tabindex="0" :aria-current="index === items.length - 1 ? 'page' : null">
35
35
  <Icon v-if="item.icon" :class="item.icon"/> {{ item.label }}
36
36
  </a>
37
37
  </span>
@@ -53,6 +53,7 @@ function onClick(event) {
53
53
  <template>
54
54
  <component ref="elem" :is="href ? 'a' : 'div'" class="pankow-button" role="button" type="button" :class="{ 'pankow-button-disabled': disabled }"
55
55
  @click="onClick" @keydown.enter="onClick" :href="disabled ? null : href" :target="target" tabindex="0"
56
+ :aria-label="!$slots['default'] && icon ? icon : undefined"
56
57
  :success="success || undefined" :danger="danger || undefined">
57
58
  <Menu ref="menuItem" :model="menu" v-if="menu"></Menu>
58
59
  <Spinner v-show="loading" style="stroke: white;" :class="{ 'pankow-button-icon-with-text': $slots['default'] }"/>
@@ -108,6 +109,11 @@ a.pankow-button:visited {
108
109
  line-height: inherit;
109
110
  }
110
111
 
112
+ .pankow-button .pankow-spinner {
113
+ width: 1.2em;
114
+ height: 1.2em;
115
+ }
116
+
111
117
  .pankow-button-icon-right-with-text {
112
118
  padding-left: 7px;
113
119
  }
@@ -19,7 +19,7 @@ function onClipboard() {
19
19
  </script>
20
20
 
21
21
  <template>
22
- <i class="pankow-clipboard-action fa-solid fa-copy" @click="onClipboard()"></i>
22
+ <i class="pankow-clipboard-action fa-solid fa-copy" tabindex="0" role="button" aria-label="Copy to clipboard" @click="onClipboard()" @keydown.enter="onClipboard()" @keydown.space.prevent="onClipboard()"></i>
23
23
  </template>
24
24
 
25
25
  <style>
@@ -2,6 +2,7 @@
2
2
 
3
3
  const model = defineModel();
4
4
  const props = defineProps({
5
+ id: String,
5
6
  readonly: {
6
7
  type: Boolean,
7
8
  default: false
@@ -15,7 +16,7 @@ const props = defineProps({
15
16
  </script>
16
17
 
17
18
  <template>
18
- <input class="pankow-text-input" :type="dateOnly ? 'date' : 'datetime-local'" :readonly="readonly" v-model="model"/>
19
+ <input :id="id" class="pankow-text-input" :type="dateOnly ? 'date' : 'datetime-local'" :readonly="readonly" v-model="model"/>
19
20
  </template>
20
21
 
21
22
  <style>
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
 
3
3
  import { ref, useTemplateRef, nextTick, computed } from 'vue';
4
+ import { uuidv4 } from '../utils.js';
4
5
  import Button from './Button.vue';
5
6
  import Icon from './Icon.vue';
6
7
 
@@ -60,6 +61,7 @@ const mergedStyle = computed(() => {
60
61
  })
61
62
 
62
63
  const visible = ref(false);
64
+ const dialogTitleId = ref(uuidv4());
63
65
  const dialog = useTemplateRef('dialog');
64
66
  const dialogBody = useTemplateRef('dialogBody');
65
67
  const confirmButton = useTemplateRef('confirmButton');
@@ -117,8 +119,8 @@ defineExpose({ open, close });
117
119
  <div class="pankow-dialog-backdrop" @click="onDismiss" v-show="visible" :style="{ 'z-index': zIndex }"></div>
118
120
  </Transition>
119
121
  <Transition :name="center ? 'pankow-bounce-center' : 'pankow-bounce-center-top'">
120
- <div ref="dialog" class="pankow-dialog" v-bind="$attrs" :class="{ 'pankow-dialog-center': center }" v-show="visible" @keydown.esc="onDismiss" tabindex="0" :style="mergedStyle">
121
- <div class="pankow-dialog-header" v-show="title">
122
+ <div ref="dialog" class="pankow-dialog" role="dialog" aria-modal="true" :aria-labelledby="title ? dialogTitleId : null" v-bind="$attrs" :class="{ 'pankow-dialog-center': center }" v-show="visible" @keydown.esc="onDismiss" tabindex="0" :style="mergedStyle">
123
+ <div :id="dialogTitleId" class="pankow-dialog-header" v-show="title">
122
124
  {{ title }}
123
125
  </div>
124
126
  <div class="pankow-dialog-body" ref="dialogBody" :class="{ 'pankow-dialog-body-no-header': !title, 'pankow-dialog-body-no-buttons': !(rejectLabel || confirmLabel) }">
@@ -3,6 +3,7 @@
3
3
  class="directory-view"
4
4
  :class="{ 'drop-target-active': dropTargetActive }"
5
5
  tabindex="0"
6
+ role="grid"
6
7
  ref="main"
7
8
  @keydown.up.prevent="onKeyUp($event)"
8
9
  @keydown.down.prevent="onKeyDown($event)"
@@ -56,7 +57,6 @@
56
57
  :show-size="showSize"
57
58
  :show-star="showStar"
58
59
  :show-modified="showModified"
59
- :rename-handler="renameHandler"
60
60
  :share-indicator-property="shareIndicatorProperty"
61
61
  :file-drop-indicator-property="fileDropIndicatorProperty"
62
62
  :select-handler="onSelectAndFocus"
@@ -76,7 +76,6 @@
76
76
  :fallbackIcon="fallbackIcon"
77
77
  :item="item"
78
78
  :show-star="showStar"
79
- :rename-handler="renameHandler"
80
79
  :share-indicator-property="shareIndicatorProperty"
81
80
  :file-drop-indicator-property="fileDropIndicatorProperty"
82
81
  :select-handler="onSelectAndFocus"
@@ -262,12 +261,6 @@ const props = defineProps({
262
261
  console.warn('Missing deleteHandler for DirectoryView');
263
262
  },
264
263
  },
265
- renameHandler: {
266
- type: Function,
267
- default() {
268
- console.warn('Missing renameHandler for DirectoryView');
269
- },
270
- },
271
264
  changeOwnerHandler: {
272
265
  type: Function,
273
266
  default() {
@@ -340,7 +333,7 @@ const props = defineProps({
340
333
  },
341
334
  });
342
335
 
343
- const emit = defineEmits(['selectionChanged', 'item-activated']);
336
+ const emit = defineEmits(['selectionChanged', 'item-activated', 'rename-requested']);
344
337
 
345
338
  const gridBody = useTemplateRef('gridBody');
346
339
  const contextMenu = useTemplateRef('contextMenu');
@@ -610,12 +603,6 @@ function onItemShare() {
610
603
  props.shareHandler(focusItem.value);
611
604
  }
612
605
 
613
- function onItemRenameBegin() {
614
- if (selectedCount.value !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
615
-
616
- elements[focusItem.value.id]?.onRenameBegin();
617
- }
618
-
619
606
  async function onChangeOwnerDialogSubmit(items, newOwnerUid) {
620
607
  changeOwnerDialog.busy = true;
621
608
  await props.changeOwnerHandler(items, newOwnerUid);
@@ -844,7 +831,7 @@ const contextMenuModel = ref([
844
831
  id: 'rename',
845
832
  label: props.tr('filemanager.list.menu.rename'),
846
833
  icon: 'fa-regular fa-pen-to-square',
847
- action: onItemRenameBegin,
834
+ action: () => emit('rename-requested', focusItem.value),
848
835
  visible: () => props.showRename,
849
836
  disabled: () => !props.editable || selectedCount.value > 1,
850
837
  },
@@ -1,6 +1,5 @@
1
1
  <template>
2
2
  <div ref="rowWrapper" class="grid-item-wrapper"
3
- :draggable="!rename"
4
3
  @mouseup="onSelect($event)"
5
4
  @drop="onDrop($event)"
6
5
  @dragover="onDragOver($event)"
@@ -9,15 +8,19 @@
9
8
 
10
9
  <div class="grid-item" :class="{ 'focused': item.focused, 'selected': item.selected, 'drop-target-active': dropTargetActive }">
11
10
  <div v-if="visible && showStar" class="grid-item-star" @dblclick.stop.prevent @click.stop.prevent="onToggleStar">
12
- <Icon :icon="`${item.star ? 'fa-solid' : 'fa-regular'} fa-star`" class="star-icon" :class="{ 'star-visible': item.star }" />
11
+ <span tabindex="0" role="button" :aria-label="item.star ? 'Unstar' : 'Star'" @keydown.enter.prevent="onToggleStar" @keydown.space.prevent="onToggleStar">
12
+ <Icon :icon="`${item.star ? 'fa-solid' : 'fa-regular'} fa-star`" class="star-icon" :class="{ 'star-visible': item.star }" />
13
+ </span>
13
14
  </div>
14
15
  <div v-if="visible" class="grid-item-actions" @mouseup.stop="onActionMenu($event)">
15
- <i class="fa-solid fa-ellipsis-vertical"/>
16
+ <span tabindex="0" role="button" aria-label="Actions" @click="onActionMenu($event)" @keydown.enter="onActionMenu($event)" @keydown.space.prevent="onActionMenu($event)">
17
+ <i class="fa-solid fa-ellipsis-vertical"/>
18
+ </span>
16
19
  </div>
17
20
 
18
21
  <div class="grid-item-icon">
19
22
  <template v-if="visible">
20
- <img :src="item.previewUrl || item.icon" ref="iconImage" @error="iconError($event)"/>
23
+ <img :src="item.previewUrl || item.icon" ref="iconImage" :alt="item.name" @error="iconError($event)"/>
21
24
  <i v-show="shareIndicatorProperty !== '' && !!item[shareIndicatorProperty]" class="fa-solid fa-share-nodes is-shared"></i>
22
25
  <i v-show="fileDropIndicatorProperty !== '' && !!item[fileDropIndicatorProperty]" class="fa-solid fa-cloud-arrow-up is-filedrop"></i>
23
26
  </template>
@@ -27,21 +30,10 @@
27
30
  <div v-if="!visible">
28
31
  {{ item.name }}
29
32
  </div>
30
- <div v-else-if="visible && !rename" class="grid-item-label" :title="item.name + (item.target ? ` → ${item.target}` : '')">
33
+ <div v-else-if="visible" class="grid-item-label" :title="item.name + (item.target ? ` → ${item.target}` : '')">
31
34
  <a v-if="item.href" class="open-action" @dblclick.stop @click="onOpen($event)" :href="item.href">{{ item.name }}</a>
32
35
  <span v-else>{{ item.name }}</span>
33
36
  </div>
34
- <div v-else-if="visible && rename" ref="renameCell" class="grid-item-label grid-item-rename">
35
- <TextInput
36
- v-model="newName"
37
- :disabled="renameBusy"
38
- @blur="onRenameEnd"
39
- @dblclick.stop
40
- @keydown.enter.stop="onRenameSubmit"
41
- @keydown.esc.stop="onRenameEnd"
42
- @keydown.stop
43
- />
44
- </div>
45
37
  </div>
46
38
  </div>
47
39
  </template>
@@ -51,7 +43,6 @@
51
43
  import { ref, onMounted } from 'vue';
52
44
 
53
45
  import Icon from './Icon.vue';
54
- import TextInput from './TextInput.vue';
55
46
 
56
47
  const props = defineProps({
57
48
  item: Object,
@@ -68,12 +59,6 @@ const props = defineProps({
68
59
  default: '',
69
60
  },
70
61
  fallbackIcon: String,
71
- renameHandler: {
72
- type: Function,
73
- default() {
74
- console.warn('Missing renameHandler for DirectoryViewGridItem');
75
- },
76
- },
77
62
  starHandler: {
78
63
  type: Function,
79
64
  default() {
@@ -103,12 +88,8 @@ const props = defineProps({
103
88
  const emit = defineEmits(['activated', 'action-menu']);
104
89
 
105
90
  const rowWrapper = ref(null);
106
- const renameCell = ref(null);
107
91
 
108
92
  const visible = ref(false);
109
- const rename = ref(false);
110
- const renameBusy = ref(false);
111
- const newName = ref('');
112
93
  const dropTargetActive = ref(false);
113
94
  const previewRetries = ref(0);
114
95
 
@@ -128,37 +109,6 @@ function onActionMenu(event) {
128
109
  emit('action-menu', props.item, event);
129
110
  }
130
111
 
131
- function onRenameBegin() {
132
- rename.value = true;
133
- newName.value = props.item.name;
134
-
135
- setTimeout(() => {
136
- const elem = renameCell.value?.querySelector('input.pankow-text-input');
137
- if (!elem) return;
138
-
139
- elem.focus();
140
-
141
- if (typeof elem.selectionStart !== 'undefined') {
142
- elem.selectionStart = 0;
143
- elem.selectionEnd = props.item.name.lastIndexOf('.');
144
- }
145
- }, 0);
146
- }
147
-
148
- function onRenameEnd() {
149
- rename.value = false;
150
- renameBusy.value = false;
151
- newName.value = '';
152
- }
153
-
154
- async function onRenameSubmit() {
155
- if (!newName.value) return;
156
-
157
- renameBusy.value = true;
158
- await props.renameHandler(props.item, newName.value);
159
- onRenameEnd();
160
- }
161
-
162
112
  function onOpen(event) {
163
113
  if (event.ctrlKey || event.metaKey) return;
164
114
 
@@ -228,7 +178,6 @@ onMounted(() => {
228
178
 
229
179
  defineExpose({
230
180
  highlight,
231
- onRenameBegin,
232
181
  get $el() {
233
182
  return rowWrapper.value;
234
183
  },
@@ -346,18 +295,6 @@ defineExpose({
346
295
  padding: 0 2px;
347
296
  }
348
297
 
349
- .grid-item-rename {
350
- overflow: hidden;
351
- }
352
-
353
- .grid-item-rename :deep(input) {
354
- width: 100%;
355
- box-sizing: border-box;
356
- text-align: center;
357
- font-size: 0.85rem;
358
- padding: 1px 4px;
359
- }
360
-
361
298
  .grid-item-star {
362
299
  position: absolute;
363
300
  top: 4px;
@@ -1,6 +1,5 @@
1
1
  <template>
2
2
  <div ref="rowWrapper" class="row-wrapper"
3
- :draggable="!rename"
4
3
  @mouseup="onSelect($event)"
5
4
  @drop="onDrop($event)"
6
5
  @dragover="onDragOver($event)"
@@ -12,7 +11,7 @@
12
11
  <div v-if="!visible" class="col label"></div>
13
12
 
14
13
  <div v-if="visible" class="col icon">
15
- <img :src="item.previewUrl || item.icon" ref="iconImage" @error="iconError($event)"/>
14
+ <img :src="item.previewUrl || item.icon" ref="iconImage" :alt="item.name" @error="iconError($event)"/>
16
15
  <i v-show="shareIndicatorProperty !== '' && !!item[shareIndicatorProperty]" class="fa-solid fa-share-nodes is-shared"></i>
17
16
  <i v-show="fileDropIndicatorProperty !== '' && !!item[fileDropIndicatorProperty]" class="fa-solid fa-cloud-arrow-up is-filedrop"></i>
18
17
  </div>
@@ -21,31 +20,21 @@
21
20
  <div v-if="!visible">
22
21
  {{ item.name }}
23
22
  </div>
24
- <div v-else-if="visible && !rename" class="col label">
23
+ <div v-else-if="visible" class="col label">
25
24
  <a v-show="item.href" class="open-action" @dblclick.stop @click="onOpen($event)" :href="item.href">{{ item.name }} {{ item.target ? `-> ${item.target}` : '' }}</a>
26
25
  <span v-show="!item.href">{{ item.name }} {{ item.target ? `→ ${item.target}` : '' }}</span>
27
26
  </div>
28
- <div v-else-if="visible && rename" ref="renameCell" class="col label rename">
29
- <TextInput
30
- ref="renameInput"
31
- v-model="newName"
32
- :disabled="renameBusy"
33
- @blur="onRenameEnd"
34
- @dblclick.stop
35
- @keydown.enter.stop="onRenameSubmit"
36
- @keydown.esc.stop="onRenameEnd"
37
- @keydown.stop
38
- />
39
- </div>
40
27
 
41
28
  <div v-if="visible && showStar" class="col star">
42
- <Icon :icon="`${item.star ? 'fa-solid' : 'fa-regular'} fa-star`" class="star-icon" :class="{ 'star-visible': item.star }" @dblclick.stop.prevent @click.stop.prevent="onToggleStar" />
29
+ <span tabindex="0" role="button" :aria-label="item.star ? 'Unstar' : 'Star'" @dblclick.stop.prevent @click.stop.prevent="onToggleStar" @keydown.enter.prevent="onToggleStar" @keydown.space.prevent="onToggleStar">
30
+ <Icon :icon="`${item.star ? 'fa-solid' : 'fa-regular'} fa-star`" class="star-icon" :class="{ 'star-visible': item.star }" />
31
+ </span>
43
32
  </div>
44
33
  <div v-if="visible && showOwner" class="col owner">{{ item.owner }}</div>
45
34
  <div v-if="visible && showSize" class="col size">{{ prettyFileSize(item.size) }}</div>
46
35
  <div v-if="visible && showModified" class="col modified" :title="item.modified.toLocaleString()">{{ prettyDate(item.modified) }}</div>
47
36
  <div v-if="visible" class="col actions">
48
- <div @mouseup.stop="onActionMenu($event)"><i class="fa-solid fa-ellipsis-vertical"/></div>
37
+ <div @mouseup.stop="onActionMenu($event)"><span tabindex="0" role="button" aria-label="Actions" @click="onActionMenu($event)" @keydown.enter="onActionMenu($event)" @keydown.space.prevent="onActionMenu($event)"><i class="fa-solid fa-ellipsis-vertical"/></span></div>
49
38
  </div>
50
39
  </div>
51
40
  </div>
@@ -58,7 +47,6 @@ import { ref, onMounted } from 'vue';
58
47
  import { prettyDate, prettyFileSize } from '../utils.js';
59
48
 
60
49
  import Icon from './Icon.vue';
61
- import TextInput from './TextInput.vue';
62
50
 
63
51
  const props = defineProps({
64
52
  item: Object,
@@ -87,12 +75,6 @@ const props = defineProps({
87
75
  default: '',
88
76
  },
89
77
  fallbackIcon: String,
90
- renameHandler: {
91
- type: Function,
92
- default() {
93
- console.warn('Missing renameHandler for DirectoryViewItem');
94
- },
95
- },
96
78
  starHandler: {
97
79
  type: Function,
98
80
  default() {
@@ -122,12 +104,8 @@ const props = defineProps({
122
104
  const emit = defineEmits(['activated', 'action-menu']);
123
105
 
124
106
  const rowWrapper = ref(null);
125
- const renameCell = ref(null);
126
107
 
127
108
  const visible = ref(false);
128
- const rename = ref(false);
129
- const renameBusy = ref(false);
130
- const newName = ref('');
131
109
  const dropTargetActive = ref(false);
132
110
  const previewRetries = ref(0);
133
111
 
@@ -147,37 +125,6 @@ function onActionMenu(event) {
147
125
  emit('action-menu', props.item, event);
148
126
  }
149
127
 
150
- function onRenameBegin() {
151
- rename.value = true;
152
- newName.value = props.item.name;
153
-
154
- setTimeout(() => {
155
- const elem = renameCell.value?.querySelector('input.pankow-text-input');
156
- if (!elem) return;
157
-
158
- elem.focus();
159
-
160
- if (typeof elem.selectionStart !== 'undefined') {
161
- elem.selectionStart = 0;
162
- elem.selectionEnd = props.item.name.lastIndexOf('.');
163
- }
164
- }, 0);
165
- }
166
-
167
- function onRenameEnd() {
168
- rename.value = false;
169
- renameBusy.value = false;
170
- newName.value = '';
171
- }
172
-
173
- async function onRenameSubmit() {
174
- if (!newName.value) return;
175
-
176
- renameBusy.value = true;
177
- await props.renameHandler(props.item, newName.value);
178
- onRenameEnd();
179
- }
180
-
181
128
  function onOpen(event) {
182
129
  if (event.ctrlKey || event.metaKey) return;
183
130
 
@@ -247,7 +194,6 @@ onMounted(() => {
247
194
 
248
195
  defineExpose({
249
196
  highlight,
250
- onRenameBegin,
251
197
  get $el() {
252
198
  return rowWrapper.value;
253
199
  },
@@ -372,10 +318,6 @@ defineExpose({
372
318
  display: block;
373
319
  }
374
320
 
375
- .label.rename > button {
376
- display: block;
377
- }
378
-
379
321
  .size {
380
322
  width: 100px;
381
323
  margin: auto;
@@ -4,6 +4,7 @@ import { useTemplateRef } from 'vue';
4
4
 
5
5
  const model = defineModel();
6
6
  const props = defineProps({
7
+ id: String,
7
8
  placeholder: String,
8
9
  readonly: {
9
10
  type: Boolean,
@@ -21,7 +22,7 @@ defineExpose({ focus });
21
22
  </script>
22
23
 
23
24
  <template>
24
- <input ref="inputElement" class="pankow-text-input" type="email" :placeholder="placeholder" :readonly="readonly" :required="$attrs['required']" v-model.trim="model"/>
25
+ <input :id="id" ref="inputElement" class="pankow-text-input" type="email" :placeholder="placeholder" :readonly="readonly" :required="$attrs['required']" v-model.trim="model"/>
25
26
  </template>
26
27
 
27
28
  <style>
@@ -6,8 +6,8 @@
6
6
  <Button danger small tool @click="onCancelUpload" icon="fa-solid fa-xmark"></Button>
7
7
  </div>
8
8
 
9
- <input type="file" ref="uploadFileInput" style="display: none" multiple/>
10
- <input type="file" ref="uploadFolderInput" style="display: none" multiple webkitdirectory directory/>
9
+ <input type="file" ref="uploadFileInput" style="display: none" multiple aria-label="Upload files"/>
10
+ <input type="file" ref="uploadFolderInput" style="display: none" multiple webkitdirectory directory aria-label="Upload folder"/>
11
11
 
12
12
  <InputDialog ref="inputDialog" />
13
13
  </div>
@@ -5,6 +5,7 @@ import { computed } from 'vue';
5
5
  const model = defineModel({ type: [String, null], default: null });
6
6
 
7
7
  const props = defineProps({
8
+ id: String,
8
9
  multiline: {
9
10
  type: Boolean,
10
11
  default: false
@@ -27,7 +28,7 @@ const displayValue = computed({
27
28
 
28
29
  <template>
29
30
  <template v-if="!multiline">
30
- <input class="pankow-masked-input" :value="displayValue" @input="displayValue = $event.target.value" @focus="$event.target.select()" />
31
+ <input :id="id" class="pankow-masked-input" :value="displayValue" @input="displayValue = $event.target.value" @focus="$event.target.select()" />
31
32
  </template>
32
33
  <template v-else>
33
34
  <textarea :value="displayValue" @input="displayValue = $event.target.value" @focus="$event.target.select()"></textarea>
@@ -325,7 +325,7 @@ defineExpose({
325
325
  <teleport to="#app">
326
326
  <div class="pankow-menu-backdrop" @click="onBackdrop($event)" @contextmenu="onBackdrop($event)" v-show="isOpen"></div>
327
327
  <Transition :name="rollUp ? 'pankow-roll-up' : 'pankow-roll-down'">
328
- <div class="pankow-menu" :class="{ 'pankow-menu-keyboard-nav': keyboardNav }" v-show="isOpen" ref="container" tabindex="0" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.esc.stop="close()" @keydown="onKeyDown" @mousemove="onMouseMove">
328
+ <div class="pankow-menu" role="menu" :aria-label="searchThreshold < model.length ? 'Filterable menu' : 'Menu'" :class="{ 'pankow-menu-keyboard-nav': keyboardNav }" v-show="isOpen" ref="container" tabindex="0" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.esc.stop="close()" @keydown="onKeyDown" @mousemove="onMouseMove">
329
329
  <TextInput placeholder="Filter ..." autocomplete="off" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.stop @keydown.esc.stop="close()" @click.stop style="width: 100%; border: 0; padding: 8px 12px;" v-model="searchString" v-if="searchThreshold < model.length"/>
330
330
  <component v-for="item in visibleItems" ref="itemElements" :is="item.type || (item.href ? MenuItemLink : MenuItem)" @activated="onItemActivated(item)" :item="item" :has-icons="hasIcons" />
331
331
  <MenuItem v-if="model.length === 0" :item="emptyItem"/>
@@ -42,7 +42,7 @@ function onActivated() {
42
42
  </script>
43
43
 
44
44
  <template>
45
- <div class="pankow-menu-item" :class="{'pankow-menu-item-disabled': isDisabled }" @click.stop="onActivated" :separator="item.separator" v-if="isVisible" tabindex="0" @keydown.enter.stop="onActivated" @keydown.space.stop="onActivated">
45
+ <div class="pankow-menu-item" role="menuitem" :aria-checked="item.checkbox ? item.checked : undefined" :class="{'pankow-menu-item-disabled': isDisabled }" @click.stop="onActivated" :separator="item.separator" v-if="isVisible" tabindex="0" @keydown.enter.stop="onActivated" @keydown.space.stop="onActivated">
46
46
  <CheckBox v-model="item.checked" v-if="item.checkbox" style="margin-right: 4px; display: inline-flex;"/>
47
47
  <span v-if="!item.separator" :style="{ width: (hasIcons && !item.checkbox) ? '29px' : 0 }"><Icon v-show="hasIcons" :icon="item.icon"/></span>{{ item.label }}
48
48
  <div class="pankow-menu-item-separator-line" v-if="item.separator"></div>
@@ -40,7 +40,7 @@ function onActivated() {
40
40
  </script>
41
41
 
42
42
  <template>
43
- <a class="pankow-menu-item" style="display: block" :href="isDisabled ? null : item.href" :target="item.target || null" @click="onActivated" :class="{ 'pankow-menu-item-disabled': isDisabled }" v-if="isVisible">
43
+ <a class="pankow-menu-item" role="menuitem" style="display: block" :href="isDisabled ? null : item.href" :target="item.target || null" @click="onActivated" :class="{ 'pankow-menu-item-disabled': isDisabled }" v-if="isVisible">
44
44
  <span :style="{ width: hasIcons ? '29px' : 0 }" style="display: inline-block;"><Icon v-show="hasIcons" :icon="item.icon" /></span>{{ item.label }}
45
45
  </a>
46
46
  </template>
@@ -162,7 +162,7 @@ onMounted(() => {
162
162
  </script>
163
163
 
164
164
  <template>
165
- <div class="pankow-multiselect" :class="{ 'pankow-multiselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop="onOpen" @keydown.up.stop="onOpen" @keydown.esc="onEscape">
165
+ <div class="pankow-multiselect" role="combobox" :aria-expanded="menu?.isOpen ?? false" aria-haspopup="listbox" :class="{ 'pankow-multiselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop="onOpen" @keydown.up.stop="onOpen" @keydown.esc="onEscape">
166
166
  <!-- native select for required and accessibility handling -->
167
167
  <select ref="nativeSelect" :required="$attrs['required']" multiple style="display: none">
168
168
  <option value=""></option>
@@ -1,9 +1,11 @@
1
1
  <template>
2
- <TransitionGroup name="pankow-notification-fade" :class="`pankow-notification-container ${position}`" tag="div">
2
+ <TransitionGroup name="pankow-notification-fade" :class="`pankow-notification-container ${position}`" tag="div" role="alert" aria-live="polite">
3
3
  <div v-for="message in messages" :key="message.id">
4
4
  <div class="pankow-notification" :[message.type]="''">
5
5
  {{ message.text }}
6
- <Icon @click="removeNotify(message.id)" icon="fa-solid fa-xmark" class="pankow-notification-close"/>
6
+ <span tabindex="0" role="button" aria-label="Close notification" @keydown.enter="removeNotify(message.id)" @keydown.space.prevent="removeNotify(message.id)" @click="removeNotify(message.id)" class="pankow-notification-close">
7
+ <Icon icon="fa-solid fa-xmark"/>
8
+ </span>
7
9
  </div>
8
10
  </div>
9
11
  </TransitionGroup>
@@ -2,6 +2,7 @@
2
2
 
3
3
  const model = defineModel();
4
4
  const props = defineProps({
5
+ id: String,
5
6
  placeholder: String,
6
7
  readonly: {
7
8
  type: Boolean,
@@ -21,7 +22,7 @@ const props = defineProps({
21
22
  </script>
22
23
 
23
24
  <template>
24
- <input class="pankow-text-input" type="number" :step="step" :min="min" :max="max" :placeholder="placeholder" :readonly="readonly" :value="model" @input="$emit('update:modelValue', parseInt($event.target.value))"/>
25
+ <input :id="id" class="pankow-text-input" type="number" :step="step" :min="min" :max="max" :placeholder="placeholder" :readonly="readonly" :value="model" @input="$emit('update:modelValue', parseInt($event.target.value))"/>
25
26
  </template>
26
27
 
27
28
  <style>
@@ -19,7 +19,7 @@ const props = defineProps({
19
19
 
20
20
  <template>
21
21
  <Transition name="pankow-roll-down">
22
- <a class="pankow-offline-banner" v-show="active" :href="href" target="_blank">
22
+ <a class="pankow-offline-banner" role="alert" v-show="active" :href="href" target="_blank">
23
23
  <i class="fa fa-solid fa-spin fa-circle-notch"/> {{ label }}
24
24
  </a>
25
25
  </Transition>
@@ -30,7 +30,7 @@ function toggleReveal() {
30
30
  <template>
31
31
  <div class="pankow-password">
32
32
  <input class="pankow-password-input" :id="id" :type="revealed ? 'text' : 'password'" :disabled="disabled ? true : undefined" :placeholder="placeholder" :autofocus="$attrs['autofocus']" :required="$attrs['required']" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"/>
33
- <i class="pankow-password-reveal" v-html="revealIcon" @click="toggleReveal"></i>
33
+ <span class="pankow-password-reveal" tabindex="0" role="button" :aria-label="revealed ? 'Hide password' : 'Show password'" @keydown.enter="toggleReveal" @keydown.space.prevent="toggleReveal" @click="toggleReveal" v-html="revealIcon"></span>
34
34
  </div>
35
35
  </template>
36
36
 
@@ -144,7 +144,7 @@ defineExpose({
144
144
  <teleport to="#app">
145
145
  <div class="pankow-popover-backdrop" @click="onBackdrop($event)" @contextmenu="onBackdrop($event)" v-show="isOpen"></div>
146
146
  <Transition :name="rollUp ? 'pankow-animation-pop-up' : 'pankow-animation-pop-down'">
147
- <div v-show="isOpen" class="pankow-popover" ref="container" :style="{ 'max-width': width || null, 'max-height': height || null }">
147
+ <div v-show="isOpen" class="pankow-popover" ref="container" role="dialog" aria-label="Popover" :style="{ 'max-width': width || null, 'max-height': height || null }">
148
148
  <slot></slot>
149
149
  </div>
150
150
  </Transition>
@@ -35,7 +35,7 @@ const normalizedValue = computed(() => {
35
35
  </script>
36
36
 
37
37
  <template>
38
- <div class="pankow-progress-bar">
38
+ <div class="pankow-progress-bar" role="progressbar" :aria-valuenow="normalizedValue" aria-valuemin="0" aria-valuemax="100" :aria-label="'Progress: ' + normalizedValue + '%'">
39
39
  <div class="pankow-progress-bar-label" v-show="showLabel"><slot>{{ value + ' %' }}</slot></div>
40
40
  <div :class="{ 'pankow-progress-bar-background': showTrack }">
41
41
  <div v-if="mode !== 'indeterminate'" class="pankow-progress-bar-filled" :style="{ width: normalizedValue+'%', height: slim ? '2px' : '6px' }">
@@ -42,7 +42,7 @@ defineExpose({ success: showSuccess, error: showError });
42
42
  <div class="pankow-save-indicator-wrapper">
43
43
  <slot />
44
44
  <Transition name="pankow-save-indicator-bounce">
45
- <div class="pankow-save-indicator" v-if="visible" :class="{ success: success, error: !success }"><Icon :icon="success ? 'fa-solid fa-check' : 'fa-solid fa-xmark'"/></div>
45
+ <div class="pankow-save-indicator" role="status" aria-live="polite" v-if="visible" :class="{ success: success, error: !success }"><Icon :icon="success ? 'fa-solid fa-check' : 'fa-solid fa-xmark'"/></div>
46
46
  </Transition>
47
47
  </div>
48
48
  </template>
@@ -38,8 +38,8 @@ defineExpose({ open, close });
38
38
  </Transition>
39
39
  <div class="pankow-sidebar" ref="sideBar" :class="{ 'pankow-sidebar-closed': !isVisible }">
40
40
  <Transition name="pankow-scale">
41
- <div class="pankow-sidebar-close-action" v-if="isVisible" @click="close()"><i class="fa-solid fa-xmark"></i></div>
42
- <div class="pankow-sidebar-open-action" v-else-if="showOpenAction" @click="open()"><i class="fa-solid fa-bars"></i></div>
41
+ <div class="pankow-sidebar-close-action" v-if="isVisible" tabindex="0" role="button" aria-label="Close sidebar" @click="close()" @keydown.enter="close()" @keydown.space.prevent="close()"><i class="fa-solid fa-xmark"></i></div>
42
+ <div class="pankow-sidebar-open-action" v-else-if="showOpenAction" tabindex="0" role="button" aria-label="Open sidebar" @click="open()" @keydown.enter="open()" @keydown.space.prevent="open()"><i class="fa-solid fa-bars"></i></div>
43
43
  </Transition>
44
44
  <div class="pankow-sidebar-inner">
45
45
  <slot></slot>
@@ -176,7 +176,7 @@ watchEffect(handleDefaultSelect);
176
176
  </script>
177
177
 
178
178
  <template>
179
- <div class="pankow-singleselect" :class="{ 'pankow-singleselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop.prevent="onSelectNext" @keydown.up.stop.prevent="onSelectPrev" @keydown="onKeyDown($event)">
179
+ <div class="pankow-singleselect" role="combobox" :aria-expanded="menu?.isOpen ?? false" aria-haspopup="listbox" :class="{ 'pankow-singleselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop.prevent="onSelectNext" @keydown.up.stop.prevent="onSelectPrev" @keydown="onKeyDown($event)">
180
180
  <!-- native select for required and accessibility handling -->
181
181
  <select v-model="selectedKey" :id="id" ref="nativeSelect" :required="$attrs['required']" style="display: none">
182
182
  <option value=""></option>
@@ -2,7 +2,7 @@
2
2
  </script>
3
3
 
4
4
  <template>
5
- <div class="pankow-spinner"></div>
5
+ <div class="pankow-spinner" role="status" aria-label="Loading"></div>
6
6
  </template>
7
7
 
8
8
  <style>
@@ -22,7 +22,7 @@ function onChange(event) {
22
22
 
23
23
  <template>
24
24
  <label :for="id || internalId" class="pankow-switch" :class="{ 'pankow-switch-disabled': disabled }">
25
- <input :id="id || internalId" class="pankow-switch-input" type="checkbox" v-model="model" :disabled="disabled" @change="onChange"/>
25
+ <input :id="id || internalId" class="pankow-switch-input" type="checkbox" role="switch" :aria-checked="model" v-model="model" :disabled="disabled" @change="onChange"/>
26
26
  <span></span>
27
27
  <div class="pankow-switch-input-label">
28
28
  <slot>{{ label }}</slot>
@@ -1,15 +1,15 @@
1
1
  <template>
2
2
  <div class="pankow-tabview" :top="tabPosition === 'top' ? true : null" :left="tabPosition === 'left' ? true : null" :right="tabPosition === 'right' ? true : null" :bottom="tabPosition === 'bottom' ? true : null">
3
- <div class="pankow-tabview-tabs" v-if="tabPosition === 'top' || tabPosition === 'left'">
4
- <div class="pankow-tabview-tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)">{{ tabs[tab] }}</div>
3
+ <div class="pankow-tabview-tabs" role="tablist" :aria-label="tabPosition === 'top' || tabPosition === 'bottom' ? 'Tabs' : 'Tabs'" v-if="tabPosition === 'top' || tabPosition === 'left'">
4
+ <div class="pankow-tabview-tab" role="tab" :tabindex="active === tab ? 0 : -1" :aria-selected="active === tab ? 'true' : 'false'" :aria-controls="'tabpanel-'+tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)" @keydown="onTabKeydown($event, tab)">{{ tabs[tab] }}</div>
5
5
  </div>
6
6
  <div class="pankow-tabview-content">
7
- <div v-for="tab in Object.keys(tabs)">
7
+ <div v-for="tab in Object.keys(tabs)" :id="'tabpanel-'+tab" role="tabpanel" :aria-labelledby="tab" tabindex="0">
8
8
  <slot :name="tab" v-if="active === tab"/>
9
9
  </div>
10
10
  </div>
11
- <div class="pankow-tabview-tabs" v-if="tabPosition === 'bottom' || tabPosition === 'right'">
12
- <div class="pankow-tabview-tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)">{{ tabs[tab] }}</div>
11
+ <div class="pankow-tabview-tabs" role="tablist" :aria-label="tabPosition === 'top' || tabPosition === 'bottom' ? 'Tabs' : 'Tabs'" v-if="tabPosition === 'bottom' || tabPosition === 'right'">
12
+ <div class="pankow-tabview-tab" role="tab" :tabindex="active === tab ? 0 : -1" :aria-selected="active === tab ? 'true' : 'false'" :aria-controls="'tabpanel-'+tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)" @keydown="onTabKeydown($event, tab)">{{ tabs[tab] }}</div>
13
13
  </div>
14
14
  </div>
15
15
  </template>
@@ -43,6 +43,42 @@ function open(key) {
43
43
  emit('changed', key);
44
44
  }
45
45
 
46
+ function onTabKeydown(event, currentKey) {
47
+ const keys = Object.keys(props.tabs);
48
+ const index = keys.indexOf(currentKey);
49
+ const isVertical = props.tabPosition === 'left' || props.tabPosition === 'right';
50
+
51
+ if (isVertical) {
52
+ if (event.key === 'ArrowDown') {
53
+ event.preventDefault();
54
+ const next = index + 1 >= keys.length ? 0 : index + 1;
55
+ open(keys[next]);
56
+ } else if (event.key === 'ArrowUp') {
57
+ event.preventDefault();
58
+ const prev = index <= 0 ? keys.length - 1 : index - 1;
59
+ open(keys[prev]);
60
+ }
61
+ } else {
62
+ if (event.key === 'ArrowRight') {
63
+ event.preventDefault();
64
+ const next = index + 1 >= keys.length ? 0 : index + 1;
65
+ open(keys[next]);
66
+ } else if (event.key === 'ArrowLeft') {
67
+ event.preventDefault();
68
+ const prev = index <= 0 ? keys.length - 1 : index - 1;
69
+ open(keys[prev]);
70
+ }
71
+ }
72
+
73
+ if (event.key === 'Home') {
74
+ event.preventDefault();
75
+ open(keys[0]);
76
+ } else if (event.key === 'End') {
77
+ event.preventDefault();
78
+ open(keys[keys.length - 1]);
79
+ }
80
+ }
81
+
46
82
  </script>
47
83
 
48
84
  <style>
@@ -153,4 +189,4 @@ function open(key) {
153
189
  margin-right: 10px;
154
190
  }
155
191
 
156
- </style>
192
+ </style>
@@ -103,7 +103,13 @@ onMounted(() => {
103
103
  class="pankow-table-header-column"
104
104
  :class="{ 'pankow-table-header-column-sortable': columns[column].sort, 'pankow-table-cell-hide-mobile': columns[column].hideMobile }"
105
105
  :style="{ 'text-align': columns[column].align || null, width: typeof columns[column].width === 'string' ? columns[column].width : 'auto' }"
106
+ :aria-sort="sortBy === column ? (sortOrder === SORT_ORDER.ASC ? 'ascending' : 'descending') : 'none'"
107
+ scope="col"
108
+ :tabindex="columns[column].sort ? 0 : null"
109
+ :aria-label="columns[column].sort ? 'Sort by ' + columns[column].label : columns[column].label"
106
110
  @click="toggleSort(column)"
111
+ @keydown.enter="toggleSort(column)"
112
+ @keydown.space.prevent="toggleSort(column)"
107
113
  >
108
114
  <Icon v-if="columns[column].icon" :icon="columns[column].icon" />
109
115
  {{ columns[column].label }}
@@ -41,7 +41,7 @@ function onRemoveIndex(index) {
41
41
 
42
42
  <template>
43
43
  <div class="pankow-tag-input">
44
- <div class="pankow-tag-input-tag" v-for="(tag, index) in model" :key="index">{{ tag }} <i class="fa-solid fa-xmark pankow-tag-input-tag-remove" @click="onRemoveIndex(index)"></i></div>
44
+ <div class="pankow-tag-input-tag" v-for="(tag, index) in model" :key="index">{{ tag }} <span tabindex="0" role="button" :aria-label="'Remove ' + tag" class="pankow-tag-input-tag-remove" @click="onRemoveIndex(index)" @keydown.enter="onRemoveIndex(index)" @keydown.space.prevent="onRemoveIndex(index)"><i class="fa-solid fa-xmark"></i></span></div>
45
45
  <input class="pankow-tag-input-input" :id="id" :placeholder="placeholder" :autofocus="$attrs['autofocus']" @keydown="onKey($event)" @blur="onBlur"/>
46
46
  </div>
47
47
  </template>
@@ -6,6 +6,7 @@ import { useTemplateRef } from 'vue';
6
6
 
7
7
  const model = defineModel();
8
8
  const props = defineProps({
9
+ id: String,
9
10
  placeholder: String,
10
11
  readonly: {
11
12
  type: Boolean,
@@ -23,7 +24,7 @@ defineExpose({ focus });
23
24
  </script>
24
25
 
25
26
  <template>
26
- <input ref="inputElement" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
27
+ <input :id="id" ref="inputElement" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
27
28
  </template>
28
29
 
29
30
  <style>
@@ -4,6 +4,7 @@
4
4
 
5
5
  const model = defineModel();
6
6
  const props = defineProps({
7
+ id: String,
7
8
  placeholder: String,
8
9
  readonly: {
9
10
  type: Boolean,
@@ -14,7 +15,7 @@ const props = defineProps({
14
15
  </script>
15
16
 
16
17
  <template>
17
- <input class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model="model"/>
18
+ <input :id="id" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model="model"/>
18
19
  </template>
19
20
 
20
21
  <style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="tree-view">
2
+ <div class="tree-view" role="tree">
3
3
  <TreeViewNode
4
4
  :entry="rootEntry"
5
5
  :list-files="listFiles"
@@ -4,7 +4,13 @@
4
4
  class="tree-view-node-row"
5
5
  :class="{ 'active': isActive, 'drop-target': dropTargetActive }"
6
6
  :style="{ paddingLeft: (props.depth * 16 + 4) + 'px' }"
7
+ tabindex="0"
8
+ role="treeitem"
9
+ :aria-expanded="expanded"
10
+ :aria-selected="isActive"
7
11
  @click="onRowClick"
12
+ @keydown.enter="onRowClick"
13
+ @keydown.space.prevent="onRowClick"
8
14
  @dragover.stop.prevent="onDragOver"
9
15
  @dragenter.stop.prevent="onDragEnter"
10
16
  @dragleave="onDragLeave"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudron/pankow",
3
3
  "private": false,
4
- "version": "4.3.6",
4
+ "version": "4.4.1",
5
5
  "description": "Vue 3 UI component library for Cloudron apps",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@highlightjs/vue-plugin": "^2.1.0",
51
- "@unhead/vue": "^3.2.1",
51
+ "@unhead/vue": "^3.2.3",
52
52
  "@vitejs/plugin-vue": "^6.0.8",
53
53
  "highlight.js": "^11.11.1",
54
54
  "typescript": "^7.0.2",
@@ -13,7 +13,7 @@
13
13
  <template #body>
14
14
  <div class="content">
15
15
  <div>
16
- <img class="preview-icon" :src="entry.previewUrl" />
16
+ <img class="preview-icon" :src="entry.previewUrl" :alt="entry.fileName" />
17
17
  <h1>{{ entry.fileName }}</h1>
18
18
  <Button icon="fa-solid fa-download" :href="entry.downloadFileUrl">Download</Button>
19
19
  </div>
package/vite-plugin.js CHANGED
@@ -1,5 +1,6 @@
1
- import { dirname } from 'node:path';
1
+ import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
+ import { existsSync } from 'node:fs';
3
4
 
4
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
5
6
 
@@ -25,6 +26,15 @@ export default function pankowPlugin() {
25
26
  if (Array.isArray(allow) && !allow.includes(__dirname)) {
26
27
  allow.push(__dirname);
27
28
  }
28
- }
29
+ },
30
+ resolveId(source) {
31
+ if (source.includes('?worker') && source.startsWith('monaco-editor/esm/vs/')) {
32
+ const cleanId = source.replace(/\?worker$/, '');
33
+ const filePath = resolve(__dirname, 'node_modules/', cleanId + '.js');
34
+ if (existsSync(filePath)) {
35
+ return filePath + '?worker';
36
+ }
37
+ }
38
+ },
29
39
  };
30
40
  }