@ditojs/admin 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dito-admin.es.js +2043 -1811
- package/dist/dito-admin.umd.js +5 -4
- package/dist/style.css +1 -1
- package/package.json +5 -5
- package/src/components/DitoContainer.vue +1 -6
- package/src/components/DitoErrors.vue +52 -4
- package/src/components/DitoHeader.vue +1 -1
- package/src/components/DitoPane.vue +1 -1
- package/src/components/DitoRoot.vue +24 -6
- package/src/components/DitoSchema.vue +22 -3
- package/src/components/DitoUploadFile.vue +198 -0
- package/src/components/index.js +1 -0
- package/src/mixins/DitoMixin.js +24 -5
- package/src/mixins/TypeMixin.js +21 -13
- package/src/mixins/ValidationMixin.js +9 -1
- package/src/mixins/ValidatorMixin.js +1 -5
- package/src/styles/_info.scss +0 -16
- package/src/styles/_tippy.scss +39 -0
- package/src/styles/style.scss +2 -1
- package/src/types/DitoTypeButton.vue +1 -0
- package/src/types/DitoTypeCheckbox.vue +1 -0
- package/src/types/DitoTypeComputed.vue +1 -0
- package/src/types/DitoTypeList.vue +2 -2
- package/src/types/DitoTypeMarkup.vue +6 -3
- package/src/types/DitoTypeNumber.vue +1 -0
- package/src/types/DitoTypeProgress.vue +1 -0
- package/src/types/DitoTypeSwitch.vue +1 -0
- package/src/types/DitoTypeText.vue +1 -0
- package/src/types/DitoTypeTextarea.vue +1 -0
- package/src/types/DitoTypeUpload.vue +196 -77
- package/src/utils/schema.js +1 -1
- package/src/utils/uid.js +7 -4
package/src/styles/style.scss
CHANGED
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
:dataPath="getDataPath(index)"
|
|
81
81
|
:data="item"
|
|
82
82
|
:meta="nestedMeta"
|
|
83
|
-
:store="getChildStore(index)"
|
|
83
|
+
:store="getChildStore(getItemUid(schema, item), index)"
|
|
84
84
|
:disabled="disabled || isLoading"
|
|
85
85
|
:collapsed="collapsed"
|
|
86
86
|
:collapsible="collapsible"
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
:dataPath="getDataPath(index)"
|
|
118
118
|
:data="item"
|
|
119
119
|
:meta="nestedMeta"
|
|
120
|
-
:store="getChildStore(index)"
|
|
120
|
+
:store="getChildStore(getItemUid(schema, item), index)"
|
|
121
121
|
@delete="deleteItem(item, index)"
|
|
122
122
|
)
|
|
123
123
|
//- Render create buttons inside table when not in a single component view:
|
|
@@ -379,6 +379,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
379
379
|
|
|
380
380
|
getButtons(settingsName, descriptions) {
|
|
381
381
|
const list = []
|
|
382
|
+
const { commands } = this.editor
|
|
382
383
|
|
|
383
384
|
const addButton = ({ name, icon, attributes, ignoreActive, onClick }) => {
|
|
384
385
|
list.push({
|
|
@@ -389,8 +390,11 @@ export default DitoTypeComponent.register('markup', {
|
|
|
389
390
|
(ignoreActive == null || !ignoreActive())
|
|
390
391
|
),
|
|
391
392
|
onClick: () => {
|
|
392
|
-
const key =
|
|
393
|
-
|
|
393
|
+
const key =
|
|
394
|
+
name in commands
|
|
395
|
+
? name
|
|
396
|
+
: `toggle${camelize(name, true)}`
|
|
397
|
+
if (key in commands) {
|
|
394
398
|
const command = attributes =>
|
|
395
399
|
this.editor.chain()[key](attributes).focus().run()
|
|
396
400
|
onClick
|
|
@@ -439,7 +443,6 @@ export default DitoTypeComponent.register('markup', {
|
|
|
439
443
|
},
|
|
440
444
|
|
|
441
445
|
focusElement() {
|
|
442
|
-
this.$el.scrollIntoView?.()
|
|
443
446
|
this.editor.focus()
|
|
444
447
|
}
|
|
445
448
|
}
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-upload
|
|
3
|
+
//- In order to handle upload buttons in multiple possible places, depending
|
|
4
|
+
//- on whether they handle single or multiple uploads, render the upload
|
|
5
|
+
//- component invisibly at the root, and delegate the click events to it from
|
|
6
|
+
//- the buttons rendered further below. Luckily this works surprisingly well.
|
|
7
|
+
VueUpload.dito-upload-input(
|
|
8
|
+
ref="upload"
|
|
9
|
+
v-model="uploads"
|
|
10
|
+
:inputId="dataPath"
|
|
11
|
+
:name="dataPath"
|
|
12
|
+
:disabled="disabled"
|
|
13
|
+
:postAction="uploadPath"
|
|
14
|
+
:extensions="extensions"
|
|
15
|
+
:accept="accept"
|
|
16
|
+
:multiple="multiple"
|
|
17
|
+
:size="maxSize"
|
|
18
|
+
:drop="$el?.closest('.dito-container')"
|
|
19
|
+
:dropDirectory="true"
|
|
20
|
+
@input-filter="onInputFilter"
|
|
21
|
+
@input-file="onInputFile"
|
|
22
|
+
)
|
|
3
23
|
table.dito-table.dito-table-separators.dito-table-background
|
|
4
|
-
//- Styling comes from DitoTableHead
|
|
24
|
+
//- Styling comes from `DitoTableHead`
|
|
5
25
|
thead.dito-table-head
|
|
6
26
|
tr
|
|
7
27
|
th
|
|
8
|
-
span
|
|
28
|
+
span File
|
|
9
29
|
th
|
|
10
30
|
span Size
|
|
11
31
|
th
|
|
@@ -18,48 +38,81 @@
|
|
|
18
38
|
:options="getSortableOptions(false)"
|
|
19
39
|
:draggable="draggable"
|
|
20
40
|
)
|
|
21
|
-
|
|
22
|
-
v-
|
|
23
|
-
:key="file.key"
|
|
41
|
+
template(
|
|
42
|
+
v-if="multiple || !isUploadActive"
|
|
24
43
|
)
|
|
25
|
-
|
|
26
|
-
v-
|
|
44
|
+
tr(
|
|
45
|
+
v-for="(file, index) in files"
|
|
46
|
+
:key="file.key"
|
|
27
47
|
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
v-if="file.upload"
|
|
48
|
+
td(
|
|
49
|
+
v-if="render"
|
|
50
|
+
v-html="renderFile(file, index)"
|
|
32
51
|
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
template(
|
|
42
|
-
v-else-if="file.upload.success"
|
|
52
|
+
td(
|
|
53
|
+
v-else-if="downloadUrls[index]"
|
|
54
|
+
)
|
|
55
|
+
a(
|
|
56
|
+
:download="file.name"
|
|
57
|
+
:href="downloadUrls[index]"
|
|
58
|
+
target="_blank"
|
|
59
|
+
@click.prevent="onClickDownload(file, index)"
|
|
43
60
|
)
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
DitoUploadFile(
|
|
62
|
+
:file="file"
|
|
63
|
+
:thumbnail="thumbnails"
|
|
64
|
+
:thumbnailUrl="thumbnailUrls[index]"
|
|
65
|
+
)
|
|
66
|
+
td(
|
|
46
67
|
v-else
|
|
47
68
|
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
a.dito-button(
|
|
53
|
-
v-if="draggable"
|
|
54
|
-
v-bind="getButtonAttributes(verbs.drag)"
|
|
69
|
+
DitoUploadFile(
|
|
70
|
+
:file="file"
|
|
71
|
+
:thumbnail="thumbnails"
|
|
72
|
+
:thumbnailUrl="thumbnailUrls[index]"
|
|
55
73
|
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
v-
|
|
60
|
-
|
|
74
|
+
td {{ formatFileSize(file.size) }}
|
|
75
|
+
td
|
|
76
|
+
template(
|
|
77
|
+
v-if="file.upload"
|
|
78
|
+
)
|
|
79
|
+
template(
|
|
80
|
+
v-if="file.upload.error"
|
|
81
|
+
)
|
|
82
|
+
| Error: {{ file.upload.error }}
|
|
83
|
+
template(
|
|
84
|
+
v-else-if="file.upload.active"
|
|
85
|
+
)
|
|
86
|
+
| Uploading...
|
|
87
|
+
template(
|
|
88
|
+
v-else-if="file.upload.success"
|
|
89
|
+
)
|
|
90
|
+
| Uploaded
|
|
91
|
+
template(
|
|
92
|
+
v-else
|
|
61
93
|
)
|
|
62
|
-
|
|
94
|
+
| Stored
|
|
95
|
+
td.dito-cell-edit-buttons
|
|
96
|
+
.dito-buttons.dito-buttons-round
|
|
97
|
+
button.dito-button.dito-button-upload(
|
|
98
|
+
v-if="!multiple"
|
|
99
|
+
:title="uploadTitle"
|
|
100
|
+
@click="onClickUpload"
|
|
101
|
+
)
|
|
102
|
+
//- Firefox doesn't like <button> here, so use <a> instead:
|
|
103
|
+
a.dito-button(
|
|
104
|
+
v-if="draggable"
|
|
105
|
+
v-bind="getButtonAttributes(verbs.drag)"
|
|
106
|
+
)
|
|
107
|
+
button.dito-button(
|
|
108
|
+
v-if="deletable"
|
|
109
|
+
type="button"
|
|
110
|
+
v-bind="getButtonAttributes(verbs.delete)"
|
|
111
|
+
@click="deleteFile(file, index)"
|
|
112
|
+
)
|
|
113
|
+
tfoot(
|
|
114
|
+
v-if="multiple || isUploadActive || !hasFiles"
|
|
115
|
+
)
|
|
63
116
|
tr
|
|
64
117
|
td(:colspan="4")
|
|
65
118
|
.dito-upload-footer
|
|
@@ -74,24 +127,11 @@
|
|
|
74
127
|
type="button"
|
|
75
128
|
@click.prevent="upload.active = false"
|
|
76
129
|
) Cancel
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
:name="dataPath"
|
|
82
|
-
:disabled="disabled"
|
|
83
|
-
:postAction="uploadPath"
|
|
84
|
-
:extensions="extensions"
|
|
85
|
-
:accept="accept"
|
|
86
|
-
:multiple="multiple"
|
|
87
|
-
:size="maxSize"
|
|
88
|
-
:title="multiple ? 'Upload Files' : 'Upload File'"
|
|
89
|
-
:drop="$el?.closest('.dito-container')"
|
|
90
|
-
:dropDirectory="true"
|
|
91
|
-
@input-filter="inputFilter"
|
|
92
|
-
@input-file="inputFile"
|
|
130
|
+
button.dito-button.dito-button-upload(
|
|
131
|
+
v-if="multiple || !hasFiles"
|
|
132
|
+
:title="uploadTitle"
|
|
133
|
+
@click="onClickUpload"
|
|
93
134
|
)
|
|
94
|
-
.dito-button-upload
|
|
95
135
|
</template>
|
|
96
136
|
|
|
97
137
|
<script>
|
|
@@ -102,7 +142,7 @@ import parseFileSize from 'filesize-parser'
|
|
|
102
142
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
103
143
|
import { formatFileSize } from '../utils/units.js'
|
|
104
144
|
import { appendDataPath } from '../utils/data.js'
|
|
105
|
-
import { isArray, asArray
|
|
145
|
+
import { isArray, asArray } from '@ditojs/utils'
|
|
106
146
|
import VueUpload from 'vue-upload-component'
|
|
107
147
|
|
|
108
148
|
// @vue/component
|
|
@@ -123,10 +163,22 @@ export default DitoTypeComponent.register('upload', {
|
|
|
123
163
|
return this.$refs.upload
|
|
124
164
|
},
|
|
125
165
|
|
|
166
|
+
uploadTitle() {
|
|
167
|
+
return this.multiple ? 'Upload Files' : 'Upload File'
|
|
168
|
+
},
|
|
169
|
+
|
|
126
170
|
files() {
|
|
127
171
|
return asFiles(this.value)
|
|
128
172
|
},
|
|
129
173
|
|
|
174
|
+
downloadUrls() {
|
|
175
|
+
return this.files.map((file, index) => this.getDownloadUrl(file, index))
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
thumbnailUrls() {
|
|
179
|
+
return this.files.map((file, index) => this.getThumbnailUrl(file, index))
|
|
180
|
+
},
|
|
181
|
+
|
|
130
182
|
multiple: getSchemaAccessor('multiple', {
|
|
131
183
|
type: Boolean,
|
|
132
184
|
default: false,
|
|
@@ -165,20 +217,43 @@ export default DitoTypeComponent.register('upload', {
|
|
|
165
217
|
default: false
|
|
166
218
|
}),
|
|
167
219
|
|
|
220
|
+
render: getSchemaAccessor('render', {
|
|
221
|
+
type: Function,
|
|
222
|
+
default: null
|
|
223
|
+
}),
|
|
224
|
+
|
|
225
|
+
thumbnails: getSchemaAccessor('thumbnails', {
|
|
226
|
+
type: [Boolean, String],
|
|
227
|
+
default(thumbnails) {
|
|
228
|
+
return thumbnails ?? !!this.schema.thumbnailUrl
|
|
229
|
+
},
|
|
230
|
+
get(thumbnails) {
|
|
231
|
+
return thumbnails === true ? 'medium' : thumbnails || null
|
|
232
|
+
}
|
|
233
|
+
}),
|
|
234
|
+
|
|
235
|
+
hasFiles() {
|
|
236
|
+
return this.files.length > 0
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
hasUploads() {
|
|
240
|
+
return this.uploads.length > 0
|
|
241
|
+
},
|
|
242
|
+
|
|
168
243
|
isUploadReady() {
|
|
169
244
|
return (
|
|
170
|
-
this.
|
|
245
|
+
this.hasUploads &&
|
|
171
246
|
!(this.upload.active || this.upload.uploaded)
|
|
172
247
|
)
|
|
173
248
|
},
|
|
174
249
|
|
|
175
250
|
isUploadActive() {
|
|
176
|
-
return this.
|
|
251
|
+
return this.hasUploads && this.upload.active
|
|
177
252
|
},
|
|
178
253
|
|
|
179
254
|
uploadProgress() {
|
|
180
255
|
return (
|
|
181
|
-
this.uploads.reduce((total, file) => +file.progress
|
|
256
|
+
this.uploads.reduce((total, file) => total + +file.progress, 0) /
|
|
182
257
|
this.uploads.length
|
|
183
258
|
)
|
|
184
259
|
},
|
|
@@ -205,19 +280,45 @@ export default DitoTypeComponent.register('upload', {
|
|
|
205
280
|
methods: {
|
|
206
281
|
formatFileSize,
|
|
207
282
|
|
|
283
|
+
getFileContext(file, index) {
|
|
284
|
+
return this.multiple
|
|
285
|
+
? new DitoContext(this, {
|
|
286
|
+
value: file,
|
|
287
|
+
data: this.files,
|
|
288
|
+
index,
|
|
289
|
+
dataPath: appendDataPath(this.dataPath, index)
|
|
290
|
+
})
|
|
291
|
+
: this.context
|
|
292
|
+
},
|
|
293
|
+
|
|
208
294
|
renderFile(file, index) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
295
|
+
return this.render(this.getFileContext(file, index))
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
getDownloadUrl(file, index) {
|
|
299
|
+
return file.url
|
|
300
|
+
? file.url
|
|
301
|
+
: !file.upload || file.upload.success
|
|
302
|
+
? this.getSchemaValue('downloadUrl', {
|
|
303
|
+
type: 'String',
|
|
304
|
+
default: null,
|
|
305
|
+
context: this.getFileContext(file, index)
|
|
218
306
|
})
|
|
307
|
+
: null
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
getThumbnailUrl(file, index) {
|
|
311
|
+
return !file.upload || file.upload.success
|
|
312
|
+
? this.getSchemaValue('thumbnailUrl', {
|
|
313
|
+
type: 'String',
|
|
314
|
+
default: null,
|
|
315
|
+
context: this.getFileContext(file, index)
|
|
316
|
+
}) || (
|
|
317
|
+
file.type.startsWith('image/')
|
|
318
|
+
? file.url
|
|
319
|
+
: null
|
|
219
320
|
)
|
|
220
|
-
:
|
|
321
|
+
: null
|
|
221
322
|
},
|
|
222
323
|
|
|
223
324
|
deleteFile(file, index) {
|
|
@@ -283,7 +384,7 @@ export default DitoTypeComponent.register('upload', {
|
|
|
283
384
|
this.replaceFile(file, null)
|
|
284
385
|
},
|
|
285
386
|
|
|
286
|
-
|
|
387
|
+
onInputFile(newFile, oldFile) {
|
|
287
388
|
if (newFile && !oldFile) {
|
|
288
389
|
const { id, name, size } = newFile
|
|
289
390
|
this.addFile({ id, name, size, upload: newFile })
|
|
@@ -324,11 +425,31 @@ export default DitoTypeComponent.register('upload', {
|
|
|
324
425
|
}
|
|
325
426
|
},
|
|
326
427
|
|
|
327
|
-
|
|
428
|
+
onInputFilter(newFile /*, oldFile, prevent */) {
|
|
328
429
|
const xhr = newFile?.xhr
|
|
329
430
|
if (this.api.cors?.credentials && xhr && !xhr.withCredentials) {
|
|
330
431
|
xhr.withCredentials = true
|
|
331
432
|
}
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
async onClickDownload(file, index) {
|
|
436
|
+
try {
|
|
437
|
+
const response = await fetch(this.downloadUrls[index])
|
|
438
|
+
const blob = await response.blob()
|
|
439
|
+
this.download({
|
|
440
|
+
filename: file.name,
|
|
441
|
+
url: URL.createObjectURL(blob)
|
|
442
|
+
})
|
|
443
|
+
} catch (error) {
|
|
444
|
+
console.error(error)
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
onClickUpload(event) {
|
|
449
|
+
// Delegate the click event to the hidden file input.
|
|
450
|
+
this.upload.$el.querySelector('input').dispatchEvent(
|
|
451
|
+
new event.constructor(event.type, event)
|
|
452
|
+
)
|
|
332
453
|
}
|
|
333
454
|
},
|
|
334
455
|
|
|
@@ -351,18 +472,16 @@ function asFiles(value) {
|
|
|
351
472
|
|
|
352
473
|
.dito-upload {
|
|
353
474
|
.dito-table {
|
|
354
|
-
tr
|
|
475
|
+
tr,
|
|
476
|
+
.dito-cell-edit-buttons {
|
|
355
477
|
vertical-align: middle;
|
|
356
478
|
}
|
|
357
479
|
}
|
|
358
480
|
|
|
359
|
-
.dito-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
position: absolute;
|
|
364
|
-
cursor: pointer;
|
|
365
|
-
}
|
|
481
|
+
.dito-upload-input {
|
|
482
|
+
// See `onClickUpload()` method for details.
|
|
483
|
+
position: absolute;
|
|
484
|
+
pointer-events: none;
|
|
366
485
|
}
|
|
367
486
|
|
|
368
487
|
.dito-upload-footer {
|
package/src/utils/schema.js
CHANGED
|
@@ -1042,5 +1042,5 @@ export function getItemUid(sourceSchema, item) {
|
|
|
1042
1042
|
// either way, pass through `getUid()` so that the ids are associated with the
|
|
1043
1043
|
// item through a weak map, as the ids can be filtered out in `processData()`
|
|
1044
1044
|
// while the components that use the uids as key are still visible.
|
|
1045
|
-
return getUid(item, getItemId(sourceSchema, item))
|
|
1045
|
+
return getUid(item, item => getItemId(sourceSchema, item))
|
|
1046
1046
|
}
|
package/src/utils/uid.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { toRaw } from 'vue'
|
|
2
|
+
|
|
1
3
|
const uidMap = new WeakMap()
|
|
2
4
|
|
|
3
5
|
// Generated and remembers unique ids per passed object using a weak map.
|
|
4
6
|
let uid = 0
|
|
5
|
-
export function getUid(
|
|
6
|
-
|
|
7
|
+
export function getUid(item, getItemId = null) {
|
|
8
|
+
const raw = toRaw(item)
|
|
9
|
+
let id = uidMap.get(raw)
|
|
7
10
|
if (!id) {
|
|
8
|
-
id =
|
|
9
|
-
uidMap.set(
|
|
11
|
+
id = getItemId?.(item) || `@${++uid}`
|
|
12
|
+
uidMap.set(raw, id)
|
|
10
13
|
}
|
|
11
14
|
return id
|
|
12
15
|
}
|