@ditojs/admin 3.2.6 → 3.3.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 +6012 -5827
- package/dist/dito-admin.umd.js +3 -7
- package/package.json +18 -17
- package/src/DitoAdmin.js +44 -24
- package/src/DitoContext.js +28 -2
- package/src/DitoTypeComponent.js +1 -0
- package/src/appState.js +6 -3
- package/src/components/DitoAccount.vue +35 -27
- package/src/components/DitoAffixes.vue +9 -2
- package/src/components/DitoClipboard.vue +59 -93
- package/src/components/DitoConfirmMessage.vue +18 -0
- package/src/components/DitoContainer.vue +56 -18
- package/src/components/DitoCreateButton.vue +36 -84
- package/src/components/DitoDialog.vue +20 -15
- package/src/components/DitoDragHandle.vue +44 -0
- package/src/components/DitoDraggable.vue +43 -16
- package/src/components/DitoEditButtons.vue +48 -24
- package/src/components/DitoErrors.vue +9 -56
- package/src/components/DitoForm.vue +82 -42
- package/src/components/DitoInputAffixes.vue +50 -0
- package/src/components/DitoLabel.vue +1 -15
- package/src/components/DitoMenu.vue +45 -52
- package/src/components/DitoNotifications.vue +38 -14
- package/src/components/DitoOptionList.vue +81 -0
- package/src/components/DitoOptionsEditButtons.vue +57 -0
- package/src/components/DitoPagination.vue +1 -8
- package/src/components/DitoPane.vue +51 -39
- package/src/components/DitoPanel.vue +12 -26
- package/src/components/DitoPanels.vue +11 -5
- package/src/components/DitoResizeHandle.vue +47 -0
- package/src/components/DitoRoot.vue +113 -320
- package/src/components/DitoSchema.vue +72 -63
- package/src/components/DitoSchemaInlined.vue +8 -9
- package/src/components/DitoScopes.vue +14 -18
- package/src/components/DitoSidebar.vue +4 -29
- package/src/components/DitoSourceItem.vue +114 -0
- package/src/components/DitoTableCell.vue +4 -0
- package/src/components/DitoTableHead.vue +15 -17
- package/src/components/DitoTabs.vue +25 -0
- package/src/components/DitoTrail.vue +25 -25
- package/src/components/DitoTreeItem.vue +102 -141
- package/src/components/DitoUploadFile.vue +5 -2
- package/src/components/DitoView.vue +33 -24
- package/src/index.js +4 -1
- package/src/mixins/DitoMixin.js +77 -110
- package/src/mixins/DomMixin.js +12 -14
- package/src/mixins/EmitterMixin.js +3 -2
- package/src/mixins/ItemMixin.js +38 -94
- package/src/mixins/OptionsMixin.js +38 -25
- package/src/mixins/ResourceMixin.js +182 -111
- package/src/mixins/RouteMixin.js +61 -44
- package/src/mixins/SchemaParentMixin.js +4 -1
- package/src/mixins/SourceMixin.js +118 -100
- package/src/mixins/TypeMixin.js +42 -40
- package/src/mixins/ValidationMixin.js +16 -4
- package/src/mixins/ValidatorMixin.js +1 -1
- package/src/styles/_settings.scss +4 -0
- package/src/styles/_tippy.scss +0 -11
- package/src/styles/style.scss +0 -1
- package/src/types/DitoTypeButton.vue +11 -14
- package/src/types/DitoTypeCheckboxes.vue +35 -17
- package/src/types/DitoTypeCode.vue +19 -41
- package/src/types/DitoTypeColor.vue +67 -19
- package/src/types/DitoTypeDate.vue +11 -14
- package/src/types/DitoTypeList.vue +50 -62
- package/src/types/DitoTypeMarkup.vue +41 -54
- package/src/types/DitoTypeMultiselect.vue +82 -52
- package/src/types/DitoTypeNumber.vue +12 -15
- package/src/types/DitoTypeObject.vue +4 -38
- package/src/types/DitoTypeRadio.vue +19 -6
- package/src/types/DitoTypeSection.vue +23 -9
- package/src/types/DitoTypeSelect.vue +39 -23
- package/src/types/DitoTypeSlider.vue +9 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +11 -14
- package/src/types/DitoTypeTreeList.vue +46 -8
- package/src/types/DitoTypeUpload.vue +190 -67
- package/src/utils/DataModel.js +368 -146
- package/src/utils/FileDropTargets.js +97 -0
- package/src/utils/ItemStores.js +31 -0
- package/src/utils/ListQuery.js +115 -0
- package/src/utils/LoadingTracker.js +76 -0
- package/src/utils/SchemaGraph.js +3 -2
- package/src/utils/Session.js +151 -0
- package/src/utils/ViewRegistry.js +85 -0
- package/src/utils/accessor.js +12 -4
- package/src/utils/affixes.js +33 -0
- package/src/utils/changeOnceEdited.js +33 -0
- package/src/utils/clipboard.js +55 -0
- package/src/utils/data.js +10 -5
- package/src/utils/dialogs.js +59 -0
- package/src/utils/dragResize.js +61 -0
- package/src/utils/filter.js +36 -2
- package/src/utils/html.js +9 -0
- package/src/utils/list.js +10 -0
- package/src/utils/options.js +4 -0
- package/src/utils/registration.js +18 -0
- package/src/utils/request.js +108 -13
- package/src/utils/route.js +6 -17
- package/src/utils/schema/data.js +29 -2
- package/src/utils/schema/events.js +19 -0
- package/src/utils/schema/item.js +114 -0
- package/src/utils/schema/lookup.js +39 -14
- package/src/utils/schema/setup.js +5 -5
- package/src/utils/schema/validation.js +16 -2
- package/src/utils/uid.js +105 -8
- package/src/validators/_decimals.js +16 -5
- package/types/index.d.ts +168 -41
- package/src/mixins/LoadingMixin.js +0 -23
- package/src/mixins/PulldownMixin.js +0 -72
- package/src/styles/_pulldown.scss +0 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/admin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.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/main/packages/admin",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"dist/",
|
|
25
25
|
"types/",
|
|
26
26
|
"!src/**/*.test.js",
|
|
27
|
-
"!src/**/__snapshots__/"
|
|
27
|
+
"!src/**/__snapshots__/",
|
|
28
|
+
"!src/test/",
|
|
29
|
+
"!src/**/node_modules/"
|
|
28
30
|
],
|
|
29
31
|
"scripts": {
|
|
30
32
|
"build": "vite build",
|
|
@@ -41,10 +43,12 @@
|
|
|
41
43
|
"not ie_mob > 0"
|
|
42
44
|
],
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"@ditojs/ui": "^3.
|
|
45
|
-
"@ditojs/utils": "^3.
|
|
46
|
+
"@ditojs/ui": "^3.3.0",
|
|
47
|
+
"@ditojs/utils": "^3.3.0",
|
|
48
|
+
"type-fest": "^5.10.0"
|
|
46
49
|
},
|
|
47
50
|
"peerDependencies": {
|
|
51
|
+
"@floating-ui/dom": "^1.8.0",
|
|
48
52
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
49
53
|
"@tiptap/core": "^3.31.4",
|
|
50
54
|
"@tiptap/extension-blockquote": "^3.31.4",
|
|
@@ -67,27 +71,26 @@
|
|
|
67
71
|
"@tiptap/extension-subscript": "^3.31.4",
|
|
68
72
|
"@tiptap/extension-superscript": "^3.31.4",
|
|
69
73
|
"@tiptap/extension-text": "^3.31.4",
|
|
70
|
-
"@tiptap/extension-text-style": "^3.31.4",
|
|
71
74
|
"@tiptap/extension-underline": "^3.31.4",
|
|
72
75
|
"@tiptap/extensions": "^3.31.4",
|
|
73
76
|
"@tiptap/pm": "^3.31.4",
|
|
74
77
|
"@tiptap/vue-3": "^3.31.4",
|
|
75
78
|
"@vueuse/integrations": "^15.0.0",
|
|
76
79
|
"codeflask": "^1.4.1",
|
|
77
|
-
"focus-trap": "^8.2.
|
|
78
|
-
"nanoid": "^6.0.
|
|
80
|
+
"focus-trap": "^8.2.3",
|
|
81
|
+
"nanoid": "^6.0.2",
|
|
79
82
|
"sortablejs": "^1.15.7",
|
|
80
83
|
"tippy.js": "^6.3.7",
|
|
81
84
|
"tiptap-footnotes": "^3.0.1",
|
|
82
|
-
"type-fest": "^5.10.0",
|
|
83
85
|
"vue": "^3.5.43",
|
|
84
86
|
"vue-color": "^3.3.3",
|
|
85
87
|
"vue-multiselect": "^3.5.0",
|
|
86
|
-
"vue-router": "^5.
|
|
88
|
+
"vue-router": "^5.4.0",
|
|
87
89
|
"vue-upload-component": "^3.2.2"
|
|
88
90
|
},
|
|
89
91
|
"devDependencies": {
|
|
90
|
-
"@ditojs/build": "^3.
|
|
92
|
+
"@ditojs/build": "^3.3.0",
|
|
93
|
+
"@floating-ui/dom": "^1.8.0",
|
|
91
94
|
"@kyvg/vue3-notification": "^3.4.2",
|
|
92
95
|
"@tiptap/core": "^3.31.4",
|
|
93
96
|
"@tiptap/extension-blockquote": "^3.31.4",
|
|
@@ -110,26 +113,24 @@
|
|
|
110
113
|
"@tiptap/extension-subscript": "^3.31.4",
|
|
111
114
|
"@tiptap/extension-superscript": "^3.31.4",
|
|
112
115
|
"@tiptap/extension-text": "^3.31.4",
|
|
113
|
-
"@tiptap/extension-text-style": "^3.31.4",
|
|
114
116
|
"@tiptap/extension-underline": "^3.31.4",
|
|
115
117
|
"@tiptap/extensions": "^3.31.4",
|
|
116
118
|
"@tiptap/pm": "^3.31.4",
|
|
117
119
|
"@tiptap/vue-3": "^3.31.4",
|
|
118
120
|
"@vueuse/integrations": "^15.0.0",
|
|
119
121
|
"codeflask": "^1.4.1",
|
|
120
|
-
"focus-trap": "^8.2.
|
|
121
|
-
"nanoid": "^6.0.
|
|
122
|
+
"focus-trap": "^8.2.3",
|
|
123
|
+
"nanoid": "^6.0.2",
|
|
122
124
|
"sortablejs": "^1.15.7",
|
|
123
125
|
"tippy.js": "^6.3.7",
|
|
124
126
|
"tiptap-footnotes": "^3.0.1",
|
|
125
|
-
"type-fest": "^5.10.0",
|
|
126
127
|
"typescript": "^6.0.3",
|
|
127
|
-
"vite": "^8.3.
|
|
128
|
+
"vite": "^8.3.4",
|
|
128
129
|
"vue": "^3.5.43",
|
|
129
130
|
"vue-color": "^3.3.3",
|
|
130
131
|
"vue-multiselect": "^3.5.0",
|
|
131
|
-
"vue-router": "^5.
|
|
132
|
+
"vue-router": "^5.4.0",
|
|
132
133
|
"vue-upload-component": "^3.2.2"
|
|
133
134
|
},
|
|
134
|
-
"gitHead": "
|
|
135
|
+
"gitHead": "1ba6ba0a6642294959966a3488b0944c7c321b39"
|
|
135
136
|
}
|
package/src/DitoAdmin.js
CHANGED
|
@@ -11,16 +11,20 @@ import {
|
|
|
11
11
|
import * as components from './components/index.js'
|
|
12
12
|
import * as types from './types/index.js'
|
|
13
13
|
import DitoRoot from './components/DitoRoot.vue'
|
|
14
|
+
import DitoView from './components/DitoView.vue'
|
|
14
15
|
import DitoTypeComponent from './DitoTypeComponent.js'
|
|
15
16
|
import ResizeDirective from './directives/resize.js'
|
|
16
17
|
import { getResource } from './utils/resource.js'
|
|
17
18
|
import { request, getApiUrl, isApiUrl } from './utils/request.js'
|
|
19
|
+
import { Session } from './utils/Session.js'
|
|
20
|
+
import { ViewRegistry } from './utils/ViewRegistry.js'
|
|
18
21
|
import verbs from './verbs.js'
|
|
19
22
|
|
|
20
23
|
export default class DitoAdmin {
|
|
21
24
|
constructor(el, {
|
|
22
|
-
// `dito` contains the base and
|
|
23
|
-
dito
|
|
25
|
+
// `dito` contains the base, api and settings passed from `AdminController`
|
|
26
|
+
// as `window.dito`, which is also the default:
|
|
27
|
+
dito: ditoObject = globalThis.dito ?? {},
|
|
24
28
|
api,
|
|
25
29
|
views = {},
|
|
26
30
|
...options
|
|
@@ -29,7 +33,7 @@ export default class DitoAdmin {
|
|
|
29
33
|
// Merge in `api` settings as passed from `config.admin` and through the
|
|
30
34
|
// `AdminController` with `api` values from from 'admin/index.js'
|
|
31
35
|
// NOTE: `AdminController` provides `dito.api.base`
|
|
32
|
-
this.api = api = assignDeeply({ base: '/' },
|
|
36
|
+
this.api = api = assignDeeply({ base: '/' }, ditoObject.api, api)
|
|
33
37
|
this.options = options
|
|
34
38
|
|
|
35
39
|
// Setup default api settings:
|
|
@@ -146,6 +150,35 @@ export default class DitoAdmin {
|
|
|
146
150
|
el = document.querySelector(el)
|
|
147
151
|
}
|
|
148
152
|
|
|
153
|
+
const router = createRouter({
|
|
154
|
+
// Start with a catch-all route, to be replaced by the actual routes once
|
|
155
|
+
// the schemas are loaded, to prevent vue-router from complaining, see
|
|
156
|
+
// `ViewRegistry.resolve()` for the actual route setup.
|
|
157
|
+
routes: [
|
|
158
|
+
{
|
|
159
|
+
name: 'catch-all',
|
|
160
|
+
path: '/:_(.*)',
|
|
161
|
+
components: {}
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
history: createWebHistory(ditoObject.base),
|
|
165
|
+
linkActiveClass: '',
|
|
166
|
+
linkExactActiveClass: ''
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const viewRegistry = new ViewRegistry({
|
|
170
|
+
api,
|
|
171
|
+
router,
|
|
172
|
+
unresolvedViews: views,
|
|
173
|
+
viewComponent: DitoView
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const session = new Session({
|
|
177
|
+
api,
|
|
178
|
+
viewRegistry,
|
|
179
|
+
redirectAfterLogin: options.login?.redirectAfterLogin
|
|
180
|
+
})
|
|
181
|
+
|
|
149
182
|
const app = (this.app = createApp({
|
|
150
183
|
components: {
|
|
151
184
|
DitoRoot,
|
|
@@ -163,12 +196,15 @@ export default class DitoAdmin {
|
|
|
163
196
|
// https://github.com/vuejs/vue/issues/7017#issuecomment-480906691
|
|
164
197
|
provide: {
|
|
165
198
|
api,
|
|
199
|
+
session,
|
|
200
|
+
viewRegistry,
|
|
166
201
|
// A default list of verbs are provided by $verbs() and can be
|
|
167
202
|
// overridden at any point in the component hierarchy.
|
|
168
203
|
$verbs: () => verbs,
|
|
204
|
+
// The views, once resolved for the logged in user, see `Session`:
|
|
205
|
+
$views: () => viewRegistry.views,
|
|
169
206
|
// Provide defaults so DitoMixin can inject them for all components:
|
|
170
207
|
// inject: [ '$isPopulated', '$schemaComponent', '$routeComponent' ]
|
|
171
|
-
$views: () => {},
|
|
172
208
|
$isPopulated: () => true,
|
|
173
209
|
$parentComponent: () => null,
|
|
174
210
|
$schemaComponent: () => null,
|
|
@@ -180,14 +216,14 @@ export default class DitoAdmin {
|
|
|
180
216
|
$dialogComponent: () => null,
|
|
181
217
|
$dataModel: () => null,
|
|
182
218
|
$panelComponent: () => null,
|
|
183
|
-
$tabComponent: () => null
|
|
219
|
+
$tabComponent: () => null,
|
|
220
|
+
$loadingTracker: () => null
|
|
184
221
|
},
|
|
185
222
|
|
|
186
223
|
render: () =>
|
|
187
224
|
createElement(DitoRoot, {
|
|
188
225
|
ref: 'root',
|
|
189
|
-
class:
|
|
190
|
-
unresolvedViews: views,
|
|
226
|
+
class: ditoObject.settings?.rootClass,
|
|
191
227
|
options
|
|
192
228
|
})
|
|
193
229
|
}))
|
|
@@ -203,23 +239,7 @@ export default class DitoAdmin {
|
|
|
203
239
|
|
|
204
240
|
app.directive('resize', ResizeDirective)
|
|
205
241
|
|
|
206
|
-
app.use(
|
|
207
|
-
createRouter({
|
|
208
|
-
// Start with a catch-all route, to be replaced by the actual routes
|
|
209
|
-
// once the schemas are loaded, to prevent vue-router from complaining,
|
|
210
|
-
// see: `resolveViews()` in `DitoRoot` for the actual route setup.
|
|
211
|
-
routes: [
|
|
212
|
-
{
|
|
213
|
-
name: 'catch-all',
|
|
214
|
-
path: '/:_(.*)',
|
|
215
|
-
components: {}
|
|
216
|
-
}
|
|
217
|
-
],
|
|
218
|
-
history: createWebHistory(dito.base),
|
|
219
|
-
linkActiveClass: '',
|
|
220
|
-
linkExactActiveClass: ''
|
|
221
|
-
})
|
|
222
|
-
)
|
|
242
|
+
app.use(router)
|
|
223
243
|
|
|
224
244
|
el.classList.add('dito-app')
|
|
225
245
|
app.mount(el)
|
package/src/DitoContext.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getLastDataPathIndex
|
|
11
11
|
} from './utils/data.js'
|
|
12
12
|
import { isNested } from './utils/schema/structure.js'
|
|
13
|
+
import { getTextFromHtml } from './utils/html.js'
|
|
13
14
|
|
|
14
15
|
const { hasOwnProperty } = Object.prototype
|
|
15
16
|
|
|
@@ -54,7 +55,15 @@ function get(context, key, defaultValue) {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
function set(context, key, value) {
|
|
57
|
-
|
|
58
|
+
// Define the value on the context object itself: An assignment would be
|
|
59
|
+
// passed on to the component that the object inherits from, see
|
|
60
|
+
// `DitoContext` constructor, and change its state for all its contexts.
|
|
61
|
+
Object.defineProperty(toObject(context), key, {
|
|
62
|
+
value,
|
|
63
|
+
writable: true,
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true
|
|
66
|
+
})
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export default class DitoContext {
|
|
@@ -245,10 +254,18 @@ export default class DitoContext {
|
|
|
245
254
|
return get(this, 'flattenedViews', null)
|
|
246
255
|
}
|
|
247
256
|
|
|
257
|
+
// The label of the item as HTML, with escaped item values, see
|
|
258
|
+
// `getItemLabel()`.
|
|
248
259
|
get itemLabel() {
|
|
249
260
|
return get(this, 'itemLabel', null)
|
|
250
261
|
}
|
|
251
262
|
|
|
263
|
+
// The text of `itemLabel`, with its escaped item values unescaped, e.g. for
|
|
264
|
+
// places that don't display HTML.
|
|
265
|
+
get itemLabelText() {
|
|
266
|
+
return getTextFromHtml(this.itemLabel)
|
|
267
|
+
}
|
|
268
|
+
|
|
252
269
|
get formLabel() {
|
|
253
270
|
return get(this, 'formLabel', null)
|
|
254
271
|
}
|
|
@@ -326,12 +343,21 @@ export default class DitoContext {
|
|
|
326
343
|
return get(this, 'wasNotified', false)
|
|
327
344
|
}
|
|
328
345
|
|
|
346
|
+
// Whether the action of the component is running, e.g. the click handler of
|
|
347
|
+
// a button, which disables it. Handlers can set it to `false` to end it
|
|
348
|
+
// early, e.g. before opening a dialog. This changes the state of the
|
|
349
|
+
// component, which resets it when the action ends.
|
|
329
350
|
get isRunning() {
|
|
330
351
|
return get(this, 'isRunning', false)
|
|
331
352
|
}
|
|
332
353
|
|
|
333
354
|
set isRunning(value) {
|
|
334
|
-
|
|
355
|
+
const { component } = this
|
|
356
|
+
if (component && 'isRunning' in component) {
|
|
357
|
+
component.isRunning = value
|
|
358
|
+
} else {
|
|
359
|
+
set(this, 'isRunning', value)
|
|
360
|
+
}
|
|
335
361
|
}
|
|
336
362
|
|
|
337
363
|
get query() {
|
package/src/DitoTypeComponent.js
CHANGED
package/src/appState.js
CHANGED
|
@@ -3,10 +3,13 @@ import { parseUserAgent } from './utils/agent'
|
|
|
3
3
|
|
|
4
4
|
export default reactive({
|
|
5
5
|
title: '',
|
|
6
|
+
// The route components by route level, see `RouteMixin`:
|
|
6
7
|
routeComponents: [],
|
|
7
8
|
user: null,
|
|
8
9
|
agent: parseUserAgent(navigator.userAgent || ''),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// The responses loaded with `cache: 'global'`, see `DitoMixin.load()`. Only
|
|
11
|
+
// `Session` replaces it, when the user changes:
|
|
12
|
+
loadCache: {},
|
|
13
|
+
// The modifier class of the page, e.g. for wide schemas, see `DitoSchema`:
|
|
14
|
+
pageClass: null
|
|
12
15
|
})
|
|
@@ -1,47 +1,36 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.dito-account
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
DitoMenuButton.dito-account__button(
|
|
4
|
+
:items="items"
|
|
5
|
+
placement="bottom-left"
|
|
6
|
+
offset
|
|
7
|
+
@select="onSelectMenuItem"
|
|
7
8
|
)
|
|
8
9
|
span {{ user.username }}
|
|
9
|
-
ul.dito-pulldown(
|
|
10
|
-
role="menu"
|
|
11
|
-
:class="{ 'dito-pulldown--open': pulldown.open }"
|
|
12
|
-
)
|
|
13
|
-
li(
|
|
14
|
-
v-for="(label, value) of items"
|
|
15
|
-
)
|
|
16
|
-
a.dito-pulldown__item(
|
|
17
|
-
role="menuitem"
|
|
18
|
-
@mousedown.stop="onPulldownMouseDown(value)"
|
|
19
|
-
@mouseup="onPulldownMouseUp(value)"
|
|
20
|
-
) {{ label }}
|
|
21
10
|
</template>
|
|
22
11
|
|
|
23
12
|
<script>
|
|
13
|
+
import { DitoMenuButton } from '@ditojs/ui/src'
|
|
24
14
|
import DitoComponent from '../DitoComponent.js'
|
|
25
|
-
import PulldownMixin from '../mixins/PulldownMixin.js'
|
|
26
15
|
|
|
27
16
|
// @vue/component
|
|
28
17
|
export default DitoComponent.component('DitoAccount', {
|
|
29
|
-
|
|
18
|
+
components: { DitoMenuButton },
|
|
30
19
|
|
|
31
20
|
data() {
|
|
32
21
|
return {
|
|
33
|
-
items:
|
|
34
|
-
settings: 'Settings',
|
|
35
|
-
logout: 'Logout'
|
|
36
|
-
|
|
22
|
+
items: [
|
|
23
|
+
{ value: 'settings', label: 'Settings' },
|
|
24
|
+
{ value: 'logout', label: 'Logout' }
|
|
25
|
+
]
|
|
37
26
|
}
|
|
38
27
|
},
|
|
39
28
|
|
|
40
29
|
methods: {
|
|
41
|
-
|
|
30
|
+
onSelectMenuItem({ value }) {
|
|
42
31
|
switch (value) {
|
|
43
32
|
case 'logout':
|
|
44
|
-
this.
|
|
33
|
+
this.session.logout()
|
|
45
34
|
break
|
|
46
35
|
case 'settings':
|
|
47
36
|
console.info('TODO: Implement Settings')
|
|
@@ -56,11 +45,30 @@ export default DitoComponent.component('DitoAccount', {
|
|
|
56
45
|
@import '../styles/_imports';
|
|
57
46
|
|
|
58
47
|
.dito-account {
|
|
59
|
-
position: relative;
|
|
60
48
|
display: inline-block;
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
// The menu button is a `DitoButton`, but shows the user name as plain text,
|
|
51
|
+
// so reset the button styles, including the ones of its states:
|
|
52
|
+
& &__button {
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
|
|
55
|
+
&,
|
|
56
|
+
&:hover,
|
|
57
|
+
&:active,
|
|
58
|
+
&:focus {
|
|
59
|
+
min-width: 0;
|
|
60
|
+
min-height: 0;
|
|
61
|
+
padding: 0;
|
|
62
|
+
border: 0;
|
|
63
|
+
font: inherit;
|
|
64
|
+
color: inherit;
|
|
65
|
+
background: none;
|
|
66
|
+
box-shadow: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&:focus-visible {
|
|
70
|
+
box-shadow: $shadow-focus;
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
74
|
</style>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
:parentContext="parentContext"
|
|
14
14
|
)
|
|
15
15
|
button.dito-affixes__clear(
|
|
16
|
-
v-if="
|
|
16
|
+
v-if="shouldShowClearButton"
|
|
17
17
|
type="button"
|
|
18
18
|
title="Clear"
|
|
19
19
|
:disabled="disabled"
|
|
@@ -43,7 +43,10 @@ export default DitoComponent.component('DitoAffixes', {
|
|
|
43
43
|
mode: { type: String, default: null },
|
|
44
44
|
position: { type: String, default: null },
|
|
45
45
|
absolute: { type: Boolean, default: false },
|
|
46
|
+
// Whether the value can be cleared, and whether there's one to clear: The
|
|
47
|
+
// clear button only shows when both are true.
|
|
46
48
|
clearable: { type: Boolean, default: false },
|
|
49
|
+
hasValue: { type: Boolean, default: false },
|
|
47
50
|
disabled: { type: Boolean, default: false },
|
|
48
51
|
inlineInfo: { type: String, default: null },
|
|
49
52
|
parentContext: { type: Object, required: true }
|
|
@@ -86,10 +89,14 @@ export default DitoComponent.component('DitoAffixes', {
|
|
|
86
89
|
}
|
|
87
90
|
},
|
|
88
91
|
|
|
92
|
+
shouldShowClearButton() {
|
|
93
|
+
return this.clearable && this.hasValue
|
|
94
|
+
},
|
|
95
|
+
|
|
89
96
|
hasContent() {
|
|
90
97
|
return (
|
|
91
98
|
this.visibleItems.length > 0 ||
|
|
92
|
-
this.
|
|
99
|
+
this.shouldShowClearButton ||
|
|
93
100
|
this.inlineInfo ||
|
|
94
101
|
hasSlotContent(this.$slots.prepend) ||
|
|
95
102
|
hasSlotContent(this.$slots.append)
|
|
@@ -2,41 +2,45 @@
|
|
|
2
2
|
.dito-clipboard.dito-buttons.dito-buttons--round(
|
|
3
3
|
v-if="clipboard"
|
|
4
4
|
)
|
|
5
|
-
|
|
6
|
-
ref="copyData"
|
|
7
|
-
type="button"
|
|
5
|
+
DitoButton.dito-button--copy(
|
|
8
6
|
title="Copy Data"
|
|
9
|
-
:disabled="!
|
|
7
|
+
:disabled="!hasData"
|
|
10
8
|
@click="onCopy"
|
|
11
9
|
)
|
|
12
|
-
|
|
13
|
-
type="button"
|
|
10
|
+
DitoButton.dito-button--paste(
|
|
14
11
|
title="Paste Data"
|
|
15
|
-
:disabled="!
|
|
12
|
+
:disabled="!isPasteEnabled"
|
|
16
13
|
@click="onPaste"
|
|
17
14
|
)
|
|
18
15
|
</template>
|
|
19
16
|
|
|
20
17
|
<script>
|
|
21
|
-
import { isObject, clone
|
|
18
|
+
import { isObject, clone } from '@ditojs/utils'
|
|
19
|
+
import { DitoButton } from '@ditojs/ui/src'
|
|
22
20
|
import DitoComponent from '../DitoComponent.js'
|
|
23
21
|
import DomMixin from '../mixins/DomMixin.js'
|
|
24
22
|
import DitoContext from '../DitoContext.js'
|
|
23
|
+
import {
|
|
24
|
+
getClipboardData,
|
|
25
|
+
hasClipboardData,
|
|
26
|
+
copyClipboardData,
|
|
27
|
+
readClipboardData
|
|
28
|
+
} from '../utils/clipboard.js'
|
|
25
29
|
|
|
26
30
|
// @vue/component
|
|
27
31
|
export default DitoComponent.component('DitoClipboard', {
|
|
28
32
|
mixins: [DomMixin],
|
|
33
|
+
components: { DitoButton },
|
|
34
|
+
|
|
35
|
+
emits: ['paste'],
|
|
29
36
|
|
|
30
37
|
props: {
|
|
31
38
|
clipboard: { type: [Boolean, Object], required: true },
|
|
32
|
-
schema: { type: Object, required: true }
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
copyEnabled: false,
|
|
38
|
-
pasteEnabled: false
|
|
39
|
-
}
|
|
39
|
+
schema: { type: Object, required: true },
|
|
40
|
+
hasData: { type: Boolean, required: true },
|
|
41
|
+
// Returns the data to copy. A function rather than the data itself, so
|
|
42
|
+
// that it's only processed when copying.
|
|
43
|
+
getData: { type: Function, required: true }
|
|
40
44
|
},
|
|
41
45
|
|
|
42
46
|
computed: {
|
|
@@ -58,106 +62,68 @@ export default DitoComponent.component('DitoClipboard', {
|
|
|
58
62
|
? clipboardData =>
|
|
59
63
|
paste.call(this, new DitoContext(this, { clipboardData }))
|
|
60
64
|
: clipboardData => clipboardData
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
watch: {
|
|
65
|
-
// Check right away also in case there's already data (e.g. create form):
|
|
66
|
-
'parentComponent.hasData': {
|
|
67
|
-
immediate: true,
|
|
68
|
-
handler: 'updateCopy'
|
|
69
65
|
},
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
handler: 'updatePaste'
|
|
67
|
+
isPasteEnabled() {
|
|
68
|
+
return hasClipboardData(this.schema.name)
|
|
74
69
|
}
|
|
75
70
|
},
|
|
76
71
|
|
|
77
72
|
mounted() {
|
|
78
|
-
//
|
|
79
|
-
// (re)activated, as those are the moments when
|
|
80
|
-
this.
|
|
81
|
-
this.domOn(
|
|
73
|
+
// Read the clipboard right away, and whenever something gets copied or the
|
|
74
|
+
// window gets (re)activated, as those are the moments when it can change:
|
|
75
|
+
this.updateClipboardData()
|
|
76
|
+
this.domOn(document, { copy: this.updateClipboardData })
|
|
77
|
+
this.domOn(window, { focus: this.updateClipboardData })
|
|
82
78
|
},
|
|
83
79
|
|
|
84
80
|
methods: {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Use the internal clipboard as fallback.
|
|
92
|
-
let { clipboardData } = this.appState
|
|
93
|
-
try {
|
|
94
|
-
const json = await navigator.clipboard?.readText?.()
|
|
95
|
-
if (json) {
|
|
96
|
-
clipboardData = JSON.parse(json)
|
|
97
|
-
}
|
|
98
|
-
} catch (err) {
|
|
99
|
-
if (report) {
|
|
100
|
-
console.error(err, err.name, err.message)
|
|
101
|
-
if (err.name === 'SyntaxError') {
|
|
102
|
-
alert(deindent`
|
|
103
|
-
The data in the clipboard appears to be malformed:
|
|
104
|
-
${err.message}
|
|
105
|
-
`)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return this.checkClipboardData(clipboardData)
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
updateCopy() {
|
|
113
|
-
this.copyEnabled = this.parentComponent.hasData
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
async updatePaste() {
|
|
117
|
-
this.pasteEnabled = !!this.checkClipboardData(this.appState.clipboardData)
|
|
118
|
-
if (!this.pasteEnabled && this.appState.agent.chrome) {
|
|
119
|
-
// See if the clipboard content is valid JSON data that is compatible
|
|
120
|
-
// with the current target schema, and only then activate the pasting:
|
|
121
|
-
const data = await this.getClipboardData(false) // Don't report
|
|
122
|
-
this.pasteEnabled = !!data
|
|
81
|
+
updateClipboardData() {
|
|
82
|
+
// Only Chrome reads the clipboard without asking for permission, so
|
|
83
|
+
// other browsers only read it when pasting.
|
|
84
|
+
if (this.appState.agent.chrome) {
|
|
85
|
+
// Errors are only reported when pasting.
|
|
86
|
+
readClipboardData().catch(() => {})
|
|
123
87
|
}
|
|
124
88
|
},
|
|
125
89
|
|
|
126
90
|
async onCopy() {
|
|
127
|
-
let data = this.parentComponent.clipboardData
|
|
128
91
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
$schema: this.schema.name,
|
|
132
|
-
...this.copyData(data)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Keep an internal clipboard as fallback.
|
|
136
|
-
this.appState.clipboardData = data
|
|
137
|
-
this.pasteEnabled = true
|
|
138
|
-
try {
|
|
139
|
-
const json = JSON.stringify(data, null, 2)
|
|
140
|
-
await navigator.clipboard?.writeText?.(json)
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error(err, err.name, err.message)
|
|
143
|
-
}
|
|
92
|
+
const data = this.getData()
|
|
93
|
+
await copyClipboardData(this.schema.name, data && this.copyData(data))
|
|
144
94
|
} catch (error) {
|
|
145
|
-
|
|
146
|
-
alert(error.message)
|
|
95
|
+
this.notifyClipboardError(error)
|
|
147
96
|
}
|
|
148
97
|
},
|
|
149
98
|
|
|
150
99
|
async onPaste() {
|
|
151
|
-
let data = await this.getClipboardData(true) // Report
|
|
152
100
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
101
|
+
await readClipboardData()
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.name === 'SyntaxError') {
|
|
104
|
+
this.notifyClipboardError(error, [
|
|
105
|
+
'The data in the clipboard appears to be malformed:',
|
|
106
|
+
error.message
|
|
107
|
+
])
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
// Fall back to the data copied inside the admin.
|
|
111
|
+
console.error(error, error.name, error.message)
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const data = getClipboardData(this.schema.name)
|
|
115
|
+
const pastedData = data && this.pasteData(data)
|
|
116
|
+
if (pastedData) {
|
|
117
|
+
this.$emit('paste', pastedData)
|
|
156
118
|
}
|
|
157
119
|
} catch (error) {
|
|
158
|
-
|
|
159
|
-
alert(error.message)
|
|
120
|
+
this.notifyClipboardError(error)
|
|
160
121
|
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
notifyClipboardError(error, text = error.message) {
|
|
125
|
+
// Notifications log their error too.
|
|
126
|
+
this.notify({ type: 'error', error, title: 'Clipboard Error', text })
|
|
161
127
|
}
|
|
162
128
|
}
|
|
163
129
|
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
p.dito-confirm-message(
|
|
3
|
+
v-html="value"
|
|
4
|
+
)
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
// Displays the HTML message of the dialogs that `confirm()` shows, see
|
|
9
|
+
// `utils/dialogs.js`, as a `{ type: 'component' }` schema, which adds
|
|
10
|
+
// `DitoMixin` and `TypeMixin` to provide `value`.
|
|
11
|
+
export default {}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<style lang="scss">
|
|
15
|
+
.dito-confirm-message {
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
</style>
|