@ditojs/admin 2.26.0 → 2.26.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.26.0",
3
+ "version": "2.26.2",
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,31 +33,31 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.26.0",
37
- "@ditojs/utils": "^2.26.0",
36
+ "@ditojs/ui": "^2.26.1",
37
+ "@ditojs/utils": "^2.26.1",
38
38
  "@kyvg/vue3-notification": "^3.2.1",
39
39
  "@lk77/vue3-color": "^3.0.6",
40
- "@tiptap/core": "^2.2.6",
41
- "@tiptap/extension-blockquote": "^2.2.6",
42
- "@tiptap/extension-bold": "^2.2.6",
43
- "@tiptap/extension-bullet-list": "^2.2.6",
44
- "@tiptap/extension-code": "^2.2.6",
45
- "@tiptap/extension-code-block": "^2.2.6",
46
- "@tiptap/extension-document": "^2.2.6",
47
- "@tiptap/extension-hard-break": "^2.2.6",
48
- "@tiptap/extension-heading": "^2.2.6",
49
- "@tiptap/extension-history": "^2.2.6",
50
- "@tiptap/extension-horizontal-rule": "^2.2.6",
51
- "@tiptap/extension-italic": "^2.2.6",
52
- "@tiptap/extension-link": "^2.2.6",
53
- "@tiptap/extension-list-item": "^2.2.6",
54
- "@tiptap/extension-ordered-list": "^2.2.6",
55
- "@tiptap/extension-paragraph": "^2.2.6",
56
- "@tiptap/extension-strike": "^2.2.6",
57
- "@tiptap/extension-text": "^2.2.6",
58
- "@tiptap/extension-underline": "^2.2.6",
59
- "@tiptap/pm": "^2.2.6",
60
- "@tiptap/vue-3": "^2.2.6",
40
+ "@tiptap/core": "^2.3.0",
41
+ "@tiptap/extension-blockquote": "^2.3.0",
42
+ "@tiptap/extension-bold": "^2.3.0",
43
+ "@tiptap/extension-bullet-list": "^2.3.0",
44
+ "@tiptap/extension-code": "^2.3.0",
45
+ "@tiptap/extension-code-block": "^2.3.0",
46
+ "@tiptap/extension-document": "^2.3.0",
47
+ "@tiptap/extension-hard-break": "^2.3.0",
48
+ "@tiptap/extension-heading": "^2.3.0",
49
+ "@tiptap/extension-history": "^2.3.0",
50
+ "@tiptap/extension-horizontal-rule": "^2.3.0",
51
+ "@tiptap/extension-italic": "^2.3.0",
52
+ "@tiptap/extension-link": "^2.3.0",
53
+ "@tiptap/extension-list-item": "^2.3.0",
54
+ "@tiptap/extension-ordered-list": "^2.3.0",
55
+ "@tiptap/extension-paragraph": "^2.3.0",
56
+ "@tiptap/extension-strike": "^2.3.0",
57
+ "@tiptap/extension-text": "^2.3.0",
58
+ "@tiptap/extension-underline": "^2.3.0",
59
+ "@tiptap/pm": "^2.3.0",
60
+ "@tiptap/vue-3": "^2.3.0",
61
61
  "@vueuse/integrations": "^10.9.0",
62
62
  "codeflask": "^1.4.1",
63
63
  "filesize": "^10.1.1",
@@ -69,21 +69,21 @@
69
69
  "tippy.js": "^6.3.7",
70
70
  "type-fest": "^4.15.0",
71
71
  "vue": "3.4.10",
72
- "vue-multiselect": "^3.0.0-beta.3",
72
+ "vue-multiselect": "^3.0.0",
73
73
  "vue-router": "^4.3.0",
74
74
  "vue-upload-component": "^3.1.15"
75
75
  },
