@ditojs/admin 2.72.0 → 2.73.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.72.0",
3
+ "version": "2.73.0",
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",
@@ -42,8 +42,8 @@
42
42
  "not ie_mob > 0"
43
43
  ],
44
44
  "dependencies": {
45
- "@ditojs/ui": "^2.72.0",
46
- "@ditojs/utils": "^2.72.0",
45
+ "@ditojs/ui": "^2.73.0",
46
+ "@ditojs/utils": "^2.73.0",
47
47
  "@kyvg/vue3-notification": "^3.4.2",
48
48
  "@lk77/vue3-color": "^3.0.6",
49
49
  "@tiptap/core": "^3.15.3",
@@ -89,9 +89,9 @@
89
89
  "vue-upload-component": "^3.1.17"
90
90
  },
91
91
  "devDependencies": {
92
- "@ditojs/build": "^2.72.0",
92
+ "@ditojs/build": "^2.73.0",
93
93
  "typescript": "^5.9.3",
94
94
  "vite": "^7.3.1"
95
95
  },
96
- "gitHead": "6af5f5bad3ff155436e4344d9c422933ff5ee7d8"
96
+ "gitHead": "0afe5ac938419c584db685cddcbd8974ee000d7e"
97
97
  }
@@ -276,6 +276,14 @@ export default class DitoContext {
276
276
  return get(this, 'wasNotified', false)
277
277
  }
278
278
 
279
+ get isRunning() {
280
+ return get(this, 'isRunning', false)
281
+ }
282
+
283
+ set isRunning(value) {
284
+ set(this, 'isRunning', value)
285
+ }
286
+
279
287
  // Helper Methods
280
288
 
