@ditojs/admin 2.100.0 → 3.1.0
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.css +1 -1
- package/dist/dito-admin.es.js +3555 -2719
- package/dist/dito-admin.umd.js +5 -6
- package/package.json +84 -46
- package/src/DitoAdmin.js +1 -65
- package/src/DitoComponent.js +1 -1
- package/src/DitoContext.js +51 -0
- package/src/DitoTypeComponent.js +23 -1
- package/src/components/DitoAffixes.vue +6 -3
- package/src/components/DitoButtons.vue +14 -1
- package/src/components/DitoContainer.vue +19 -10
- package/src/components/DitoCreateButton.vue +11 -2
- package/src/components/DitoDialog.vue +21 -2
- package/src/components/DitoForm.vue +152 -84
- package/src/components/DitoLabel.vue +3 -2
- package/src/components/DitoPane.vue +55 -11
- package/src/components/DitoPanel.vue +34 -5
- package/src/components/DitoPanels.vue +2 -1
- package/src/components/DitoRoot.vue +6 -6
- package/src/components/DitoRouterView.vue +27 -0
- package/src/components/DitoSchema.vue +278 -145
- package/src/components/DitoSchemaInlined.vue +1 -1
- package/src/components/DitoTabs.vue +16 -18
- package/src/components/DitoTrail.vue +5 -4
- package/src/components/DitoTreeItem.vue +17 -6
- package/src/components/DitoView.vue +21 -3
- package/src/components/index.js +1 -0
- package/src/mixins/DitoMixin.js +53 -7
- package/src/mixins/ItemMixin.js +3 -6
- package/src/mixins/NumberMixin.js +47 -31
- package/src/mixins/OptionsMixin.js +132 -71
- package/src/mixins/ResourceMixin.js +5 -2
- package/src/mixins/RouteMixin.js +39 -1
- package/src/mixins/SortableMixin.js +0 -13
- package/src/mixins/SourceMixin.js +155 -96
- package/src/mixins/TextMixin.js +1 -1
- package/src/mixins/TypeMixin.js +37 -22
- package/src/mixins/ValidationMixin.js +9 -27
- package/src/mixins/ValidatorMixin.js +5 -7
- package/src/mixins/ValueMixin.js +5 -2
- package/src/types/DitoTypeButton.vue +1 -1
- package/src/types/DitoTypeCheckbox.vue +1 -1
- package/src/types/DitoTypeCheckboxes.vue +1 -1
- package/src/types/DitoTypeCode.vue +8 -1
- package/src/types/DitoTypeColor.vue +20 -34
- package/src/types/DitoTypeComponent.vue +1 -1
- package/src/types/DitoTypeComputed.vue +4 -27
- package/src/types/DitoTypeDate.vue +16 -5
- package/src/types/DitoTypeLabel.vue +1 -4
- package/src/types/DitoTypeList.vue +9 -5
- package/src/types/DitoTypeMarkup.vue +25 -13
- package/src/types/DitoTypeMultiselect.vue +37 -6
- package/src/types/DitoTypeNumber.vue +1 -1
- package/src/types/DitoTypeObject.vue +10 -7
- package/src/types/DitoTypePanel.vue +20 -6
- package/src/types/DitoTypeProgress.vue +1 -1
- package/src/types/DitoTypeRadio.vue +1 -1
- package/src/types/DitoTypeSection.vue +32 -4
- package/src/types/DitoTypeSelect.vue +1 -1
- package/src/types/DitoTypeSlider.vue +1 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +13 -15
- package/src/types/DitoTypeTextarea.vue +1 -1
- package/src/types/DitoTypeTreeList.vue +5 -7
- package/src/types/DitoTypeUpload.vue +1 -2
- package/src/utils/DataSchemaResolver.js +154 -0
- package/src/utils/FormModel.js +615 -0
- package/src/utils/data.js +20 -0
- package/src/utils/filter.js +3 -2
- package/src/utils/options.js +3 -0
- package/src/utils/request.js +70 -0
- package/src/utils/schema/data.js +659 -0
- package/src/utils/schema/lookup.js +250 -0
- package/src/utils/schema/setup.js +354 -0
- package/src/utils/schema/structure.js +172 -0
- package/src/utils/schema/types.js +34 -0
- package/src/utils/schema/validation.js +142 -0
- package/src/utils/units.js +70 -3
- package/types/index.d.ts +174 -59
- package/src/mixins/DataMixin.js +0 -131
- package/src/utils/schema.js +0 -1121
package/src/mixins/ValueMixin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isFunction } from '@ditojs/utils'
|
|
1
2
|
import DitoContext from '../DitoContext.js'
|
|
2
|
-
import { computeValue } from '../utils/schema.js'
|
|
3
|
+
import { computeValue } from '../utils/schema/data.js'
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
computed: {
|
|
@@ -12,8 +13,10 @@ export default {
|
|
|
12
13
|
this.dataPath,
|
|
13
14
|
{ component: this }
|
|
14
15
|
)
|
|
16
|
+
// Only call `format()` if it's a function, as some types use `format`
|
|
17
|
+
// for other purposes, e.g. `DitoTypeColor` for the color format.
|
|
15
18
|
const { format } = this.schema
|
|
16
|
-
return format
|
|
19
|
+
return isFunction(format)
|
|
17
20
|
? format(new DitoContext(this, { value }))
|
|
18
21
|
: value
|
|
19
22
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-code(
|
|
3
|
-
:id="
|
|
3
|
+
:id="componentPath"
|
|
4
4
|
:style="style"
|
|
5
5
|
)
|
|
6
6
|
.dito-code__editor(ref="editor")
|
|
@@ -120,6 +120,13 @@ export default DitoTypeComponent.register('code', {
|
|
|
120
120
|
flask.updateLanguage(language)
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
+
this.$watch(
|
|
124
|
+
() => this.readonly || this.disabled,
|
|
125
|
+
readonly =>
|
|
126
|
+
readonly ? flask.enableReadonlyMode() : flask.disableReadonlyMode(),
|
|
127
|
+
{ immediate: true }
|
|
128
|
+
)
|
|
129
|
+
|
|
123
130
|
setCode(this.value || '')
|
|
124
131
|
},
|
|
125
132
|
|
|
@@ -5,7 +5,7 @@ DitoTrigger.dito-color(
|
|
|
5
5
|
)
|
|
6
6
|
template(#trigger)
|
|
7
7
|
DitoInput(
|
|
8
|
-
:id="
|
|
8
|
+
:id="componentPath"
|
|
9
9
|
ref="element"
|
|
10
10
|
v-model="hexValue"
|
|
11
11
|
type="input"
|
|
@@ -39,7 +39,7 @@ DitoTrigger.dito-color(
|
|
|
39
39
|
div(:style="{ background: `#${hexValue || '00000000'}` }")
|
|
40
40
|
template(#popup)
|
|
41
41
|
SketchPicker.dito-color__picker(
|
|
42
|
-
v-model="colorValue"
|
|
42
|
+
v-model:tinyColor="colorValue"
|
|
43
43
|
:disableAlpha="!alpha"
|
|
44
44
|
:disableFields="!inputs"
|
|
45
45
|
:presetColors="presets"
|
|
@@ -47,21 +47,14 @@ DitoTrigger.dito-color(
|
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
49
|
<script>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
import {
|
|
50
|
+
// Use the `tinycolor` exported by `vue-color`, as required for its
|
|
51
|
+
// `v-model:tinyColor` binding.
|
|
52
|
+
import { SketchPicker, tinycolor } from 'vue-color'
|
|
53
53
|
import { DitoTrigger, DitoInput } from '@ditojs/ui/src'
|
|
54
54
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
55
55
|
import DitoAffixes from '../components/DitoAffixes.vue'
|
|
56
56
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
57
57
|
|
|
58
|
-
// Monkey-patch the `SketchPicker's` `hex` computed property to return lowercase
|
|
59
|
-
// hex values instead of uppercase ones.
|
|
60
|
-
const { hex } = SketchPicker.computed
|
|
61
|
-
SketchPicker.computed.hex = function () {
|
|
62
|
-
return hex.call(this).toLowerCase()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
58
|
// @vue/component
|
|
66
59
|
export default DitoTypeComponent.register('color', {
|
|
67
60
|
components: { DitoTrigger, DitoInput, DitoAffixes, SketchPicker },
|
|
@@ -80,15 +73,21 @@ export default DitoTypeComponent.register('color', {
|
|
|
80
73
|
|
|
81
74
|
colorValue: {
|
|
82
75
|
get() {
|
|
83
|
-
return (
|
|
84
|
-
this.convertedValue ||
|
|
85
|
-
this.value ||
|
|
86
|
-
(this.colorFormat === 'hex' ? '' : {})
|
|
87
|
-
)
|
|
76
|
+
return tinycolor(this.convertedValue || this.value || '#000000')
|
|
88
77
|
},
|
|
89
78
|
|
|
90
|
-
set(
|
|
91
|
-
|
|
79
|
+
set(color) {
|
|
80
|
+
// Picker changes take precedence over values typed into the input.
|
|
81
|
+
this.convertedValue = null
|
|
82
|
+
// Skip unchanged colors, as converting formats like `hsl` back and
|
|
83
|
+
// forth isn't lossless and would cause endless updates.
|
|
84
|
+
const { value } = this
|
|
85
|
+
if (
|
|
86
|
+
!value ||
|
|
87
|
+
color.toHex8String() !== tinycolor(value).toHex8String()
|
|
88
|
+
) {
|
|
89
|
+
this.value = toTinyColorFormat(color, this.colorFormat)
|
|
90
|
+
}
|
|
92
91
|
}
|
|
93
92
|
},
|
|
94
93
|
|
|
@@ -174,21 +173,7 @@ export default DitoTypeComponent.register('color', {
|
|
|
174
173
|
})
|
|
175
174
|
|
|
176
175
|
function convertColor(color, format) {
|
|
177
|
-
return
|
|
178
|
-
? toVue3ColorFormat(color, format)
|
|
179
|
-
: toTinyColorFormat(tinycolor(color), format)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function toVue3ColorFormat(color, format) {
|
|
183
|
-
const value =
|
|
184
|
-
color[
|
|
185
|
-
{
|
|
186
|
-
hex: color?.a < 1 ? 'hex8' : 'hex',
|
|
187
|
-
rgb: 'rgba'
|
|
188
|
-
}[format] ||
|
|
189
|
-
format
|
|
190
|
-
]
|
|
191
|
-
return isString(value) && value[0] === '#' ? value.toLowerCase() : value
|
|
176
|
+
return toTinyColorFormat(tinycolor(color), format)
|
|
192
177
|
}
|
|
193
178
|
|
|
194
179
|
// This should really be in tinycolor, but it only has the string equivalent
|
|
@@ -224,6 +209,7 @@ function toTinyColorFormat(color, format) {
|
|
|
224
209
|
|
|
225
210
|
<style lang="scss">
|
|
226
211
|
@import '../styles/_imports';
|
|
212
|
+
@import 'vue-color/style.css';
|
|
227
213
|
|
|
228
214
|
$color-swatch-width: $pattern-transparency-size;
|
|
229
215
|
$color-swatch-radius: $border-radius - $border-width;
|
|
@@ -15,7 +15,7 @@ component(
|
|
|
15
15
|
// }
|
|
16
16
|
|
|
17
17
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
18
|
-
import { resolveSchemaComponent } from '../utils/schema.js'
|
|
18
|
+
import { resolveSchemaComponent } from '../utils/schema/setup.js'
|
|
19
19
|
|
|
20
20
|
// @vue/component
|
|
21
21
|
export default DitoTypeComponent.register('component', {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
//- TODO: Find a better way to trigger evaluation of `value` that dose not
|
|
3
|
-
//- involve actually rendering it when the component is not visible.
|
|
4
2
|
input.dito-text.dito-input(
|
|
5
|
-
:id="
|
|
3
|
+
:id="componentPath"
|
|
6
4
|
ref="element"
|
|
7
5
|
:name="name"
|
|
8
6
|
type="text"
|
|
@@ -14,37 +12,16 @@ input.dito-text.dito-input(
|
|
|
14
12
|
|
|
15
13
|
<script>
|
|
16
14
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
17
|
-
import ValueMixin from '../mixins/ValueMixin.js'
|
|
18
|
-
import DataMixin from '../mixins/DataMixin.js'
|
|
19
15
|
|
|
20
16
|
export default DitoTypeComponent.register(
|
|
21
17
|
['computed', 'data', 'hidden'],
|
|
22
18
|
// @vue/component
|
|
23
19
|
{
|
|
24
|
-
mixins: [DataMixin],
|
|
25
|
-
|
|
26
20
|
defaultValue: () => undefined, // Callback to override `defaultValue: null`
|
|
27
21
|
defaultVisible: false,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
get() {
|
|
32
|
-
const { schema } = this
|
|
33
|
-
if (schema.data || schema.dataPath) {
|
|
34
|
-
const value = this.handleDataSchema(schema, 'value')
|
|
35
|
-
// TODO: Fix side-effects
|
|
36
|
-
// eslint-disable-next-line max-len
|
|
37
|
-
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
|
38
|
-
this.data[this.name] = value
|
|
39
|
-
}
|
|
40
|
-
return ValueMixin.computed.value.get.call(this)
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
set(value) {
|
|
44
|
-
ValueMixin.computed.value.set.call(this, value)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
22
|
+
// The value is resolved from `schema.data` or `schema.dataPath` by
|
|
23
|
+
// `FormModel`, see `hasValueFromDataSchema()`:
|
|
24
|
+
valueFromDataSchema: true
|
|
48
25
|
}
|
|
49
26
|
)
|
|
50
27
|
</script>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
.dito-date
|
|
3
3
|
component(
|
|
4
4
|
:is="getComponent(type)"
|
|
5
|
-
:id="
|
|
5
|
+
:id="componentPath"
|
|
6
6
|
ref="element"
|
|
7
7
|
v-model="dateValue"
|
|
8
8
|
:locale="locale"
|
|
@@ -41,7 +41,12 @@ import {
|
|
|
41
41
|
DitoTimePicker,
|
|
42
42
|
DitoDateTimePicker
|
|
43
43
|
} from '@ditojs/ui/src'
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
isDate,
|
|
46
|
+
assignDeeply,
|
|
47
|
+
formatPlainDate,
|
|
48
|
+
parsePlainDate
|
|
49
|
+
} from '@ditojs/utils'
|
|
45
50
|
|
|
46
51
|
export default DitoTypeComponent.register(
|
|
47
52
|
['date', 'datetime', 'time'],
|
|
@@ -58,7 +63,7 @@ export default DitoTypeComponent.register(
|
|
|
58
63
|
dateValue: {
|
|
59
64
|
get() {
|
|
60
65
|
const { value } = this
|
|
61
|
-
return value ? new Date(value) : value
|
|
66
|
+
return value ? parsePlainDate(value) ?? new Date(value) : value
|
|
62
67
|
},
|
|
63
68
|
|
|
64
69
|
set(value) {
|
|
@@ -91,8 +96,14 @@ export default DitoTypeComponent.register(
|
|
|
91
96
|
}
|
|
92
97
|
},
|
|
93
98
|
|
|
94
|
-
processValue({ value }) {
|
|
95
|
-
|
|
99
|
+
processValue({ schema, value }) {
|
|
100
|
+
// Send dates as their plain date, so they can't shift to another day
|
|
101
|
+
// through time zones, and all other date types as ISO strings.
|
|
102
|
+
return isDate(value)
|
|
103
|
+
? schema.type === 'date'
|
|
104
|
+
? formatPlainDate(value)
|
|
105
|
+
: value.toISOString()
|
|
106
|
+
: value
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-list(
|
|
3
3
|
v-if="isReady"
|
|
4
|
-
:id="
|
|
4
|
+
:id="componentPath"
|
|
5
5
|
role="region"
|
|
6
6
|
:aria-label="label || labelize(schema.name)"
|
|
7
7
|
)
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
as="tbody"
|
|
43
43
|
:options="getDraggableOptions()"
|
|
44
44
|
:draggable="draggable"
|
|
45
|
-
:modelValue="
|
|
45
|
+
:modelValue="listData"
|
|
46
46
|
@update:modelValue="value => (listData = value)"
|
|
47
47
|
)
|
|
48
48
|
tr(
|
|
49
49
|
v-for="(item, index) in listData"
|
|
50
|
-
:id="
|
|
50
|
+
:id="getComponentPath(index)"
|
|
51
51
|
:key="getItemUid(schema, item)"
|
|
52
52
|
)
|
|
53
53
|
template(
|
|
@@ -163,10 +163,10 @@ import DitoContext from '../DitoContext.js'
|
|
|
163
163
|
import SourceMixin from '../mixins/SourceMixin.js'
|
|
164
164
|
import SortableMixin from '../mixins/SortableMixin.js'
|
|
165
165
|
import {
|
|
166
|
-
getViewEditPath,
|
|
167
166
|
resolveSchemaComponent,
|
|
168
167
|
resolveSchemaComponents
|
|
169
|
-
} from '../utils/schema.js'
|
|
168
|
+
} from '../utils/schema/setup.js'
|
|
169
|
+
import { getViewEditPath } from '../utils/schema/lookup.js'
|
|
170
170
|
import { createFiltersPanel } from '../utils/filter.js'
|
|
171
171
|
import { appendDataPath } from '../utils/data.js'
|
|
172
172
|
import { pickBy, equals, hyphenate } from '@ditojs/utils'
|
|
@@ -250,6 +250,10 @@ export default DitoTypeComponent.register('list', {
|
|
|
250
250
|
return appendDataPath(this.dataPath, index)
|
|
251
251
|
},
|
|
252
252
|
|
|
253
|
+
getComponentPath(index) {
|
|
254
|
+
return appendDataPath(this.componentPath, index)
|
|
255
|
+
},
|
|
256
|
+
|
|
253
257
|
getEditPath(item, index) {
|
|
254
258
|
if (this.editable) {
|
|
255
259
|
const id = this.getItemId(this.schema, item, index)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
|
-
.dito-markup(:id="
|
|
2
|
+
.dito-markup(:id="componentPath")
|
|
3
3
|
.dito-buttons.dito-buttons--toolbar(
|
|
4
4
|
v-if="groupedButtons.length > 0"
|
|
5
5
|
)
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
v-for="buttons in groupedButtons"
|
|
8
8
|
)
|
|
9
9
|
button.dito-button(
|
|
10
|
-
v-for="{
|
|
11
|
-
:key="
|
|
10
|
+
v-for="{ id, label, icon, isActive, onClick } in buttons"
|
|
11
|
+
:key="id"
|
|
12
12
|
:class="{ 'dito-button--active': isActive }"
|
|
13
|
+
:aria-label="label"
|
|
14
|
+
:aria-pressed="isActive"
|
|
13
15
|
@click="onClick"
|
|
14
16
|
)
|
|
15
17
|
DitoIcon(:name="icon")
|
|
@@ -169,7 +171,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
169
171
|
|
|
170
172
|
editorOptions() {
|
|
171
173
|
return {
|
|
172
|
-
editable: !this.
|
|
174
|
+
editable: !this.readonly,
|
|
173
175
|
autoFocus: this.autofocus,
|
|
174
176
|
disableInputRules: !this.enableRules.input,
|
|
175
177
|
disablePasteRules: !this.enableRules.paste,
|
|
@@ -237,7 +239,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
237
239
|
},
|
|
238
240
|
|
|
239
241
|
watch: {
|
|
240
|
-
|
|
242
|
+
readonly: 'updateEditorOptions',
|
|
241
243
|
autofocus: 'updateEditorOptions',
|
|
242
244
|
enableRules: 'updateEditorOptions'
|
|
243
245
|
},
|
|
@@ -249,8 +251,9 @@ export default DitoTypeComponent.register('markup', {
|
|
|
249
251
|
const onFocus = () => this.onFocus()
|
|
250
252
|
|
|
251
253
|
const onBlur = () => {
|
|
252
|
-
|
|
254
|
+
// Write the value first, so that validating on blur validates it.
|
|
253
255
|
updateValue()
|
|
256
|
+
this.onBlur()
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
const onUpdate = () => {
|
|
@@ -258,10 +261,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
258
261
|
this.onInput()
|
|
259
262
|
}
|
|
260
263
|
|
|
261
|
-
const setValueDebounced = debounce(() =>
|
|
262
|
-
ignoreWatch = true
|
|
263
|
-
updateValue()
|
|
264
|
-
}, 100)
|
|
264
|
+
const setValueDebounced = debounce(() => updateValue(), 100)
|
|
265
265
|
|
|
266
266
|
const updateValue = () => {
|
|
267
267
|
const content = this.editor.getHTML()
|
|
@@ -270,6 +270,9 @@ export default DitoTypeComponent.register('markup', {
|
|
|
270
270
|
: content
|
|
271
271
|
if (value !== this.value) {
|
|
272
272
|
changed = true
|
|
273
|
+
// The value comes from the editor, so don't set it back as content,
|
|
274
|
+
// which would re-parse it and e.g. collapse trailing whitespace.
|
|
275
|
+
ignoreWatch = true
|
|
273
276
|
this.value = value
|
|
274
277
|
}
|
|
275
278
|
if (!this.focused && changed) {
|
|
@@ -337,7 +340,6 @@ export default DitoTypeComponent.register('markup', {
|
|
|
337
340
|
async onClickLink(editor) {
|
|
338
341
|
const attributes = await this.rootComponent.showDialog({
|
|
339
342
|
components: {
|
|
340
|
-
DitoIcon,
|
|
341
343
|
href: {
|
|
342
344
|
type: 'url',
|
|
343
345
|
label: 'Link',
|
|
@@ -440,9 +442,18 @@ export default DitoTypeComponent.register('markup', {
|
|
|
440
442
|
const list = []
|
|
441
443
|
const { commands } = this.editor
|
|
442
444
|
|
|
443
|
-
const addButton = ({
|
|
445
|
+
const addButton = ({
|
|
446
|
+
name,
|
|
447
|
+
id = name,
|
|
448
|
+
label = this.labelize(id),
|
|
449
|
+
icon,
|
|
450
|
+
command,
|
|
451
|
+
attributes,
|
|
452
|
+
onClick
|
|
453
|
+
}) => {
|
|
444
454
|
list.push({
|
|
445
|
-
|
|
455
|
+
id,
|
|
456
|
+
label,
|
|
446
457
|
icon,
|
|
447
458
|
isActive: this.editor.isActive(name, attributes),
|
|
448
459
|
onClick: () => {
|
|
@@ -479,6 +490,7 @@ export default DitoTypeComponent.register('markup', {
|
|
|
479
490
|
if (setting.includes(value)) {
|
|
480
491
|
addButton({
|
|
481
492
|
name,
|
|
493
|
+
id: `${name}-${value}`,
|
|
482
494
|
icon: `${icon}-${value}`,
|
|
483
495
|
command,
|
|
484
496
|
attributes: { [attribute]: value },
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
:parentContext="context"
|
|
11
11
|
)
|
|
12
12
|
VueMultiselect(
|
|
13
|
-
:id="
|
|
13
|
+
:id="componentPath"
|
|
14
14
|
ref="element"
|
|
15
15
|
v-model="selectedOptions"
|
|
16
16
|
:aria-label="label"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
:preserveSearch="!!searchFilter"
|
|
31
31
|
:clearOnSelect="!searchFilter"
|
|
32
32
|
:closeOnSelect="!stayOpen"
|
|
33
|
-
:loading="
|
|
33
|
+
:loading="isLoadingOptions || isLoadingSearchedOptions"
|
|
34
34
|
v-bind="attributes"
|
|
35
35
|
@open="onOpen"
|
|
36
36
|
@close="onClose"
|
|
@@ -80,6 +80,9 @@ export default DitoTypeComponent.register('multiselect', {
|
|
|
80
80
|
data() {
|
|
81
81
|
return {
|
|
82
82
|
searchedOptions: null,
|
|
83
|
+
isLoadingSearchedOptions: false,
|
|
84
|
+
// The term of the current search, see `onSearchChange()`:
|
|
85
|
+
currentSearchTerm: null,
|
|
83
86
|
populate: false
|
|
84
87
|
}
|
|
85
88
|
},
|
|
@@ -141,7 +144,9 @@ export default DitoTypeComponent.register('multiselect', {
|
|
|
141
144
|
const prefix = 'multiselect'
|
|
142
145
|
return {
|
|
143
146
|
[`${prefix}--multiple`]: this.multiple,
|
|
144
|
-
[`${prefix}--loading`]:
|
|
147
|
+
[`${prefix}--loading`]: (
|
|
148
|
+
this.isLoadingOptions || this.isLoadingSearchedOptions
|
|
149
|
+
),
|
|
145
150
|
[`${prefix}--highlight`]: this.showHighlight
|
|
146
151
|
}
|
|
147
152
|
},
|
|
@@ -225,18 +230,44 @@ export default DitoTypeComponent.register('multiselect', {
|
|
|
225
230
|
}
|
|
226
231
|
},
|
|
227
232
|
|
|
233
|
+
// Returns the options that `searchFilter()` returns for the search term,
|
|
234
|
+
// or `null` if it fails. Waits for the options, as the filter receives
|
|
235
|
+
// them, e.g. to search them when the search term is entered while they are
|
|
236
|
+
// still loading.
|
|
237
|
+
async loadSearchedOptions(searchTerm) {
|
|
238
|
+
try {
|
|
239
|
+
await this.optionsResolver.waitForValue()
|
|
240
|
+
return await this.searchFilter(new DitoContext(this, { searchTerm }))
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.addError(error.message || error)
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
|
|
228
247
|
async onSearchChange(searchTerm) {
|
|
229
248
|
if (this.searchFilter) {
|
|
249
|
+
this.currentSearchTerm = searchTerm
|
|
230
250
|
if (searchTerm) {
|
|
231
251
|
// Set `searchedOptions` to an empty array, before it will be
|
|
232
252
|
// populated asynchronously with the actual results.
|
|
233
253
|
this.searchedOptions = []
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
254
|
+
// Use a timeout to allow already resolved promises to return options
|
|
255
|
+
// without showing a loading indicator.
|
|
256
|
+
const timer = setTimeout(() => {
|
|
257
|
+
this.isLoadingSearchedOptions = true
|
|
258
|
+
}, 0)
|
|
259
|
+
const options = await this.loadSearchedOptions(searchTerm)
|
|
260
|
+
clearTimeout(timer)
|
|
261
|
+
// Searches that were replaced by newer ones in the meantime neither
|
|
262
|
+
// show their options nor end the loading state.
|
|
263
|
+
if (searchTerm === this.currentSearchTerm) {
|
|
264
|
+
this.searchedOptions = options
|
|
265
|
+
this.isLoadingSearchedOptions = false
|
|
266
|
+
}
|
|
237
267
|
} else {
|
|
238
268
|
// Clear `searchedOptions` when the query is cleared.
|
|
239
269
|
this.searchedOptions = null
|
|
270
|
+
this.isLoadingSearchedOptions = false
|
|
240
271
|
}
|
|
241
272
|
}
|
|
242
273
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-object(
|
|
3
3
|
v-if="isReady"
|
|
4
|
-
:id="
|
|
4
|
+
:id="componentPath"
|
|
5
5
|
)
|
|
6
6
|
.dito-object-content(
|
|
7
7
|
v-if="objectData"
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
:disabled="disabled || isLoading"
|
|
19
19
|
:collapsed="collapsed"
|
|
20
20
|
:collapsible="collapsible"
|
|
21
|
-
:deletable="
|
|
22
|
-
:editable="objectData && editable"
|
|
23
|
-
:editPath="path"
|
|
21
|
+
:deletable="deletable"
|
|
24
22
|
:accumulatedBasis="accumulatedBasis"
|
|
25
23
|
@delete="deleteItem(objectData)"
|
|
26
24
|
)
|
|
@@ -39,8 +37,9 @@
|
|
|
39
37
|
v-else
|
|
40
38
|
v-html="getItemLabel(schema, objectData)"
|
|
41
39
|
)
|
|
42
|
-
//- NOTE: `DitoEditButtons` here only handle the create
|
|
43
|
-
//- schema, the edit buttons inside are handled by
|
|
40
|
+
//- NOTE: For inlined objects, `DitoEditButtons` here only handle the create
|
|
41
|
+
//- button outside of the schema, the other edit buttons inside are handled by
|
|
42
|
+
//- `DitoSchemaInlined`.
|
|
44
43
|
DitoEditButtons(
|
|
45
44
|
:buttons="buttonSchemas"
|
|
46
45
|
:schema="schema"
|
|
@@ -52,6 +51,10 @@
|
|
|
52
51
|
:disabled="disabled || isLoading"
|
|
53
52
|
:creatable="creatable"
|
|
54
53
|
:createPath="createPath"
|
|
54
|
+
:editable="editable"
|
|
55
|
+
:editPath="path"
|
|
56
|
+
:deletable="!isInlined && deletable"
|
|
57
|
+
@delete="deleteItem(objectData)"
|
|
55
58
|
)
|
|
56
59
|
</template>
|
|
57
60
|
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
60
63
|
import DitoContext from '../DitoContext.js'
|
|
61
64
|
import SourceMixin from '../mixins/SourceMixin.js'
|
|
62
|
-
import { resolveSchemaComponent } from '../utils/schema.js'
|
|
65
|
+
import { resolveSchemaComponent } from '../utils/schema/setup.js'
|
|
63
66
|
|
|
64
67
|
// @vue/component
|
|
65
68
|
export default DitoTypeComponent.register('object', {
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import DitoTypeComponent from '../DitoTypeComponent.js'
|
|
3
|
+
import { setupSchemaComponents } from '../utils/schema/setup.js'
|
|
3
4
|
|
|
4
5
|
// @vue/component
|
|
5
6
|
export default DitoTypeComponent.register('panel', {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Panels share the data of their schema, or provide their own data through
|
|
8
|
+
// `schema.data`, see `DitoPanel`, so they never hold a value of their own.
|
|
9
|
+
defaultNested: false,
|
|
8
10
|
generateLabel: false,
|
|
9
11
|
omitSpacing: true,
|
|
10
12
|
|
|
11
13
|
getPanelSchema(api, schema) {
|
|
12
|
-
// For a TypePanel, the component schema is also the panel schema
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// For a TypePanel, the component schema is also the panel schema. Its name
|
|
15
|
+
// is added to the panel's data path and component path, see
|
|
16
|
+
// `getAllPanelEntries()`, as unnested components don't add it to data
|
|
17
|
+
// paths.
|
|
18
|
+
return schema
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async processSchema(api, schema, name, routes, level) {
|
|
22
|
+
// Process the panel's components so their forms get resolved too.
|
|
23
|
+
await setupSchemaComponents(api, schema, routes, level)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
// Panel components are displayed in the sidebar, see
|
|
28
|
+
// `DitoContainer.panelEntries`, not in place.
|
|
29
|
+
return null
|
|
16
30
|
}
|
|
17
31
|
})
|
|
18
32
|
</script>
|