76
76
  "devDependencies": {
77
- "@ditojs/build": "^2.26.0",
77
+ "@ditojs/build": "^2.26.1",
78
78
  "@vitejs/plugin-vue": "^5.0.4",
79
79
  "@vue/compiler-sfc": "3.4.21",
80
80
  "pug": "^3.0.2",
81
- "sass": "1.74.1",
82
- "typescript": "^5.4.4",
81
+ "sass": "1.75.0",
82
+ "typescript": "^5.4.5",
83
83
  "vite": "^5.2.8"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "da47b5b8b9fca80193a7e93344bdc14cdaada329",
86
+ "gitHead": "7fd7a0b9a8727e1a27a8e78e7381f58c95017499",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
@@ -61,6 +61,12 @@ export default class DitoContext {
61
61
  : new DitoContext(component, context)
62
62
  }
63
63
 
64
+ extend(object) {
65
+ // Create a copy of this context that inherits from the real one, but
66
+ // overrides some properties with the ones from the passed `object`.
67
+ return Object.setPrototypeOf(object, this)
68
+ }
69
+
64
70
  // `nested` is `true` when the data-path points a value inside an item, and
65
71
  // `false` when it points to the item itself.
66
72
  get nested() {
@@ -3,20 +3,18 @@
3
3
  :class="{ 'dito-form-nested': isNestedRoute }"
4
4
  :data-resource="sourceSchema.path"
5
5
  )
6
- //- NOTE: Nested form components are kept alive by using `v-show` instead of
7
- //- `v-if` here, so event handling and other things still work with nested
8
- //- editing. Only render a router-view here if this isn't the last data route
9
- //- and not a nested form route, which will appear elsewhere in its own view.
6
+ //- Only render a router-view here if this isn't the last data route and not a
7
+ //- nested form route, which will appear elsewhere in its own view.
10
8
  RouterView(
11
- v-if="!(isLastUnnestedRoute || isNestedRoute)"
9
+ v-if="!isLastUnnestedRoute && !isNestedRoute"
12
10
  v-show="!isActive"
13
11
  )
14
- //- Use a <div> for inlined forms, as we shouldn't nest actual <form> tags.
15
- component(
12
+ //- NOTE: Nested form components are kept alive by using `v-show` instead of
13
+ //- `v-if` here, so event handling and other things still work with nested
14
+ //- editing.
15
+ DitoFormInner(
16
16
  v-show="isActive"
17
- :is="isNestedRoute ? 'div' : 'form'"
18
- :class="{ 'dito-scroll-parent': isRootForm }"
19
- @submit.prevent
17
+ :nested="isNestedRoute"
20
18
  )
21
19
  //- Prevent implicit submission of the form, for example when typing enter
22
20
  //- in an input field.
@@ -34,12 +32,12 @@
34
32
  :store="store"
35
33
  :padding="isNestedRoute ? 'nested' : 'root'"
36
34
  :disabled="isLoading"
37
- :scrollable="isRootForm"
35
+ :scrollable="!isNestedRoute"
38
36
  generateLabels
39
37
  )
40
38
  template(#buttons)
41
39
  DitoButtons.dito-buttons-round.dito-buttons-large.dito-buttons-main(
42
- :class="{ 'dito-buttons-sticky': isRootForm }"
40
+ :class="{ 'dito-buttons-sticky': !isNestedRoute }"
43
41
  :buttons="buttonSchemas"
44
42
  :dataPath="dataPath"
45
43
  :data="data"
@@ -127,10 +125,6 @@ export default DitoComponent.component('DitoForm', {
127
125
  )
128
126
  },
129
127
 
130
- isRootForm() {
131
- return this.dataPath === '' && !this.isNestedRoute
132
- },
133
-
134
128
  isActive() {
135
129
  return this.isLastRoute || this.isLastUnnestedRoute
136
130
  },
@@ -0,0 +1,26 @@
1
+ <template lang="pug">
2
+ //- Use a <div> for nested forms, as we shouldn't nest actual <form> tags.
3
+ div(
4
+ v-if="nested"
5
+ )
6
+ slot
7
+ form.dito-scroll-parent(
8
+ v-else
9
+ @submit.prevent
10
+ )
11
+ slot
12
+ </template>
13
+
14
+ <script>
15
+ import DitoComponent from '../DitoComponent.js'
16
+
17
+ // @vue/component
18
+ export default DitoComponent.component('DitoFormInner', {
19
+ props: {
20
+ nested: {
21
+ type: Boolean,
22
+ default: false
23
+ }
24
+ }
25
+ })
26
+ </script>
@@ -27,6 +27,7 @@ export { default as DitoCreateButton } from './DitoCreateButton.vue'
27
27
  export { default as DitoClipboard } from './DitoClipboard.vue'
28
28
  export { default as DitoView } from './DitoView.vue'
29
29
  export { default as DitoForm } from './DitoForm.vue'
30
+ export { default as DitoFormInner } from './DitoFormInner.vue'
30
31
  export { default as DitoFormNested } from './DitoFormNested.vue'
31
32
  export { default as DitoErrors } from './DitoErrors.vue'
32
33
  export { default as DitoScopes } from './DitoScopes.vue'
@@ -534,30 +534,36 @@ export default {
534
534
  }
535
535
  },
536
536
 
537
- async emitEvent(event, {
537
+ emitEvent(event, {
538
538
  context = null,
539
539
  parent = null
540
540
  } = {}) {
541
541
  const hasListeners = this.hasListeners(event)
542
542
  const parentHasListeners = parent?.hasListeners(event)
543
543
  if (hasListeners || parentHasListeners) {
544
- // The effects of some events need some time to propagate through Vue.
545
- // Use $nextTick() to make sure our handlers see these changes.
546
- // For example, `processedItem` is only correct after components that
547
- // are newly rendered due to data changes have registered themselves.
548
- if (['load', 'change'].includes(event)) {
549
- await this.$nextTick()
550
- }
551
-
552
- const getContext = () => (context = DitoContext.get(this, context))
553
- const res = hasListeners
554
- ? await this.emit(event, getContext())
555
- : undefined
556
- // Don't bubble to parent if handled event returned `false`
557
- if (parentHasListeners && res !== false) {
558
- parent.emit(event, getContext())
559
- }
560
- return res
544
+ const emitEvent = target =>
545
+ target.emit(event, (context = DitoContext.get(this, context)))
546
+
547
+ const handleParentListeners = result =>
548
+ // Don't bubble to parent if handled event returned `false`
549
+ parentHasListeners && result !== false
550
+ ? emitEvent(parent).then(() => result)
551
+ : result
552
+
553
+ const handleListeners = () =>
554
+ hasListeners
555
+ ? emitEvent(this).then(handleParentListeners)
556
+ : handleParentListeners(undefined)
557
+
558
+ return ['load', 'change'].includes(event)
559
+ ? // The effects of some events need time to propagate through Vue.
560
+ // Use $nextTick() to make sure our handlers see these changes.
561
+ // For example, `processedItem` is only correct after components
562
+ // that are newly rendered due to data changes have registered.
563
+ // NOTE: The result of `handleListeners()` makes it through the
564
+ // `$nextTick()` call and will be returned as expected.
565
+ this.$nextTick(handleListeners)
566
+ : handleListeners()
561
567
  }
562
568
  },
563
569
 
@@ -118,6 +118,8 @@ export default {
118
118
  }
119
119
  })
120
120
  }
121
+ // Make sure it's thenable even if there are no listeners.
122
+ return Promise.resolve()
121
123
  },
