@ditojs/admin 1.12.0 → 1.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "1.12.0",
3
+ "version": "1.13.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",
@@ -17,12 +17,14 @@
17
17
  },
18
18
  "files": [
19
19
  "src/",
20
- "dist/"
20
+ "dist/",
21
+ "types/"
21
22
  ],
22
23
  "scripts": {
23
24
  "build": "vite build",
24
25
  "watch": "yarn build --mode 'development' --watch",
25
- "prepare": "yarn build"
26
+ "prepare": "yarn build",
27
+ "types": "tsc --noEmit types/index.d.ts"
26
28
  },
27
29
  "engines": {
28
30
  "node": ">= 18.0.0",
@@ -35,8 +37,8 @@
35
37
  "not ie_mob > 0"
36
38
  ],
37
39
  "dependencies": {
38
- "@ditojs/ui": "^1.12.0",
39
- "@ditojs/utils": "^1.12.0",
40
+ "@ditojs/ui": "^1.13.1",
41
+ "@ditojs/utils": "^1.13.1",
40
42
  "codeflask": "^1.4.1",
41
43
  "filesize": "^10.0.5",
42
44
  "filesize-parser": "^1.5.0",
@@ -58,11 +60,14 @@
58
60
  "vuedraggable": "^2.24.3"
59
61
  },
60
62
  "devDependencies": {
61
- "@ditojs/build": "^1.12.0",
63
+ "@ditojs/build": "^1.13.0",
62
64
  "pug": "^3.0.2",
63
65
  "sass": "1.55.0",
66
+ "type-fest": "^3.1.0",
67
+ "typescript": "^4.8.4",
64
68
  "vite": "^3.2.2",
65
69
  "vite-plugin-vue2": "^2.0.2"
66
70
  },
67
- "gitHead": "3707045b72d84db4b5174d2efeb8cf62e2065ffa"
71
+ "types": "types",
72
+ "gitHead": "0c6a508e04ca915691c1fb0f3706827c8ecfe0ab"
68
73
  }
package/src/DitoAdmin.js CHANGED
@@ -3,7 +3,9 @@ import VueModal from 'vue-js-modal'
3
3
  import VueRouter from 'vue-router'
4
4
  import VueNotifications from 'vue-notification'
5
5
  import {
6
- isString, isAbsoluteUrl, merge, hyphenate, camelize, defaultFormats
6
+ isString, isArray, asArray, isAbsoluteUrl,
7
+ merge, hyphenate, camelize,
8
+ defaultFormats
7
9
  } from '@ditojs/utils'
8
10
  import * as components from './components/index.js'
9
11
  import * as types from './types/index.js'
