@ditojs/admin 2.10.7 → 2.11.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.
- package/dist/dito-admin.es.js +318 -301
- package/dist/dito-admin.umd.js +5 -5
- package/dist/style.css +1 -1
- package/package.json +30 -30
- package/src/DitoAdmin.js +4 -2
- package/src/components/DitoForm.vue +5 -1
- package/src/components/DitoPane.vue +7 -4
- package/src/components/DitoRoot.vue +3 -1
- package/src/components/DitoSchema.vue +4 -2
- package/src/mixins/DataMixin.js +23 -23
- package/src/mixins/DitoMixin.js +16 -2
- package/src/mixins/OptionsMixin.js +1 -3
- package/src/mixins/ResourceMixin.js +11 -3
- package/src/styles/_button.scss +6 -2
- package/src/types/DitoTypeComputed.vue +1 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/master/packages/admin",
|
|
@@ -33,57 +33,57 @@
|
|
|
33
33
|
"not ie_mob > 0"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ditojs/ui": "^2.
|
|
37
|
-
"@ditojs/utils": "^2.
|
|
36
|
+
"@ditojs/ui": "^2.11.1",
|
|
37
|
+
"@ditojs/utils": "^2.11.1",
|
|
38
38
|
"@kyvg/vue3-notification": "^2.9.1",
|
|
39
39
|
"@lk77/vue3-color": "^3.0.6",
|
|
40
|
-
"@tiptap/core": "^2.0.
|
|
41
|
-
"@tiptap/extension-blockquote": "^2.0.
|
|
42
|
-
"@tiptap/extension-bold": "^2.0.
|
|
43
|
-
"@tiptap/extension-bullet-list": "^2.0.
|
|
44
|
-
"@tiptap/extension-code": "^2.0.
|
|
45
|
-
"@tiptap/extension-code-block": "^2.0.
|
|
46
|
-
"@tiptap/extension-document": "^2.0.
|
|
47
|
-
"@tiptap/extension-hard-break": "^2.0.
|
|
48
|
-
"@tiptap/extension-heading": "^2.0.
|
|
49
|
-
"@tiptap/extension-history": "^2.0.
|
|
50
|
-
"@tiptap/extension-horizontal-rule": "^2.0.
|
|
51
|
-
"@tiptap/extension-italic": "^2.0.
|
|
52
|
-
"@tiptap/extension-link": "^2.0.
|
|
53
|
-
"@tiptap/extension-list-item": "^2.0.
|
|
54
|
-
"@tiptap/extension-ordered-list": "^2.0.
|
|
55
|
-
"@tiptap/extension-paragraph": "^2.0.
|
|
56
|
-
"@tiptap/extension-strike": "^2.0.
|
|
57
|
-
"@tiptap/extension-text": "^2.0.
|
|
58
|
-
"@tiptap/extension-underline": "^2.0.
|
|
59
|
-
"@tiptap/pm": "^2.0.
|
|
60
|
-
"@tiptap/vue-3": "^2.0.
|
|
40
|
+
"@tiptap/core": "^2.0.4",
|
|
41
|
+
"@tiptap/extension-blockquote": "^2.0.4",
|
|
42
|
+
"@tiptap/extension-bold": "^2.0.4",
|
|
43
|
+
"@tiptap/extension-bullet-list": "^2.0.4",
|
|
44
|
+
"@tiptap/extension-code": "^2.0.4",
|
|
45
|
+
"@tiptap/extension-code-block": "^2.0.4",
|
|
46
|
+
"@tiptap/extension-document": "^2.0.4",
|
|
47
|
+
"@tiptap/extension-hard-break": "^2.0.4",
|
|
48
|
+
"@tiptap/extension-heading": "^2.0.4",
|
|
49
|
+
"@tiptap/extension-history": "^2.0.4",
|
|
50
|
+
"@tiptap/extension-horizontal-rule": "^2.0.4",
|
|
51
|
+
"@tiptap/extension-italic": "^2.0.4",
|
|
52
|
+
"@tiptap/extension-link": "^2.0.4",
|
|
53
|
+
"@tiptap/extension-list-item": "^2.0.4",
|
|
54
|
+
"@tiptap/extension-ordered-list": "^2.0.4",
|
|
55
|
+
"@tiptap/extension-paragraph": "^2.0.4",
|
|
56
|
+
"@tiptap/extension-strike": "^2.0.4",
|
|
57
|
+
"@tiptap/extension-text": "^2.0.4",
|
|
58
|
+
"@tiptap/extension-underline": "^2.0.4",
|
|
59
|
+
"@tiptap/pm": "^2.0.4",
|
|
60
|
+
"@tiptap/vue-3": "^2.0.4",
|
|
61
61
|
"@vueuse/integrations": "^10.2.1",
|
|
62
62
|
"codeflask": "^1.4.1",
|
|
63
|
-
"filesize": "^10.0.
|
|
63
|
+
"filesize": "^10.0.8",
|
|
64
64
|
"filesize-parser": "^1.5.0",
|
|
65
65
|
"focus-trap": "^7.5.2",
|
|
66
66
|
"nanoid": "^4.0.2",
|
|
67
67
|
"sortablejs": "^1.15.0",
|
|
68
68
|
"tinycolor2": "^1.6.0",
|
|
69
69
|
"tippy.js": "^6.3.7",
|
|
70
|
-
"type-fest": "^
|
|
70
|
+
"type-fest": "^4.0.0",
|
|
71
71
|
"vue": "^3.3.4",
|
|
72
72
|
"vue-multiselect": "^3.0.0-beta.2",
|
|
73
73
|
"vue-router": "^4.2.4",
|
|
74
74
|
"vue-upload-component": "^3.1.8"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@ditojs/build": "^2.
|
|
77
|
+
"@ditojs/build": "^2.11.0",
|
|
78
78
|
"@vitejs/plugin-vue": "^4.2.3",
|
|
79
79
|
"@vue/compiler-sfc": "^3.3.4",
|
|
80
80
|
"pug": "^3.0.2",
|
|
81
|
-
"sass": "1.
|
|
81
|
+
"sass": "1.64.1",
|
|
82
82
|
"typescript": "^5.1.6",
|
|
83
|
-
"vite": "^4.4.
|
|
83
|
+
"vite": "^4.4.7"
|
|
84
84
|
},
|
|
85
85
|
"types": "types",
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "fb1e7ce180f5c9ce8e058a7ef22465e0428cd758",
|
|
87
87
|
"scripts": {
|
|
88
88
|
"build": "vite build",
|
|
89
89
|
"watch": "yarn build --mode 'development' --watch",
|
package/src/DitoAdmin.js
CHANGED
|
@@ -251,7 +251,8 @@ async function request(api, {
|
|
|
251
251
|
params = null,
|
|
252
252
|
query = params || null,
|
|
253
253
|
headers = null,
|
|
254
|
-
data = null
|
|
254
|
+
data = null,
|
|
255
|
+
signal = null
|
|
255
256
|
}) {
|
|
256
257
|
if (params) {
|
|
257
258
|
deprecate(
|
|
@@ -271,7 +272,8 @@ async function request(api, {
|
|
|
271
272
|
credentials:
|
|
272
273
|
isApiUrl && api.cors?.credentials
|
|
273
274
|
? 'include'
|
|
274
|
-
: 'same-origin'
|
|
275
|
+
: 'same-origin',
|
|
276
|
+
signal
|
|
275
277
|
})
|
|
276
278
|
|
|
277
279
|
if (response.headers.get('Content-Type')?.includes('application/json')) {
|
|
@@ -476,7 +476,11 @@ export default DitoComponent.component('DitoForm', {
|
|
|
476
476
|
} else if (this.isCreating) {
|
|
477
477
|
// Redirect to the form editing the newly created item:
|
|
478
478
|
const id = this.getItemId(this.schema, this.data)
|
|
479
|
-
this.$router.replace({
|
|
479
|
+
this.$router.replace({
|
|
480
|
+
path: resolvePath(`${this.path}/../${id}`),
|
|
481
|
+
// Preserve hash for tabs:
|
|
482
|
+
hash: this.$route.hash
|
|
483
|
+
})
|
|
480
484
|
}
|
|
481
485
|
}
|
|
482
486
|
return success
|
|
@@ -240,6 +240,8 @@ export default DitoComponent.component('DitoPane', {
|
|
|
240
240
|
$self: &;
|
|
241
241
|
$root-padding: $content-padding - $form-spacing-half;
|
|
242
242
|
|
|
243
|
+
--pane-padding: 0px;
|
|
244
|
+
|
|
243
245
|
display: flex;
|
|
244
246
|
position: relative;
|
|
245
247
|
flex-flow: row wrap;
|
|
@@ -247,6 +249,7 @@ export default DitoComponent.component('DitoPane', {
|
|
|
247
249
|
align-content: flex-start;
|
|
248
250
|
// Remove the padding added by `.dito-container` inside `.dito-pane`:
|
|
249
251
|
margin: -$form-spacing-half;
|
|
252
|
+
padding: var(--pane-padding);
|
|
250
253
|
// Use `flex: 0%` for all `.dito-pane` except `.dito-pane__main`,
|
|
251
254
|
// so that the `.dito-buttons-main` can be moved all the way to the bottom.
|
|
252
255
|
flex: 0%;
|
|
@@ -260,10 +263,10 @@ export default DitoComponent.component('DitoPane', {
|
|
|
260
263
|
margin: 0;
|
|
261
264
|
// Move the negative margin used to remove the padding added by
|
|
262
265
|
// `.dito-container` inside `.dito-pane` to the padding:
|
|
263
|
-
padding: $root-padding;
|
|
266
|
+
--pane-padding: #{$root-padding};
|
|
264
267
|
|
|
265
268
|
&#{$self}--single {
|
|
266
|
-
padding: $content-padding;
|
|
269
|
+
--pane-padding: #{$content-padding};
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
&:has(> .dito-container--label-vertical:first-of-type) {
|
|
@@ -293,11 +296,11 @@ export default DitoComponent.component('DitoPane', {
|
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
&--padding-inlined {
|
|
296
|
-
padding:
|
|
299
|
+
--pane-padding: 0px;
|
|
297
300
|
}
|
|
298
301
|
|
|
299
302
|
&--padding-nested {
|
|
300
|
-
padding: $form-spacing;
|
|
303
|
+
--pane-padding: #{$form-spacing};
|
|
301
304
|
|
|
302
305
|
&:has(> .dito-container--label-vertical:first-of-type) {
|
|
303
306
|
// Reduce top spacing when the first row has labels.
|
|
@@ -502,7 +502,9 @@ function addRoutes(router, routes) {
|
|
|
502
502
|
.dito-drag-leave-active {
|
|
503
503
|
$duration: 0.15s;
|
|
504
504
|
|
|
505
|
-
transition:
|
|
505
|
+
transition:
|
|
506
|
+
opacity $duration,
|
|
507
|
+
backdrop-filter $duration;
|
|
506
508
|
|
|
507
509
|
~ * .dito-drop-target {
|
|
508
510
|
transition: filter $duration;
|
|
@@ -57,11 +57,13 @@ slot(name="before")
|
|
|
57
57
|
)
|
|
58
58
|
template(
|
|
59
59
|
v-for="(tabSchema, tab) in tabs"
|
|
60
|
+
:key="tab"
|
|
60
61
|
)
|
|
62
|
+
//- TODO: Switch to v-if instead of v-show, once validation is
|
|
63
|
+
//- decoupled from components.
|
|
61
64
|
DitoPane.dito-pane__tab(
|
|
62
|
-
v-
|
|
65
|
+
v-show="selectedTab === tab"
|
|
63
66
|
ref="tabs"
|
|
64
|
-
:key="tab"
|
|
65
67
|
:tab="tab"
|
|
66
68
|
:schema="tabSchema"
|
|
67
69
|
:dataPath="dataPath"
|
package/src/mixins/DataMixin.js
CHANGED
|
@@ -20,10 +20,7 @@ export default {
|
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
methods: {
|
|
23
|
-
handleDataSchema(schema, name, {
|
|
24
|
-
resolveCounter = 1,
|
|
25
|
-
...loadingOptions
|
|
26
|
-
} = {}) {
|
|
23
|
+
handleDataSchema(schema, name, loadingOptions) {
|
|
27
24
|
if (!isObject(schema)) {
|
|
28
25
|
schema = { data: schema }
|
|
29
26
|
}
|
|
@@ -31,27 +28,24 @@ export default {
|
|
|
31
28
|
// See if there is async data loading already in process.
|
|
32
29
|
const asyncEntry = (this.asyncDataEntries[name] ||= reactive({
|
|
33
30
|
dependencyFunction: null,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
resolvedData: undefined,
|
|
32
|
+
resolving: false,
|
|
33
|
+
resolved: false
|
|
37
34
|
}))
|
|
38
35
|
// If the data callback provided a dependency function when it was called,
|
|
39
36
|
// cal it in every call of `handleDataSchema()` to force Vue to keep track
|
|
40
37
|
// of the async dependencies.
|
|
41
38
|
asyncEntry.dependencyFunction?.(this.context)
|
|
42
39
|
|
|
43
|
-
if (asyncEntry.
|
|
44
|
-
// If the data was resolved already, return it and clear the
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// reevaluates if one of the dependencies changed. This is to ensure
|
|
50
|
-
// that a cached value here doesn't block / override reevaluation:
|
|
40
|
+
if (asyncEntry.resolved) {
|
|
41
|
+
// If the data was resolved already, return it and clear the resolved
|
|
42
|
+
// value. This works because Vue caches the result of computed getters
|
|
43
|
+
// and only reevaluates if one of the dependencies changed. This is to
|
|
44
|
+
// ensure that a cached value here doesn't block / override
|
|
45
|
+
// reevaluation when a dependency changes:
|
|
51
46
|
const { resolvedData } = asyncEntry
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
47
|
+
asyncEntry.resolvedData = undefined
|
|
48
|
+
asyncEntry.resolved = false
|
|
55
49
|
return resolvedData
|
|
56
50
|
}
|
|
57
51
|
// Avoid calling the data function twice:
|
|
@@ -73,6 +67,7 @@ export default {
|
|
|
73
67
|
data = result
|
|
74
68
|
}
|
|
75
69
|
}
|
|
70
|
+
// NOTE: If the data is not a promise, it is resolved already.
|
|
76
71
|
if (isPromise(data)) {
|
|
77
72
|
// If the data is asynchronous, it can't be returned straight away.
|
|
78
73
|
// But we can cheat using computed properties and `resolvedData`,
|
|
@@ -80,11 +75,16 @@ export default {
|
|
|
80
75
|
// triggering a recompute of the computed property that calls
|
|
81
76
|
// `handleDataSchema()`.
|
|
82
77
|
asyncEntry.resolving = true
|
|
83
|
-
this.resolveData(data, loadingOptions)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
this.resolveData(data, loadingOptions)
|
|
79
|
+
.then(data => {
|
|
80
|
+
asyncEntry.resolvedData = data
|
|
81
|
+
asyncEntry.resolving = false
|
|
82
|
+
asyncEntry.resolved = true
|
|
83
|
+
})
|
|
84
|
+
.catch(error => {
|
|
85
|
+
console.error(error)
|
|
86
|
+
asyncEntry.resolving = false
|
|
87
|
+
})
|
|
88
88
|
// Clear data until promise is resolved and `resolvedData` is set
|
|
89
89
|
data = null
|
|
90
90
|
}
|
package/src/mixins/DitoMixin.js
CHANGED
|
@@ -321,14 +321,28 @@ export default {
|
|
|
321
321
|
return url ? this.api.getApiUrl({ url, query: resource.query }) : null
|
|
322
322
|
},
|
|
323
323
|
|
|
324
|
-
async sendRequest({
|
|
324
|
+
async sendRequest({
|
|
325
|
+
method,
|
|
326
|
+
url,
|
|
327
|
+
resource,
|
|
328
|
+
query,
|
|
329
|
+
data,
|
|
330
|
+
signal,
|
|
331
|
+
internal
|
|
332
|
+
}) {
|
|
325
333
|
url ||= this.getResourceUrl(resource)
|
|
326
334
|
method ||= resource?.method
|
|
327
335
|
const checkUser = !internal && this.api.isApiUrl(url)
|
|
328
336
|
if (checkUser) {
|
|
329
337
|
await this.rootComponent.ensureUser()
|
|
330
338
|
}
|
|
331
|
-
const response = await this.api.request({
|
|
339
|
+
const response = await this.api.request({
|
|
340
|
+
method,
|
|
341
|
+
url,
|
|
342
|
+
query,
|
|
343
|
+
data,
|
|
344
|
+
signal
|
|
345
|
+
})
|
|
332
346
|
// Detect change of the own user, and fetch it again if it was changed.
|
|
333
347
|
if (
|
|
334
348
|
checkUser &&
|
|
@@ -69,9 +69,7 @@ export default {
|
|
|
69
69
|
},
|
|
70
70
|
|
|
71
71
|
options() {
|
|
72
|
-
const data = this.handleDataSchema(this.schema.options, 'options'
|
|
73
|
-
resolveCounter: 1
|
|
74
|
-
}) ?? []
|
|
72
|
+
const data = this.handleDataSchema(this.schema.options, 'options') ?? []
|
|
75
73
|
if (!isArray(data)) {
|
|
76
74
|
throw new Error(`Invalid options data, should be array: ${data}`)
|
|
77
75
|
}
|
|
@@ -20,7 +20,8 @@ export default {
|
|
|
20
20
|
|
|
21
21
|
data() {
|
|
22
22
|
return {
|
|
23
|
-
loadedData: null
|
|
23
|
+
loadedData: null,
|
|
24
|
+
abortController: null
|
|
24
25
|
}
|
|
25
26
|
},
|
|
26
27
|
|
|
@@ -239,8 +240,12 @@ export default {
|
|
|
239
240
|
updateRoot: true, // Display spinner in header when loading in resources
|
|
240
241
|
updateView: this.isInView // Notify view of loading for view components
|
|
241
242
|
}
|
|
243
|
+
this.abortController?.abort()
|
|
244
|
+
const controller = new AbortController()
|
|
245
|
+
this.abortController = controller
|
|
246
|
+
const { signal } = controller
|
|
247
|
+
const request = { method, resource, query, data, signal }
|
|
242
248
|
this.setLoading(true, loadingOptions)
|
|
243
|
-
const request = { method, resource, data, query }
|
|
244
249
|
try {
|
|
245
250
|
const response = await this.sendRequest(request)
|
|
246
251
|
// Pass both request and response to the callback, so they can be
|
|
@@ -258,7 +263,10 @@ export default {
|
|
|
258
263
|
this.notify({ type: 'error', title, text })
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
|
-
this.
|
|
266
|
+
if (this.abortController === controller) {
|
|
267
|
+
this.abortController = null
|
|
268
|
+
this.setLoading(false, loadingOptions)
|
|
269
|
+
}
|
|
262
270
|
},
|
|
263
271
|
|
|
264
272
|
getPayloadData(button, method) {
|
package/src/styles/_button.scss
CHANGED
|
@@ -142,11 +142,15 @@
|
|
|
142
142
|
position: sticky;
|
|
143
143
|
bottom: 0;
|
|
144
144
|
z-index: $z-index-main-buttons;
|
|
145
|
-
|
|
145
|
+
background: $content-color-background;
|
|
146
146
|
margin-top: 2 * $content-padding;
|
|
147
|
+
margin-bottom: calc(-1 * var(--pane-padding, 0px));
|
|
147
148
|
box-shadow: 0 (-$content-padding) $content-padding (-$content-padding)
|
|
148
149
|
$color-shadow;
|
|
149
|
-
|
|
150
|
+
|
|
151
|
+
&:empty {
|
|
152
|
+
display: none;
|
|
153
|
+
}
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
&.dito-buttons-round,
|
|
@@ -31,12 +31,7 @@ export default DitoTypeComponent.register(
|
|
|
31
31
|
get() {
|
|
32
32
|
const { schema } = this
|
|
33
33
|
if (schema.data || schema.dataPath) {
|
|
34
|
-
const value = this.handleDataSchema(schema, 'schema'
|
|
35
|
-
// Modifying `this.data` below triggers another call of the
|
|
36
|
-
// `value` getter, so use a value of 2 for `resolveCounter` to
|
|
37
|
-
// return the resolved data twice.
|
|
38
|
-
resolveCounter: 2
|
|
39
|
-
})
|
|
34
|
+
const value = this.handleDataSchema(schema, 'schema')
|
|
40
35
|
// TODO: Fix side-effects
|
|
41
36
|
// eslint-disable-next-line
|
|
42
37
|
this.data[this.name] = value
|