122
124
 
123
125
  // Checks if the component has listeners for a given event type:
@@ -122,7 +122,12 @@ export function getSchemaIdentifier(schema) {
122
122
  return JSON.stringify(schema)
123
123
  }
124
124
 
125
- export async function resolveSchema(schema, unwrapModule = false) {
125
+ const resolvedSchemas = new WeakMap()
126
+ export async function resolveSchema(value, unwrapModule = false) {
127
+ if (resolvedSchemas.has(value)) {
128
+ return resolvedSchemas.get(value)
129
+ }
130
+ let schema = value
126
131
  if (isFunction(schema)) {
127
132
  schema = schema()
128
133
  }
@@ -147,6 +152,7 @@ export async function resolveSchema(schema, unwrapModule = false) {
147
152
  }
148
153
  }
149
154
  }
155
+ resolvedSchemas.set(value, schema)
150
156
  return schema
151
157
  }
152
158
 
@@ -236,29 +242,34 @@ export async function resolveSchemaComponents(schemas) {
236
242
  await mapConcurrently(Object.values(schemas || {}), resolveSchemaComponent)
237
243
  }
238
244
 
245
+ const processedSchemas = new WeakSet()
239
246
  export async function processSchemaComponents(
240
247
  api,
241
248
  schema,
242
249
  routes = null,
243
250
  level = 0
244
251
  ) {
245
- const promises = []
246
- const process = (component, name, relativeLevel) => {
247
- promises.push(
248
- processSchemaComponent(
249
- api,
250
- component,
251
- name,
252
- routes,
253
- level + relativeLevel
252
+ if (schema && !processedSchemas.has(schema)) {
253
+ processedSchemas.add(schema)
254
+
255
+ const promises = []
256
+ const process = (component, name, relativeLevel) => {
257
+ promises.push(
258
+ processSchemaComponent(
259
+ api,
260
+ component,
261
+ name,
262
+ routes,
263
+ level + relativeLevel
264
+ )
254
265
  )
255
- )
256
- }
266
+ }
257
267
 
258
- iterateNestedSchemaComponents(schema, process)
259
- iterateSchemaComponents(getPanelSchemas(schema), process)
268
+ iterateNestedSchemaComponents(schema, process)
269
+ iterateSchemaComponents(getPanelSchemas(schema), process)
260
270
 
261
- await Promise.all(promises)
271
+ await Promise.all(promises)
272
+ }
262
273
  }
263
274
 
264
275
  export async function processSchemaComponent(