@@ -39,8 +41,8 @@ export default class DitoAdmin {
39
41
  api.locale ||= 'en-US'
40
42
  api.formats = merge({}, defaultFormats, api.formats)
41
43
  api.request ||= options => request(api, options)
42
- api.getApiUrl ||= path => getApiUrl(api, path)
43
- api.isApiRequest ||= url => isApiRequest(api, url)
44
+ api.getApiUrl ||= options => getApiUrl(api, options)
45
+ api.isApiUrl ||= url => isApiUrl(api, url)
44
46
  // Setting `api.normalizePaths = true (plural) sets both:
45
47
  // `api.normalizePath = hyphenate` and `api.denormalizePath = camelize`
46
48
  api.normalizePath ||= api.normalizePaths ? hyphenate : val => val
@@ -210,24 +212,16 @@ async function request(api, {
210
212
  deprecate(`request.params is deprecated. Use action.method and action.path instead.`)
211
213
  }
212
214
 
213
- const isApiRequest = api.isApiRequest(url)
214
- if (isApiRequest && !isAbsoluteUrl(url)) {
215
- url = api.getApiUrl(url)
216
- }
217
-
218
- const search = query && new URLSearchParams(query).toString()
219
- if (search) {
220
- url = `${url}?${search}`
221
- }
215
+ const isApiUrl = api.isApiUrl(url)
222
216
 
223
- const response = await fetch(url, {
217
+ const response = await fetch(api.getApiUrl({ url, query }), {
224
218
  method: method.toUpperCase(),
225
219
  ...(data && { body: JSON.stringify(data) }),
226
220
  headers: {
227
- ...(isApiRequest && api.headers),
221
+ ...(isApiUrl && api.headers),
228
222
  ...headers
229
223
  },
230
- credentials: isApiRequest && api.cors?.credentials
224
+ credentials: isApiUrl && api.cors?.credentials
231
225
  ? 'include'
232
226
  : 'same-origin'
233
227
  })
@@ -242,11 +236,41 @@ async function request(api, {
242
236
  return response
243
237
  }
244
238
 
245
- function getApiUrl(api, path) {
246
- // Use same approach as axios `combineURLs()` to join baseURL with path:
247
- return `${api.url.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`
239
+ function isApiUrl(api, url) {
240
+ return !isAbsoluteUrl(url) || url.startsWith(api.url)
248
241
  }
249
242
 
250
- function isApiRequest(api, url) {
251
- return !isAbsoluteUrl(url) || url.startsWith(api.url)
243
+ function getApiUrl(api, { url, query }) {
244
+ if (!isAbsoluteUrl(url)) {
245
+ url = combineUrls(api.url, url)
246
+ }
247
+ // Support optional query parameters, to be are added to the URL.
248
+ const search = formatQuery(query)
249
+ return search ? `${url}?${search}` : url
250
+ }
251
+
252
+ function combineUrls(baseUrl, relativeUrl) {
253
+ // Use same approach as axios `combineURLs()` to join baseUrl & relativeUrl:
254
+ return `${baseUrl.replace(/\/+$/, '')}/${relativeUrl.replace(/^\/+/, '')}`
255
+ }
256
+
257
+ function formatQuery(query) {
258
+ const entries = query
259
+ ? isArray(query)
260
+ ? query
261
+ : Object.entries(query)
262
+ : []
263
+ return new URLSearchParams(
264
+ // Expand array values into multiple entries under the same key, so
265
+ // `formatQuery({ foo: [1, 2], bar: 3 })` => 'foo=1&foo=2&bar=3'.
266
+ entries.reduce(
267
+ (entries, [key, value]) => {
268
+ for (const val of asArray(value)) {
269
+ entries.push([key, val])
270
+ }
271
+ return entries
272
+ },
273
+ []
274
+ )
275
+ ).toString()
252
276
  }
@@ -292,19 +292,14 @@ export default {
292
292
  },
293
293
 
294
294
  getResourceUrl(resource) {
295
- const path = this.getResourcePath(resource)
296
- if (!path) return null
297
- const url = this.api.getApiUrl(path)
298
- // Support optional query parameters, to be are added to the URL.
299
- const { query } = resource
300
- const search = query && new URLSearchParams(query).toString()
301
- return search ? `${url}?${search}` : url
295
+ const url = this.getResourcePath(resource)
296
+ return url ? this.api.getApiUrl({ url, query: resource.query }) : null
302
297
  },
303
298
 
304
299
  async sendRequest({ method, url, resource, query, data, internal }) {
305
300
  url ||= this.getResourcePath(resource)
306
301
  method ||= resource?.method
307
- const checkUser = !internal && this.api.isApiRequest(url)
302
+ const checkUser = !internal && this.api.isApiUrl(url)
308
303
  if (checkUser) {
309
304
  await this.rootComponent.ensureUser()
310
305
  }
@@ -385,13 +380,10 @@ export default {
385
380
  if (isString(options)) {
386
381
  options = { url: options }
387
382
  }
388
- const { url, filename } = options
389
383
  // See: https://stackoverflow.com/a/49917066/1163708
390
384
  const a = document.createElement('a')
391
- a.href = url
392
- if (filename) {
393
- a.download = filename
394
- }
385
+ a.href = this.api.getApiUrl(options)
386
+ a.download = options.filename ?? null
395
387
  const { body } = document
396
388
  body.appendChild(a)
397
389
  a.click()
@@ -135,11 +135,13 @@ export default TypeComponent.register('color', {
135
135
  default: 'hex'
136
136
  }),
137
137
 
138
+ // TODO: Rename to `showAlpha`?
138
139
  alpha: getSchemaAccessor('alpha', {
139
140
  type: Boolean,
140
141
  default: false
141
142
  }),
142
143
 
144
+ // TODO: Rename to `showInputs`?
143
145
  inputs: getSchemaAccessor('inputs', {
144
146
  type: Boolean,
145
147
  default: true
@@ -293,8 +293,8 @@ export default TypeComponent.register('multiselect', {
293
293
  placeholder() {
294
294
  const { placeholder, searchable, taggable } = this.schema
295
295
  return placeholder || (
296
- searchable && taggable ? 'Search or add a tag'
297
- : searchable ? 'Select or search entry'
296
+ searchable && taggable ? `Search or add a ${this.label}`
297
+ : searchable ? `Select or search ${this.label}`
298
298
  : undefined
299
299
  )
300
300
  }
@@ -50,6 +50,7 @@ export default TypeComponent.register('slider', {
50
50
  nativeField: true,
51
51
 
52
52
  computed: {
53
+ // TODO: Rename to `showInput`?
53
54
  input: getSchemaAccessor('input', {
54
55
  type: Boolean,
55
56
  default: true