viur-ui 0.0.1 → 0.0.3
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/README.md +67 -41
- package/index.d.ts +18 -9
- package/index.js +6 -3
- package/package.json +6 -6
- package/src/components/Browser/Browser.vue +58 -58
- package/src/components/Browser/TreeNav.vue +32 -32
- package/src/components/Browser/ViurBrowser.d.vue.ts +21 -21
- package/src/components/Browser/ViurBrowser.vue +47 -44
- package/src/components/Browser/dragSession.ts +27 -27
- package/src/components/Browser/index.ts +3 -3
- package/src/components/Browser/types.ts +9 -9
- package/src/components/DataTable/ColumnFilterPanel.vue +7 -7
- package/src/components/DataTable/DataTable.vue +359 -351
- package/src/components/DataTable/ViurDataTable.d.vue.ts +26 -26
- package/src/components/DataTable/ViurDataTable.vue +51 -50
- package/src/components/DataTable/columns.ts +30 -30
- package/src/components/DataTable/index.ts +12 -12
- package/src/components/DataTable/rows.ts +71 -72
- package/src/components/DataTable/types.ts +55 -55
- package/src/components/DataTable/useColumnLayout.ts +28 -28
- package/src/components/DataTable/useGridNavigation.ts +74 -15
- package/src/components/DataTable/useVirtualRows.ts +10 -10
- package/src/components/DataTable/viurColumns.ts +69 -69
- package/src/forms/ViForm.d.vue.ts +32 -32
- package/src/forms/ViForm.vue +17 -16
- package/src/forms/ViFormCategory.vue +3 -1
- package/src/forms/bones/Bone.vue +125 -108
- package/src/forms/bones/BoneActions.vue +21 -21
- package/src/forms/bones/BoneLabel.vue +1 -1
- package/src/forms/bones/BoneMultipleWrapper.vue +3 -3
- package/src/forms/bones/BoneNestedWrapper.vue +3 -3
- package/src/forms/bones/BoneSingleEntry.vue +2 -2
- package/src/forms/bones/FilterBone.vue +3 -3
- package/src/forms/bones/actions/ActionDialog.vue +2 -2
- package/src/forms/bones/actions/Debug.vue +6 -6
- package/src/forms/bones/actions/Delete.vue +2 -2
- package/src/forms/bones/actions/DescribeImage.vue +4 -3
- package/src/forms/bones/actions/Download.vue +4 -4
- package/src/forms/bones/actions/Placeholder.vue +2 -2
- package/src/forms/bones/actions/index.js +72 -74
- package/src/forms/bones/bone.d.ts +19 -19
- package/src/forms/bones/uploadAdapter.js +3 -3
- package/src/forms/bones/utils.js +40 -41
- package/src/forms/bones/view/base_item.vue +1 -1
- package/src/forms/bones/view/boneLogic.js +1 -1
- package/src/forms/bones/widgets/booleanBone.vue +19 -19
- package/src/forms/bones/widgets/booleanBoneChoose.vue +2 -2
- package/src/forms/bones/widgets/booleanBoneSelect.vue +3 -3
- package/src/forms/bones/widgets/codeBone.vue +22 -22
- package/src/forms/bones/widgets/colorBone.vue +18 -18
- package/src/forms/bones/widgets/dateBone.vue +22 -22
- package/src/forms/bones/widgets/emailBone.vue +20 -20
- package/src/forms/bones/widgets/fileBone.vue +32 -31
- package/src/forms/bones/widgets/keyBone.vue +21 -21
- package/src/forms/bones/widgets/numericBone.vue +21 -21
- package/src/forms/bones/widgets/passwordBone.vue +34 -31
- package/src/forms/bones/widgets/phoneBone.vue +20 -20
- package/src/forms/bones/widgets/rawBone.vue +19 -19
- package/src/forms/bones/widgets/recordBone.vue +16 -16
- package/src/forms/bones/widgets/relationalBone.vue +26 -26
- package/src/forms/bones/widgets/selectBone.vue +23 -23
- package/src/forms/bones/widgets/selectBoneChoose.vue +2 -2
- package/src/forms/bones/widgets/spatialBone.vue +21 -21
- package/src/forms/bones/widgets/stringBone.vue +27 -27
- package/src/forms/bones/widgets/textBone.vue +28 -26
- package/src/forms/install.d.ts +1 -1
- package/src/forms/install.js +7 -0
- package/src/forms/layouts/LayloutList2Columns.vue +1 -1
- package/src/forms/layouts/LayoutCategory.vue +1 -1
- package/src/forms/layouts/LayoutList.vue +1 -1
- package/src/forms/utils.js +22 -22
- package/src/request/configStore.js +4 -4
- package/src/request/envelope.js +6 -6
- package/src/request/request.js +211 -121
- package/src/translations/de.js +31 -0
- package/src/translations/en.js +31 -0
- package/src/user/LoginScreen.d.vue.ts +24 -24
- package/src/user/LoginScreen.vue +6 -7
- package/src/user/store.d.ts +16 -16
- package/src/vite/safeHtml.js +73 -0
- package/src/vite/sanitizeVHtml.d.ts +4 -0
- package/src/vite/sanitizeVHtml.js +15 -0
- package/src/webawesomeCompat.css +0 -40
- package/src/public/assets/icons/fontawesome-manifest.json +0 -8
- package/src/public/assets/icons/regular/file.svg +0 -1
- package/src/public/assets/icons/solid/arrows-left-right.svg +0 -1
- package/src/public/assets/icons/solid/bug.svg +0 -1
- package/src/public/assets/icons/solid/check.svg +0 -1
- package/src/public/assets/icons/solid/circle-info.svg +0 -1
- package/src/public/assets/icons/solid/compress.svg +0 -1
- package/src/public/assets/icons/solid/download.svg +0 -1
- package/src/public/assets/icons/solid/expand.svg +0 -1
- package/src/public/assets/icons/solid/grip-vertical.svg +0 -1
- package/src/public/assets/icons/solid/plus.svg +0 -1
- package/src/public/assets/icons/solid/question.svg +0 -1
- package/src/public/assets/icons/solid/triangle-exclamation.svg +0 -1
- package/src/public/assets/icons/solid/upload.svg +0 -1
- package/src/public/assets/icons/solid/xmark.svg +0 -1
package/src/request/envelope.js
CHANGED
|
@@ -64,7 +64,7 @@ export function v1SelectionFollow(body) {
|
|
|
64
64
|
if (!String(bone?.["type"] ?? "").startsWith("select")) continue
|
|
65
65
|
const chosen = values?.[name]
|
|
66
66
|
if (typeof chosen !== "string" || !/^(\/|https?:\/\/)/.test(chosen)) continue
|
|
67
|
-
//
|
|
67
|
+
// Only if the value actually is one of the offered target URLs.
|
|
68
68
|
if (bone?.["values"] && Object.prototype.hasOwnProperty.call(bone["values"], chosen)) return chosen
|
|
69
69
|
}
|
|
70
70
|
return null
|
|
@@ -190,11 +190,11 @@ export function normalizeEnvelope(data) {
|
|
|
190
190
|
out["action"] = action
|
|
191
191
|
out["status"] = status
|
|
192
192
|
out["errors"] = normalizeErrors(data["errors"])
|
|
193
|
-
//
|
|
194
|
-
// next_url –
|
|
195
|
-
// params.action_url –
|
|
196
|
-
//
|
|
197
|
-
//
|
|
193
|
+
// Target of the step. v1 puts it in different places depending on the stage:
|
|
194
|
+
// next_url – set by the server (e.g. choice of the second factor)
|
|
195
|
+
// params.action_url – for a rendered step that brings its own target
|
|
196
|
+
// (viur-core: the OTP step of the second factor
|
|
197
|
+
// comes as `edit` with action_url)
|
|
198
198
|
out["follow"] = data["follow"] ?? data["next_url"] ?? data["params"]?.["action_url"] ?? null
|
|
199
199
|
out["structure"] = data["structure"] ?? null
|
|
200
200
|
|
package/src/request/request.js
CHANGED
|
@@ -62,31 +62,43 @@ let _skeyPending = null
|
|
|
62
62
|
|
|
63
63
|
async function _refillSkeys(renderer) {
|
|
64
64
|
const base = import.meta.env.VITE_API_URL ?? window.location.origin
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
try {
|
|
66
|
+
let response = await fetch(`${base}/${renderer}/skey?amount=${SKEY_BATCH_SIZE}`, { credentials: "include" })
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
response = await fetch(`${base}/${renderer}/skey`, { credentials: "include" })
|
|
69
|
+
if (!response.ok) throw new Error("Failed to fetch skey token")
|
|
70
|
+
}
|
|
71
|
+
let data = await response.json()
|
|
72
|
+
if (!Array.isArray(data)) data = [data]
|
|
73
|
+
_skeyCache.push(...data)
|
|
74
|
+
} finally {
|
|
75
|
+
// also on failure: otherwise every later caller awaits the same rejected promise
|
|
76
|
+
_skeyPending = null
|
|
69
77
|
}
|
|
70
|
-
let data = await response.json()
|
|
71
|
-
if (!Array.isArray(data)) data = [data]
|
|
72
|
-
_skeyCache.push(...data)
|
|
73
|
-
_skeyPending = null
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
async function fetchSkeyToken(renderer) {
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
81
|
+
try {
|
|
82
|
+
if (_skeyCache.length === 0) {
|
|
83
|
+
if (!_skeyPending) _skeyPending = _refillSkeys(renderer)
|
|
84
|
+
await _skeyPending
|
|
85
|
+
}
|
|
86
|
+
return _skeyCache.shift()
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw error
|
|
80
89
|
}
|
|
81
|
-
return _skeyCache.shift()
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
async function executeAndThrow(fetchResult, executeImpl) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
try {
|
|
94
|
+
await executeImpl()
|
|
95
|
+
if (fetchResult.error.value) {
|
|
96
|
+
throw fetchResult.error.value
|
|
97
|
+
}
|
|
98
|
+
return fetchResult
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw error
|
|
88
101
|
}
|
|
89
|
-
return fetchResult
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
@@ -115,6 +127,11 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
115
127
|
const orders = ref([])
|
|
116
128
|
const params = ref({ ...initialParams })
|
|
117
129
|
let _isNext = false
|
|
130
|
+
// Bumped by execute()/reset(). A request remembers the generation it started
|
|
131
|
+
// in; once a newer one exists it must not write anymore. Aborting alone is
|
|
132
|
+
// not enough: after the list response arrived, afterFetch still awaits the
|
|
133
|
+
// structure round trip and would then write into the already cleared state.
|
|
134
|
+
let _generation = 0
|
|
118
135
|
|
|
119
136
|
function _buildUrl() {
|
|
120
137
|
const base = url ?? create_url({ controller, endpoint: group ? `list/${group}` : "list", renderer, version })
|
|
@@ -126,16 +143,16 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
126
143
|
}
|
|
127
144
|
|
|
128
145
|
/**
|
|
129
|
-
*
|
|
146
|
+
* Fetch the structure of the list.
|
|
130
147
|
*
|
|
131
|
-
*
|
|
132
|
-
* •
|
|
148
|
+
* Two response shapes are in circulation:
|
|
149
|
+
* • current: `/<modul>/structure` renders like any other action, i.e.
|
|
133
150
|
* `{ action: "structure.view", values, structure }`
|
|
134
|
-
* •
|
|
135
|
-
*
|
|
136
|
-
* (`json.bone.structure.keytuples`) — normalizeStructure
|
|
151
|
+
* • historic (getStructure): `{ viewSkel: {…}, editSkel: {…} }`
|
|
152
|
+
* Additionally, the structure can be a PAIR LIST
|
|
153
|
+
* (`json.bone.structure.keytuples`) — normalizeStructure flattens that.
|
|
137
154
|
*/
|
|
138
|
-
async function _fetchStructure() {
|
|
155
|
+
async function _fetchStructure(generation) {
|
|
139
156
|
const skeltype =
|
|
140
157
|
params.value.skelType === "node"
|
|
141
158
|
? "viewNodeSkel"
|
|
@@ -146,11 +163,15 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
146
163
|
if (params.value.skelType) {
|
|
147
164
|
url += `${url.includes("?") ? "&" : "?"}skelType=${encodeURIComponent(params.value.skelType)}`
|
|
148
165
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
try {
|
|
167
|
+
const resp = await fetch(url, { credentials: "include", headers: { Accept: "application/json" } })
|
|
168
|
+
if (!resp.ok) return
|
|
169
|
+
const body = normalizeEnvelope(await resp.json())
|
|
170
|
+
const raw = body?.["structure"] ?? body?.[skeltype]
|
|
171
|
+
if (raw && generation === _generation) structure.value = normalizeStructure(raw)
|
|
172
|
+
} catch {
|
|
173
|
+
// the structure is optional, the list stays usable without it
|
|
174
|
+
}
|
|
154
175
|
}
|
|
155
176
|
|
|
156
177
|
const listUrl = ref(_buildUrl())
|
|
@@ -166,7 +187,9 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
166
187
|
const extraHeaders = typeof headers === "function" ? headers() : headers
|
|
167
188
|
if (extraHeaders) options.headers = { ...options.headers, ...extraHeaders }
|
|
168
189
|
if (cached && !_isNext) {
|
|
169
|
-
const hit = await getCached(url, ttl)
|
|
190
|
+
const hit = await getCached(url, ttl).catch((error) => {
|
|
191
|
+
throw error
|
|
192
|
+
})
|
|
170
193
|
if (hit) {
|
|
171
194
|
data.value = hit.data ?? hit.skellist ?? hit
|
|
172
195
|
cursor.value = hit.meta?.cursor ?? hit.cursor ?? ""
|
|
@@ -175,22 +198,34 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
175
198
|
return { options, url }
|
|
176
199
|
}
|
|
177
200
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
const generation = _generation
|
|
202
|
+
if (!secure) return { options, url, generation }
|
|
203
|
+
try {
|
|
204
|
+
const skey = await fetchSkeyToken(renderer)
|
|
205
|
+
const separator = url.includes("?") ? "&" : "?"
|
|
206
|
+
return { url: `${url}${separator}skey=${skey}`, options, generation }
|
|
207
|
+
} catch (error) {
|
|
208
|
+
throw error
|
|
209
|
+
}
|
|
182
210
|
},
|
|
183
211
|
async afterFetch(ctx) {
|
|
184
212
|
// Normalize v1/v2 up to the canonical v2 envelope, then read the list
|
|
185
213
|
// payload from `data` and pagination from `meta`.
|
|
214
|
+
const { generation } = ctx.context
|
|
215
|
+
if (generation !== _generation) return ctx
|
|
186
216
|
const responseData = normalizeEnvelope(ctx.data)
|
|
187
217
|
if (!_isNext) {
|
|
188
|
-
|
|
218
|
+
try {
|
|
219
|
+
await _fetchStructure(generation)
|
|
220
|
+
} catch (error) {
|
|
221
|
+
throw error
|
|
222
|
+
}
|
|
223
|
+
if (generation !== _generation) return ctx
|
|
189
224
|
}
|
|
190
225
|
const list = responseData.data ?? []
|
|
191
|
-
//
|
|
192
|
-
// (
|
|
193
|
-
// separate
|
|
226
|
+
// Some installations deliver the structure along with the list
|
|
227
|
+
// (compatibility flag `json.bone.structure.inlists`) — then the
|
|
228
|
+
// separate call above was in vain, but this one is more current.
|
|
194
229
|
if (responseData.structure) {
|
|
195
230
|
structure.value = normalizeStructure(responseData.structure)
|
|
196
231
|
}
|
|
@@ -199,12 +234,19 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
199
234
|
data.value = _isNext ? data.value.concat(list) : list
|
|
200
235
|
if (cached && !_isNext) {
|
|
201
236
|
const entityKeys = list.map((s) => s.key).filter(Boolean)
|
|
202
|
-
await setCached(ctx.url ?? listUrl.value, responseData, entityKeys)
|
|
237
|
+
await setCached(ctx.url ?? listUrl.value, responseData, entityKeys).catch((error) => {
|
|
238
|
+
throw error
|
|
239
|
+
})
|
|
203
240
|
}
|
|
204
241
|
ctx.data = data.value
|
|
205
242
|
return ctx
|
|
206
243
|
},
|
|
207
244
|
onFetchError(ctx) {
|
|
245
|
+
// A superseded or aborted request is no failure: keep the current
|
|
246
|
+
// error as it is (null for the run that replaced this one).
|
|
247
|
+
if (ctx.context.generation !== _generation || ctx.error?.name === "AbortError") {
|
|
248
|
+
return { ...ctx, error: error.value }
|
|
249
|
+
}
|
|
208
250
|
ctx.error = new HTTPError(ctx.response?.status, ctx.response?.statusText, ctx.error?.message, ctx.response)
|
|
209
251
|
if (messages) {
|
|
210
252
|
notify(
|
|
@@ -218,8 +260,8 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
218
260
|
|
|
219
261
|
const { isFetching, isFinished, error, execute: _execute, abort } = useViurFetch(listUrl, { immediate }).json()
|
|
220
262
|
|
|
221
|
-
/**
|
|
222
|
-
*
|
|
263
|
+
/** Everything loaded: last response is in and no cursor left open. From here
|
|
264
|
+
* on, sorting/filtering may run locally on the dataset. */
|
|
223
265
|
const complete = computed(() => isFinished.value && !cursor.value)
|
|
224
266
|
|
|
225
267
|
const fetchResult = {
|
|
@@ -245,11 +287,16 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
245
287
|
params.value = { ...params.value, cursor: cursor.value }
|
|
246
288
|
_isNext = true
|
|
247
289
|
listUrl.value = _buildUrl()
|
|
248
|
-
|
|
290
|
+
try {
|
|
291
|
+
await executeAndThrow(fetchResult, _execute)
|
|
292
|
+
} catch {
|
|
293
|
+
// already in `error`; rethrowing here would only be an unhandled rejection
|
|
294
|
+
}
|
|
249
295
|
}
|
|
250
296
|
})
|
|
251
297
|
|
|
252
298
|
function execute() {
|
|
299
|
+
_generation++
|
|
253
300
|
_isNext = false
|
|
254
301
|
abort()
|
|
255
302
|
data.value = []
|
|
@@ -262,37 +309,49 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
262
309
|
return executeAndThrow(fetchResult, _execute)
|
|
263
310
|
}
|
|
264
311
|
|
|
265
|
-
/**
|
|
266
|
-
*
|
|
312
|
+
/** Load one more page; detects the ViUR end (empty page with the same
|
|
313
|
+
* cursor marker) and then clears the cursor. Returns true on progress. */
|
|
267
314
|
async function _advance() {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
cursor.value
|
|
277
|
-
|
|
315
|
+
try {
|
|
316
|
+
if (!cursor.value) return false
|
|
317
|
+
const before = data.value.length
|
|
318
|
+
const cursorBefore = cursor.value
|
|
319
|
+
params.value = { ...params.value, cursor: cursor.value }
|
|
320
|
+
_isNext = true
|
|
321
|
+
listUrl.value = _buildUrl()
|
|
322
|
+
await executeAndThrow(fetchResult, _execute)
|
|
323
|
+
if (data.value.length === before && cursor.value === cursorBefore) {
|
|
324
|
+
cursor.value = ""
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
return true
|
|
328
|
+
} catch (error) {
|
|
329
|
+
throw error
|
|
278
330
|
}
|
|
279
|
-
return true
|
|
280
331
|
}
|
|
281
332
|
|
|
282
|
-
/**
|
|
283
|
-
*
|
|
333
|
+
/** Load the next `pages` pages (default: one) — e.g. directly as the
|
|
334
|
+
* handler of the DataTable cursor bar: `@load-more="({ pages }) =>
|
|
284
335
|
* pages === 'all' ? list.nextAll() : list.next(pages)"`. */
|
|
285
336
|
async function next(pages = 1) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
337
|
+
try {
|
|
338
|
+
let remaining = pages
|
|
339
|
+
while (remaining-- > 0) {
|
|
340
|
+
if (!(await _advance())) return
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
throw error
|
|
289
344
|
}
|
|
290
345
|
}
|
|
291
346
|
|
|
292
|
-
/**
|
|
347
|
+
/** Load all remaining pages ("load everything"). */
|
|
293
348
|
async function nextAll() {
|
|
294
|
-
|
|
295
|
-
|
|
349
|
+
try {
|
|
350
|
+
while (await _advance()) {
|
|
351
|
+
/* until the end */
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
throw error
|
|
296
355
|
}
|
|
297
356
|
}
|
|
298
357
|
|
|
@@ -302,6 +361,7 @@ function createControllerListFetch({ all = false, secure = false } = {}) {
|
|
|
302
361
|
}
|
|
303
362
|
|
|
304
363
|
function reset() {
|
|
364
|
+
_generation++
|
|
305
365
|
abort()
|
|
306
366
|
data.value = []
|
|
307
367
|
structure.value = {}
|
|
@@ -352,7 +412,7 @@ export function useTreeFetch(
|
|
|
352
412
|
const isFinished = computed(() => nodes.isFinished.value && leaves.isFinished.value)
|
|
353
413
|
const error = computed(() => nodes.error.value ?? leaves.error.value)
|
|
354
414
|
const hasMore = computed(() => !!(nodes.cursor.value || leaves.cursor.value))
|
|
355
|
-
/**
|
|
415
|
+
/** Both lists fully loaded — sorting/filtering may run locally. */
|
|
356
416
|
const complete = computed(() => isFinished.value && !hasMore.value)
|
|
357
417
|
|
|
358
418
|
function _both(fn) {
|
|
@@ -363,27 +423,39 @@ export function useTreeFetch(
|
|
|
363
423
|
return _both((l) => l.execute())
|
|
364
424
|
}
|
|
365
425
|
|
|
366
|
-
//
|
|
367
|
-
// (
|
|
368
|
-
//
|
|
426
|
+
// Load one more page: nodes to the end first, then leaves. useListFetch.next()
|
|
427
|
+
// detects the ViUR end (empty page with the same cursor marker) itself and
|
|
428
|
+
// then clears its cursor — only progress counts here.
|
|
369
429
|
async function _nextOf(list) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
430
|
+
try {
|
|
431
|
+
const before = list.data.value.length
|
|
432
|
+
await list.next()
|
|
433
|
+
return list.data.value.length > before
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw error
|
|
436
|
+
}
|
|
373
437
|
}
|
|
374
438
|
|
|
375
439
|
async function next() {
|
|
376
|
-
|
|
377
|
-
|
|
440
|
+
try {
|
|
441
|
+
if (nodes.cursor.value && (await _nextOf(nodes))) return
|
|
442
|
+
if (leaves.cursor.value) await _nextOf(leaves)
|
|
443
|
+
} catch (error) {
|
|
444
|
+
throw error
|
|
445
|
+
}
|
|
378
446
|
}
|
|
379
447
|
|
|
380
|
-
/**
|
|
448
|
+
/** Load all remaining pages of both lists ("load everything"). */
|
|
381
449
|
async function nextAll() {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
450
|
+
try {
|
|
451
|
+
while (nodes.cursor.value) {
|
|
452
|
+
if (!(await _nextOf(nodes))) break
|
|
453
|
+
}
|
|
454
|
+
while (leaves.cursor.value) {
|
|
455
|
+
if (!(await _nextOf(leaves))) break
|
|
456
|
+
}
|
|
457
|
+
} catch (error) {
|
|
458
|
+
throw error
|
|
387
459
|
}
|
|
388
460
|
}
|
|
389
461
|
|
|
@@ -459,7 +531,12 @@ export function createControllerFetch({
|
|
|
459
531
|
},
|
|
460
532
|
options: {
|
|
461
533
|
async beforeFetch({ options, url, cancel }) {
|
|
462
|
-
|
|
534
|
+
let skeyToken = null
|
|
535
|
+
try {
|
|
536
|
+
skeyToken = secure ? ctxSkey || (await fetchSkeyToken(renderer)) : null
|
|
537
|
+
} catch (error) {
|
|
538
|
+
throw error
|
|
539
|
+
}
|
|
463
540
|
const base = create_url({ controller, endpoint, renderer, version, lang })
|
|
464
541
|
const extraQs = query ? (typeof query === "string" ? query : new URLSearchParams(query).toString()) : ""
|
|
465
542
|
|
|
@@ -473,7 +550,9 @@ export function createControllerFetch({
|
|
|
473
550
|
_cacheKey = qs ? `${base}?${qs}` : base
|
|
474
551
|
|
|
475
552
|
if (cached) {
|
|
476
|
-
const hit = await getCached(_cacheKey, ttl)
|
|
553
|
+
const hit = await getCached(_cacheKey, ttl).catch((error) => {
|
|
554
|
+
throw error
|
|
555
|
+
})
|
|
477
556
|
if (hit) {
|
|
478
557
|
_cachedHit = hit
|
|
479
558
|
cancel()
|
|
@@ -525,18 +604,25 @@ export function createControllerFetch({
|
|
|
525
604
|
}
|
|
526
605
|
if (cached && method.toUpperCase() === "GET" && _cacheKey) {
|
|
527
606
|
const entityKey = ctx.data?.data?.key
|
|
528
|
-
await setCached(_cacheKey, ctx.data, entityKey ? [entityKey] : [])
|
|
607
|
+
await setCached(_cacheKey, ctx.data, entityKey ? [entityKey] : []).catch((error) => {
|
|
608
|
+
throw error
|
|
609
|
+
})
|
|
529
610
|
}
|
|
530
611
|
if (invalidates) {
|
|
531
612
|
const keys = []
|
|
532
613
|
if (data.key) keys.push(data.key)
|
|
533
614
|
if (ctx.data?.data?.key) keys.push(ctx.data.data.key)
|
|
534
|
-
if (keys.length)
|
|
615
|
+
if (keys.length)
|
|
616
|
+
await invalidateByKey(keys).catch((error) => {
|
|
617
|
+
throw error
|
|
618
|
+
})
|
|
535
619
|
}
|
|
536
620
|
if (invalidatesAll) {
|
|
537
621
|
const base = import.meta.env.VITE_API_URL ?? window.location.origin
|
|
538
622
|
const controllerPath = Array.isArray(controller) ? controller.join("/") : controller
|
|
539
|
-
await clearCache(`${base}/${renderer}/${controllerPath}/`)
|
|
623
|
+
await clearCache(`${base}/${renderer}/${controllerPath}/`).catch((error) => {
|
|
624
|
+
throw error
|
|
625
|
+
})
|
|
540
626
|
}
|
|
541
627
|
return ctx
|
|
542
628
|
},
|
|
@@ -586,47 +672,51 @@ export async function uploadFile(
|
|
|
586
672
|
file,
|
|
587
673
|
{ target = undefined, publicUpload = false, renderer = null, version = null } = {}
|
|
588
674
|
) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
675
|
+
try {
|
|
676
|
+
const _renderer = renderer ?? import.meta.env.VITE_DEFAULT_RENDERER ?? "json"
|
|
677
|
+
const fetchOpts = { credentials: "include", headers: { Accept: "application/json" }, method: "POST" }
|
|
678
|
+
|
|
679
|
+
// Step 1: get upload URL
|
|
680
|
+
const filedata = {
|
|
681
|
+
fileName: file.name,
|
|
682
|
+
mimeType: file.type || "application/octet-stream",
|
|
683
|
+
size: file.size.toString(),
|
|
684
|
+
}
|
|
685
|
+
if (target) filedata.node = target
|
|
686
|
+
if (publicUpload) filedata.public = true
|
|
687
|
+
const fd1 = _toFormData(filedata)
|
|
688
|
+
fd1.append("skey", await fetchSkeyToken(_renderer))
|
|
689
|
+
|
|
690
|
+
const getUrlResp = await fetch(
|
|
691
|
+
create_url({ controller: "file", endpoint: "getUploadURL", renderer: _renderer, version }),
|
|
692
|
+
{
|
|
693
|
+
...fetchOpts,
|
|
694
|
+
body: fd1,
|
|
695
|
+
}
|
|
696
|
+
)
|
|
697
|
+
if (!getUrlResp.ok) throw new Error(`getUploadURL failed: ${getUrlResp.status}`)
|
|
698
|
+
// Envelope-agnostic: v2 carries the payload in `data`, v1 in `values`.
|
|
699
|
+
const getUrlData = normalizeEnvelope(await getUrlResp.json())
|
|
700
|
+
const { uploadUrl, uploadKey } = getUrlData.data ?? getUrlData.values ?? {}
|
|
602
701
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
702
|
+
// Step 2: upload raw bytes to external storage (no-cors, no credentials)
|
|
703
|
+
await fetch(uploadUrl, { method: "POST", body: file, mode: "no-cors" })
|
|
704
|
+
|
|
705
|
+
// Step 3: register file in ViUR
|
|
706
|
+
const fd3 = _toFormData({ key: uploadKey, skelType: "leaf" })
|
|
707
|
+
fd3.append("skey", await fetchSkeyToken(_renderer))
|
|
708
|
+
|
|
709
|
+
const addResp = await fetch(create_url({ controller: "file", endpoint: "add", renderer: _renderer, version }), {
|
|
606
710
|
...fetchOpts,
|
|
607
|
-
body:
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
await fetch(uploadUrl, { method: "POST", body: file, mode: "no-cors" })
|
|
617
|
-
|
|
618
|
-
// Step 3: register file in ViUR
|
|
619
|
-
const fd3 = _toFormData({ key: uploadKey, skelType: "leaf" })
|
|
620
|
-
fd3.append("skey", await fetchSkeyToken(_renderer))
|
|
621
|
-
|
|
622
|
-
const addResp = await fetch(create_url({ controller: "file", endpoint: "add", renderer: _renderer, version }), {
|
|
623
|
-
...fetchOpts,
|
|
624
|
-
body: fd3,
|
|
625
|
-
})
|
|
626
|
-
if (!addResp.ok) throw new Error(`file/add failed: ${addResp.status}`)
|
|
627
|
-
const addData = normalizeEnvelope(await addResp.json())
|
|
628
|
-
if (addData.status !== "success") throw new Error(`Upload failed: ${JSON.stringify(addData)}`)
|
|
629
|
-
return addData.data ?? addData.values
|
|
711
|
+
body: fd3,
|
|
712
|
+
})
|
|
713
|
+
if (!addResp.ok) throw new Error(`file/add failed: ${addResp.status}`)
|
|
714
|
+
const addData = normalizeEnvelope(await addResp.json())
|
|
715
|
+
if (addData.status !== "success") throw new Error(`Upload failed: ${JSON.stringify(addData)}`)
|
|
716
|
+
return addData.data ?? addData.values
|
|
717
|
+
} catch (error) {
|
|
718
|
+
throw error
|
|
719
|
+
}
|
|
630
720
|
}
|
|
631
721
|
|
|
632
722
|
function _absolute(url) {
|
package/src/translations/de.js
CHANGED
|
@@ -12,6 +12,7 @@ export default {
|
|
|
12
12
|
list: "Auflisten",
|
|
13
13
|
addEmpty: "Neuer Eintrag",
|
|
14
14
|
no_entries: "Keine Einträge",
|
|
15
|
+
errors_summary: "Fehler",
|
|
15
16
|
},
|
|
16
17
|
lang: "Sprache",
|
|
17
18
|
bones: {
|
|
@@ -27,6 +28,13 @@ export default {
|
|
|
27
28
|
phone: {
|
|
28
29
|
placeholder: "Telefon/Fax",
|
|
29
30
|
},
|
|
31
|
+
password: {
|
|
32
|
+
mismatch: "Die eingegebenen Passwörter stimmen nicht überein.",
|
|
33
|
+
empty: "Das eingegebene Passwort ist leer.",
|
|
34
|
+
},
|
|
35
|
+
text: {
|
|
36
|
+
openInNewWindow: "Link in neuem Fenster öffnen",
|
|
37
|
+
},
|
|
30
38
|
},
|
|
31
39
|
|
|
32
40
|
errors: {
|
|
@@ -77,6 +85,10 @@ export default {
|
|
|
77
85
|
collapseRow: "Zeile einklappen",
|
|
78
86
|
expandRow: "Zeile ausklappen",
|
|
79
87
|
noData: "Keine Daten",
|
|
88
|
+
noValues: "Keine Werte",
|
|
89
|
+
clear: "Leeren",
|
|
90
|
+
filterEquals: "genau …",
|
|
91
|
+
filterContains: "enthält …",
|
|
80
92
|
noResults: "Keine Treffer",
|
|
81
93
|
filterFrom: "von",
|
|
82
94
|
filterTo: "bis",
|
|
@@ -108,6 +120,7 @@ export default {
|
|
|
108
120
|
path: "Pfad",
|
|
109
121
|
navigation: "Navigation",
|
|
110
122
|
uploadHint: "Dateien hier ablegen",
|
|
123
|
+
name: "Name",
|
|
111
124
|
},
|
|
112
125
|
form: {
|
|
113
126
|
next: "Weiter",
|
|
@@ -122,6 +135,7 @@ export default {
|
|
|
122
135
|
original: "Original Sprache ({lang}):",
|
|
123
136
|
target: "Zielsprache ({lang}):",
|
|
124
137
|
run: "Übersetzen",
|
|
138
|
+
name: "Übersetzen",
|
|
125
139
|
applyNext: "Übernehmen und nächste leere Sprache",
|
|
126
140
|
apply: "Ziel für {lang} übernehmen",
|
|
127
141
|
},
|
|
@@ -130,6 +144,23 @@ export default {
|
|
|
130
144
|
instructions: "weitere Anweisungen",
|
|
131
145
|
run: "Beschreibung erstellen",
|
|
132
146
|
apply: "Beschreibung übernehmen",
|
|
147
|
+
name: "Bildbeschreibung erstellen",
|
|
148
|
+
title: "Bild beschreiben",
|
|
149
|
+
},
|
|
150
|
+
debug: {
|
|
151
|
+
name: "Debug",
|
|
152
|
+
title: "Bone Debug-Informationen anzeigen",
|
|
153
|
+
structure: "Bone-Struktur",
|
|
154
|
+
value: "Bone-Wert",
|
|
155
|
+
state: "Bone-Status",
|
|
156
|
+
context: "Kontext",
|
|
157
|
+
},
|
|
158
|
+
placeholder: {
|
|
159
|
+
name: "Platzhalter",
|
|
160
|
+
title: "Platzhalter (TODO: entfernen)",
|
|
161
|
+
},
|
|
162
|
+
delete: {
|
|
163
|
+
name: "Löschen",
|
|
133
164
|
},
|
|
134
165
|
},
|
|
135
166
|
messages: {
|