281
289
  get request() {
@@ -0,0 +1,56 @@
1
+ <template lang="pug">
2
+ component(
3
+ v-if="item.type === 'text'"
4
+ :is="item.as || 'span'"
5
+ ) {{ item.text }}
6
+ component(
7
+ v-else-if="item.type === 'html'"
8
+ :is="item.as || 'span'"
9
+ v-html="item.html"
10
+ )
11
+ DitoSpinner(
12
+ v-else-if="item.type === 'spinner'"
13
+ :size="item.size"
14
+ :color="item.color"
15
+ )
16
+ DitoIcon(
17
+ v-else-if="item.type === 'icon'"
18
+ :name="item.name"
19
+ :disabled="item.disabled"
20
+ )
21
+ component(
22
+ v-else-if="item.type === 'component'"
23
+ :is="item.component"
24
+ v-bind="item.props"
25
+ )
26
+ </template>
27
+
28
+ <script>
29
+ import DitoComponent from '../DitoComponent.js'
30
+ import DitoSpinner from './DitoSpinner.vue'
31
+ import { DitoIcon } from '@ditojs/ui/src'
32
+
33
+ export default DitoComponent.component('DitoAffix', {
34
+ components: { DitoSpinner, DitoIcon },
35
+
36
+ props: {
37
+ item: { type: Object, required: true },
38
+ parentContext: { type: Object, required: true }
39
+ },
40
+
41
+ computed: {
42
+ // Override DitoMixin's context with the parent's context
43
+ context() {
44
+ return this.parentContext
45
+ }
46
+ }
47
+ })
48
+ </script>
49
+
50
+ <style lang="scss">
51
+ .dito-affix {
52
+ &--text {
53
+ display: inline-block;
54
+ }
55
+ }
56
+ </style>
@@ -0,0 +1,82 @@
1
+ <template lang="pug">
2
+ .dito-affixes(
3
+ v-if="visibleItems.length > 0"
4
+ )
5
+ DitoAffix.dito-affix(
6
+ v-for="(item, index) in visibleItems"
7
+ :key="index"
8
+ :class="[`dito-affix--${item.type}`, item.class]"
9
+ :style="item.style"
10
+ :item="item"
11
+ :parentContext="parentContext"
12
+ )
13
+ </template>
14
+
15
+ <script>
16
+ import DitoComponent from '../DitoComponent.js'
17
+ import DitoAffix from './DitoAffix.vue'
18
+ import { asArray, isString } from '@ditojs/utils'
19
+ import { shouldRenderSchema } from '../utils/schema.js'
20
+
21
+ export default DitoComponent.component('DitoAffixes', {
22
+ components: { DitoAffix },
23
+
24
+ props: {
25
+ items: { type: [String, Object, Array], default: null },
26
+ parentContext: { type: Object, required: true }
27
+ },
28
+
29
+ computed: {
30
+ // Override DitoMixin's context with the parent's context
31
+ context() {
32
+ return this.parentContext
33
+ },
34
+
35
+ processedItems() {
36
+ return asArray(this.items)
37
+ .filter(Boolean)
38
+ .map(item =>
39
+ isString(item)
40
+ ? { type: 'text', text: item }
41
+ : item.type
42
+ ? item
43
+ : item.text != null
44
+ ? { type: 'text', ...item }
45
+ : item.html != null
46
+ ? { type: 'html', ...item }
47
+ : item
48
+ )
49
+ },
50
+
51
+ visibleItems() {
52
+ return this.processedItems.filter(item =>
53
+ shouldRenderSchema(item, this.context)
54
+ )
55
+ }
56
+ }
57
+ })
58
+ </script>
59
+
60
+ <style lang="scss">
61
+ @import '../styles/_imports';
62
+
63
+ .dito-affixes {
64
+ @include user-select(none);
65
+
66
+ display: flex;
67
+ align-items: center;
68
+ gap: $input-padding-hor;
69
+
70
+ @at-root .dito-input & {
71
+ color: $color-grey;
72
+
73
+ .dito-icon--disabled {
74
+ color: $color-light;
75
+ }
76
+ }
77
+
78
+ @at-root .dito-input:focus-within & {
79
+ color: $color-active;
80
+ }
81
+ }
82
+ </style>
@@ -12,29 +12,19 @@ component.dito-label(
12
12
  .dito-label__inner(
13
13
  v-if="text || prefixes.length > 0 || suffixes.length > 0"
14
14
  )
15
- .dito-label__prefix(
16
- v-if="prefixes.length > 0"
15
+ DitoAffixes.dito-label__prefix(
16
+ :items="prefixes"
17
+ :parentContext="context"
17
18
  )
18
- DitoElement(
19
- v-for="(prefix, index) of prefixes"
20
- :key="`prefix-${index}`"
21
- as="span"
22
- :content="prefix"
23
- )
24
19
  label(
25
20
  v-if="text"
26
21
  :for="dataPath"
27
22
  v-html="text"
28
23
  )
29
- .dito-label__suffix(
30
- v-if="suffixes.length > 0"
24
+ DitoAffixes.dito-label__suffix(
25
+ :items="suffixes"
26
+ :parentContext="context"
31
27
  )
32
- DitoElement(
33
- v-for="(suffix, index) of suffixes"
34
- :key="`suffix-${index}`"
35
- as="span"
36
- :content="suffix"
37
- )
38
28
  .dito-info(
39
29
  v-if="info"
40
30
  :data-info="info"
@@ -43,10 +33,13 @@ component.dito-label(
43
33
 
44
34
  <script>
45
35
  import DitoComponent from '../DitoComponent.js'
36
+ import DitoAffixes from './DitoAffixes.vue'
46
37
  import { isObject, asArray } from '@ditojs/utils'
47
38
 
48
39
  // @vue/component
49
40
  export default DitoComponent.component('DitoLabel', {
41
+ components: { DitoAffixes },
42
+
50
43
  emits: ['open'],
51
44
 
52
45
  props: {
@@ -7,7 +7,7 @@ Pagination(
7
7
  </template>
8
8
 
9
9
  <script>
10
- import { Pagination } from '@ditojs/ui/src'
10
+ import { DitoPagination as Pagination } from '@ditojs/ui/src'
11
11
  import DitoComponent from '../DitoComponent.js'
12
12
 
13
13
  // @vue/component
@@ -18,7 +18,7 @@ component.dito-panel(
18
18
  :hasOwnData="hasOwnData"
19
19
  generateLabels
20
20
  )
21
- template(#before)
21
+ template(#prepend)
22
22
  h2.dito-panel__header(:class="{ 'dito-panel__header--sticky': sticky }")
23
23
  span {{ getLabel(schema) }}
24
24
  DitoButtons.dito-buttons--small(
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- slot(name="before")
2
+ slot(name="prepend")
3
3
  .dito-schema(
4
4
  :class="{ 'dito-scroll-parent': scrollable, 'dito-schema--open': opened }"
5
5
  v-bind="$attrs"
@@ -101,7 +101,7 @@ slot(name="before")
101
101
  v-if="inlined && !hasHeader"
102
102
  name="edit-buttons"
103
103
  )
104
- slot(name="after")
104
+ slot(name="append")
105
105
  </template>
106
106
 
107
107
  <script>
@@ -12,7 +12,6 @@ export { default as DitoNotifications } from './DitoNotifications.vue'
12
12
  export { default as DitoSidebar } from './DitoSidebar.vue'
13
13
  export { default as DitoAccount } from './DitoAccount.vue'
14
14
  export { default as DitoDialog } from './DitoDialog.vue'
15
- export { default as DitoElement } from './DitoElement.vue'
16
15
  export { default as DitoLabel } from './DitoLabel.vue'
17
16
  export { default as DitoSchema } from './DitoSchema.vue'
18
17
  export { default as DitoSchemaInlined } from './DitoSchemaInlined.vue'
@@ -38,4 +37,6 @@ export { default as DitoTableCell } from './DitoTableCell.vue'
38
37
  export { default as DitoUploadFile } from './DitoUploadFile.vue'
39
38
  export { default as DitoDraggable } from './DitoDraggable.vue'
40
39
  export { default as DitoSpinner } from './DitoSpinner.vue'
40
+ export { default as DitoAffix } from './DitoAffix.vue'
41
+ export { default as DitoAffixes } from './DitoAffixes.vue'
41
42
  export { default as DitoVNode } from './DitoVNode.vue'
@@ -91,6 +91,7 @@ export default {
91
91
  const entry = queue.shift()
92
92
  if (entry) {
93
93
  let result
94
+ const errors = []
94
95
  for (const callback of callbacks) {
95
96
  try {
96
97
  const res = await callback.apply(this, entry.args)
@@ -98,15 +99,20 @@ export default {
98
99
  result = res
99
100
  }
100
101
  } catch (error) {
101
- console.error(
102
- `Error during event handler for '${event}':`,
103
- error
104
- )
102
+ errors.push(error)
105
103
  }
106
104
  }
107
- // Resolve the promise that was added to the queue for the event
108
- // that was just completed by the wrapper that called `next()`
109
- entry.resolve(result)
105
+ if (errors.length > 0) {
106
+ const error = new AggregateError(
107
+ errors,
108
+ `Errors during event handler for '${event}'`
109
+ )
110
+ entry.resolve(Promise.reject(error))
111
+ } else {
112
+ // Resolve the promise that was added to the queue for the event
113
+ // that was just completed by the wrapper that called `next()`
114
+ entry.resolve(result)
115
+ }
110
116
  next()
111
117
  }
112
118
  }
@@ -111,8 +111,10 @@
111
111
  }
112
112
 
113
113
  &--overlay {
114
+ $overlay-inset: calc(2 * $border-width);
115
+
114
116
  position: absolute;
115
- inset: $border-width;
117
+ inset: $overlay-inset;
116
118
  left: unset;
117
119
  padding: 0;
118
120
  border: 0;
@@ -120,8 +122,7 @@
120
122
  cursor: pointer;
121
123
 
122
124
  &.dito-button--clear {
123
- width: 2em;
124
- background: $color-white;
125
+ width: calc(2em - 2 * ($border-width + $overlay-inset));
125
126
  display: none;
126
127
 
127
128
  @at-root .dito-component:hover:not(:has(.dito-component)) & {
@@ -1,36 +1,55 @@
1
1
  <template lang="pug">
2
- button.dito-button(
2
+ DitoButton.dito-button(
3
3
  :id="dataPath"
4
4
  ref="element"
5
5
  :type="type"
6
+ :text="text"
6
7
  :title="title"
7
8
  :class="buttonClass"
8
9
  v-bind="attributes"
9
10
  )
10
- .dito-button__text(
11
- v-if="text"
12
- ) {{ text }}
13
- .dito-info(
14
- v-if="!label && info"
15
- :data-info="info"
16
- )
11
+ template(#prefix)
12
+ DitoAffixes(
13
+ v-if="prefixes.length > 0"
14
+ :items="prefixes"
15
+ :parentContext="context"
16
+ )
17
+ template(#suffix)
18
+ DitoAffixes(
19
+ v-if="suffixes.length > 0"
20
+ :items="suffixes"
21
+ :parentContext="context"
22
+ )
23
+ .dito-info(
24
+ v-if="!label && info"
25
+ :data-info="info"
26
+ )
17
27
  </template>
18
28
 
19
29
  <script>
20
30
  import DitoTypeComponent from '../DitoTypeComponent.js'
31
+ import DitoAffixes from '../components/DitoAffixes.vue'
32
+ import { DitoButton } from '@ditojs/ui/src'
21
33
  import { getSchemaAccessor } from '../utils/accessor.js'
22
34
  import { hasResource } from '../utils/resource.js'
23
- import { labelize } from '@ditojs/utils'
35
+ import { labelize, asArray } from '@ditojs/utils'
24
36
 
25
37
  export default DitoTypeComponent.register(
26
38
  ['button', 'submit'],
27
39
  // @vue/component
28
40
  {
41
+ components: { DitoAffixes, DitoButton },
29
42
  defaultValue: () => undefined, // Callback to override `defaultValue: null`
30
43
  excludeValue: true,
31
44
  defaultWidth: 'auto',
32
45
  generateLabel: false,
33
46
 
47
+ data() {
48
+ return {
49
+ isRunning: false
50
+ }
51
+ },
52
+
34
53
  computed: {
35
54
  verb() {
36
55
  return this.verbs[this.name]
@@ -53,6 +72,14 @@ export default DitoTypeComponent.register(
53
72
  default: null
54
73
  }),
55
74
 
75
+ prefixes() {
76
+ return asArray(this.schema.prefix)
77
+ },
78
+
79
+ suffixes() {
80
+ return asArray(this.schema.suffix)
81
+ },
82
+
56
83
  closeForm: getSchemaAccessor('closeForm', {
57
84
  type: Boolean,
58
85
  default: false
@@ -71,15 +98,31 @@ export default DitoTypeComponent.register(
71
98
  },
72
99
 
73
100
  async onClick() {
74
- const res = await this.emitEvent('click', {
75
- parent: this.schemaComponent
76
- })
77
- // Have buttons that define resources call `this.submit()` by default:
78
- if (
79
- res === undefined && // Meaning: don't prevent default.
80
- hasResource(this.schema)
81
- ) {
82
- await this.submit()
101
+ this.isRunning = true
102
+ try {
103
+ const res = await this.emitEvent('click', {
104
+ parent: this.schemaComponent
105
+ })
106
+ // Have buttons that define resources call `this.submit()` by default:
107
+ if (
108
+ res === undefined && // Meaning: don't prevent default.
109
+ hasResource(this.schema)
110
+ ) {
111
+ await this.submit()
112
+ }
113
+ } catch (error) {
114
+ const res = await this.emitEvent('error', { error })
115
+ if (res === undefined) {
116
+ if (error instanceof AggregateError) {
117
+ for (const err of error.errors) {
118
+ this.notify({ type: 'error', text: err })
119
+ }
120
+ } else {
121
+ this.notify({ type: 'error', text: error })
122
+ }
123
+ }
124
+ } finally {
125
+ this.isRunning = false
83
126
  }
84
127
  }
85
128
  }
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- Trigger.dito-color(
2
+ DitoTrigger.dito-color(
3
3
  v-model:show="showPopup"
4
4
  trigger="focus"
5
5
  )
@@ -35,7 +35,7 @@ Trigger.dito-color(
35
35
  import tinycolor from 'tinycolor2'
36
36
  import { Sketch as SketchPicker } from '@lk77/vue3-color'
37
37
  import { isObject, isString } from '@ditojs/utils'
38
- import { Trigger } from '@ditojs/ui/src'
38
+ import { DitoTrigger } from '@ditojs/ui/src'
39
39
  import DitoTypeComponent from '../DitoTypeComponent.js'
40
40
  import { getSchemaAccessor } from '../utils/accessor.js'
41
41
 
@@ -48,7 +48,7 @@ SketchPicker.computed.hex = function () {
48
48
 
49
49
  // @vue/component
50
50
  export default DitoTypeComponent.register('color', {
51
- components: { Trigger, SketchPicker },
51
+ components: { DitoTrigger, SketchPicker },
52
52
 
53
53
  data() {
54
54
  return {
@@ -9,7 +9,16 @@
9
9
  :format="formats"
10
10
  v-bind="attributes"
11
11
  )
12
- template(#after)
12
+ template(#prefix)
13
+ DitoAffixes(
14
+ :items="schema.prefix"
15
+ :parentContext="context"
16
+ )
17
+ template(#suffix)
18
+ DitoAffixes(
19
+ :items="schema.suffix"
20
+ :parentContext="context"
21
+ )
13
22
  button.dito-button--clear.dito-button--overlay(
14
23
  v-if="showClearButton"
15
24
  :disabled="disabled"
@@ -21,14 +30,19 @@
21
30
  <script>
22
31
  import DitoTypeComponent from '../DitoTypeComponent.js'
23
32
  import { getSchemaAccessor } from '../utils/accessor.js'
24
- import { DatePicker, TimePicker, DateTimePicker } from '@ditojs/ui/src'
33
+ import DitoAffixes from '../components/DitoAffixes.vue'
34
+ import {
35
+ DitoDatePicker,
36
+ DitoTimePicker,
37
+ DitoDateTimePicker
38
+ } from '@ditojs/ui/src'
25
39
  import { isDate, assignDeeply } from '@ditojs/utils'
26
40
 
27
41
  export default DitoTypeComponent.register(
28
42
  ['date', 'datetime', 'time'],
29
43
  // @vue/component
30
44
  {
31
- components: { DatePicker, TimePicker, DateTimePicker },
45
+ components: { DitoAffixes },
32
46
  // TODO: This is only here so we get placeholder added. Come up with a
33
47
  // better way to support attributes per component (a list of actually
34
48
  // supported attributes)
@@ -65,9 +79,9 @@ export default DitoTypeComponent.register(
65
79
  methods: {
66
80
  getComponent(type) {
67
81
  return {
68
- date: 'date-picker',
69
- time: 'time-picker',
70
- datetime: 'date-time-picker'
82
+ date: DitoDatePicker,
83
+ time: DitoTimePicker,
84
+ datetime: DitoDateTimePicker
71
85
  }[type]
72
86
  }
73
87
  },
@@ -12,7 +12,7 @@
12
12
  :class="{ 'dito-button--active': isActive }"
13
13
  @click="onClick"
14
14
  )
15
- Icon(:name="icon")
15
+ DitoIcon(:name="icon")
16
16
  EditorContent.dito-markup-editor(
17
17
  ref="editor"
18
18
  :editor="editor"
@@ -66,7 +66,7 @@ import { Footnotes, FootnoteReference, Footnote } from 'tiptap-footnotes'
66
66
  // Tools:
67
67
  import { History } from '@tiptap/extension-history'
68
68
 
69
- import { Icon } from '@ditojs/ui/src'
69
+ import { DitoIcon } from '@ditojs/ui/src'
70
70
  import { isArray, isObject, hyphenate, debounce, camelize } from '@ditojs/utils'
71
71
 
72
72
  // @vue/component
@@ -74,7 +74,7 @@ export default DitoTypeComponent.register('markup', {
74
74
  mixins: [DomMixin],
75
75
  components: {
76
76
  EditorContent,
77
- Icon
77
+ DitoIcon
78
78
  },
79
79
 
80
80
  data() {
@@ -298,6 +298,7 @@ export default DitoTypeComponent.register('markup', {
298
298
  async onClickLink(editor) {
299
299
  const attributes = await this.rootComponent.showDialog({
300
300
  components: {
301
+ DitoIcon,
301
302
  href: {
302
303
  type: 'url',
303
304
  label: 'Link',
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- InputField.dito-number(
2
+ DitoInput.dito-number(
3
3
  :id="dataPath"
4
4
  ref="element"
5
5
  v-model="inputValue"
@@ -9,7 +9,16 @@ InputField.dito-number(
9
9
  :max="max"
10
10
  :step="stepValue"
11
11
  )
12
- template(#after)
12
+ template(#prefix)
13
+ DitoAffixes(
14
+ :items="schema.prefix"
15
+ :parentContext="context"
16
+ )
17
+ template(#suffix)
18
+ DitoAffixes(
19
+ :items="schema.suffix"
20
+ :parentContext="context"
21
+ )
13
22
  button.dito-button--clear.dito-button--overlay(
14
23
  v-if="showClearButton"
15
24
  :disabled="disabled"
@@ -20,14 +29,15 @@ InputField.dito-number(
20
29
  <script>
21
30
  import DitoTypeComponent from '../DitoTypeComponent.js'
22
31
  import NumberMixin from '../mixins/NumberMixin.js'
23
- import { InputField } from '@ditojs/ui/src'
32
+ import DitoAffixes from '../components/DitoAffixes.vue'
33
+ import { DitoInput } from '@ditojs/ui/src'
24
34
 
25
35
  export default DitoTypeComponent.register(
26
36
  ['number', 'integer'],
27
37
  // @vue/component
28
38
  {
29
39
  mixins: [NumberMixin],
30
- components: { InputField },
40
+ components: { DitoInput, DitoAffixes },
31
41
  nativeField: true,
32
42
  textField: true,
33
43
 
@@ -10,7 +10,7 @@
10
10
  :max="max"
11
11
  :step="stepValue"
12
12
  )
13
- InputField.dito-number(
13
+ DitoInput.dito-number(
14
14
  v-if="input"
15
15
  v-model="inputValue"
16
16
  type="number"
@@ -25,12 +25,12 @@
25
25
  import DitoTypeComponent from '../DitoTypeComponent.js'
26
26
  import NumberMixin from '../mixins/NumberMixin.js'
27
27
  import { getSchemaAccessor } from '../utils/accessor.js'
28
- import { InputField } from '@ditojs/ui/src'
28
+ import { DitoInput } from '@ditojs/ui/src'
29
29
 
30
30
  // @vue/component
31
31
  export default DitoTypeComponent.register('slider', {
32
32
  mixins: [NumberMixin],
33
- components: { InputField },
33
+ components: { DitoInput },
34
34
  nativeField: true,
35
35
 
36
36
  computed: {
@@ -1,5 +1,5 @@
1
1
  <template lang="pug">
2
- SwitchButton.dito-switch(
2
+ DitoSwitch.dito-switch(
3
3
  :id="dataPath"
4
4
  ref="element"
5
5
  v-model="value"
@@ -10,7 +10,7 @@ SwitchButton.dito-switch(
10
10
 
11
11
  <script>
12
12
  import DitoTypeComponent from '../DitoTypeComponent.js'
13
- import { SwitchButton } from '@ditojs/ui/src'
13
+ import { DitoSwitch } from '@ditojs/ui/src'
14
14
 
15
15
  // @vue/component
16
16
  export default DitoTypeComponent.register('switch', {
@@ -18,7 +18,7 @@ export default DitoTypeComponent.register('switch', {
18
18
  defaultWidth: 'auto',
19
19
 
20
20
  components: {
21
- SwitchButton
21
+ DitoSwitch
22
22
  },
23
23
 
24
24
  computed: {