@riducms/admin 0.3.0 → 0.5.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 +10 -8
- package/src/core/forms/form-schema.ts +61 -14
- package/src/core/runtime/admin-runtime.svelte.ts +11 -12
- package/src/features/account/account-route.svelte +1 -1
- package/src/features/account/account-security-controller.svelte.ts +9 -7
- package/src/features/account/account-security-route.svelte +1 -1
- package/src/features/api-reference/api-reference-code.svelte +82 -32
- package/src/features/api-reference/api-reference-examples.ts +1 -1
- package/src/features/auth/create-first-user-controller.svelte.ts +11 -10
- package/src/features/auth/forgot-password-route.svelte +1 -1
- package/src/features/auth/login-route.svelte +2 -2
- package/src/features/auth/logout-button.svelte +1 -1
- package/src/features/auth/request-verification-route.svelte +1 -1
- package/src/features/auth/reset-password-route.svelte +6 -3
- package/src/features/auth/verify-email-route.svelte +6 -3
- package/src/features/documents/document-controller.svelte.ts +17 -8
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +10 -5
- package/src/fields/select/select-field.svelte +28 -12
package/package.json
CHANGED
|
@@ -21,19 +21,21 @@
|
|
|
21
21
|
"@iconify/json": "^2.2.509",
|
|
22
22
|
"@internationalized/date": "^3.12.3",
|
|
23
23
|
"@lezer/highlight": "^1.2.3",
|
|
24
|
-
"@riducms/plugin": "0.
|
|
25
|
-
"@riducms/protocol": "0.
|
|
26
|
-
"@riducms/sdk": "0.
|
|
27
|
-
"@riducms/translations": "0.
|
|
28
|
-
"@riducms/ui": "0.
|
|
24
|
+
"@riducms/plugin": "0.5.0",
|
|
25
|
+
"@riducms/protocol": "0.5.0",
|
|
26
|
+
"@riducms/sdk": "0.5.0",
|
|
27
|
+
"@riducms/translations": "0.5.0",
|
|
28
|
+
"@riducms/ui": "0.5.0",
|
|
29
|
+
"@twinkleplop/bash": "0.1.5",
|
|
30
|
+
"@twinkleplop/go": "0.1.5",
|
|
31
|
+
"@twinkleplop/typescript": "0.1.5",
|
|
29
32
|
"bits-ui": "^2.18.1",
|
|
30
|
-
"shiki": "4.4.3",
|
|
31
33
|
"svelte-sonner": "^1.1.1"
|
|
32
34
|
},
|
|
33
35
|
"description": "Framework-owned Svelte 5 authoring application embedded by Ridu servers.",
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@hvniel/lexical-svelte": "^0.1.1",
|
|
36
|
-
"@riducms/build": "0.
|
|
38
|
+
"@riducms/build": "0.5.0",
|
|
37
39
|
"svelte": "^5.57.0"
|
|
38
40
|
},
|
|
39
41
|
"exports": {
|
|
@@ -72,5 +74,5 @@
|
|
|
72
74
|
"test:unit": "bun test tests"
|
|
73
75
|
},
|
|
74
76
|
"type": "module",
|
|
75
|
-
"version": "0.
|
|
77
|
+
"version": "0.5.0"
|
|
76
78
|
}
|
|
@@ -66,6 +66,16 @@ export function submissionFormValues(
|
|
|
66
66
|
return submissionRecord(fields, input, "", include);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** Project an update onto caller edits, retaining row membership and stable identities. */
|
|
70
|
+
export function changedFormValues(
|
|
71
|
+
fields: readonly SchemaField[],
|
|
72
|
+
input: FormValues,
|
|
73
|
+
original: FormValues,
|
|
74
|
+
isInherited?: (path: string) => boolean
|
|
75
|
+
): FormValues {
|
|
76
|
+
return submissionRecord(fields, input, "", () => true, original, isInherited);
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/** Initialize a new record, retaining supplied values and container metadata. */
|
|
70
80
|
export function initialFormValues(
|
|
71
81
|
fields: readonly SchemaField[],
|
|
@@ -100,9 +110,12 @@ function submissionRecord(
|
|
|
100
110
|
fields: readonly SchemaField[],
|
|
101
111
|
input: unknown,
|
|
102
112
|
prefix: string,
|
|
103
|
-
include: (path: string, canonicalPath: string) => boolean
|
|
113
|
+
include: (path: string, canonicalPath: string) => boolean,
|
|
114
|
+
original?: unknown,
|
|
115
|
+
isInherited?: (path: string) => boolean
|
|
104
116
|
): FormValues {
|
|
105
117
|
const values = isRecord(input) ? input : {};
|
|
118
|
+
const before = isRecord(original) ? original : {};
|
|
106
119
|
const submitted: FormValues = {};
|
|
107
120
|
for (const field of fields) {
|
|
108
121
|
const path = joinFormPath(prefix, field.name);
|
|
@@ -113,23 +126,47 @@ function submissionRecord(
|
|
|
113
126
|
)
|
|
114
127
|
continue;
|
|
115
128
|
const value = values[field.name];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
const displayedPrevious = before[field.name];
|
|
130
|
+
if (Object.hasOwn(before, field.name) && deepEqual(value, displayedPrevious)) continue;
|
|
131
|
+
// An edited fallback establishes a new locale value, without retained children to merge.
|
|
132
|
+
const previous = isInherited?.(path) === true ? undefined : displayedPrevious;
|
|
133
|
+
|
|
134
|
+
if (field.type === "group" && isRecord(value)) {
|
|
135
|
+
submitted[field.name] = submissionRecord(
|
|
136
|
+
field.nested?.fields ?? [],
|
|
137
|
+
value,
|
|
138
|
+
path,
|
|
139
|
+
include,
|
|
140
|
+
previous,
|
|
141
|
+
isInherited
|
|
121
142
|
);
|
|
122
|
-
} else if (field.type === "blocks" && Array.isArray(value)) {
|
|
143
|
+
} else if ((field.type === "array" || field.type === "blocks") && Array.isArray(value)) {
|
|
144
|
+
const previousRows = new Map(
|
|
145
|
+
(Array.isArray(previous) ? previous : []).flatMap((row) =>
|
|
146
|
+
isRecord(row) && typeof row._key === "string" ? [[row._key, row] as const] : []
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
const blocks = resolveBlockTypes(field.blocks);
|
|
123
150
|
submitted[field.name] = value.map((row, index) => {
|
|
124
|
-
if (!isRecord(row)
|
|
125
|
-
const
|
|
126
|
-
|
|
151
|
+
if (!isRecord(row)) return cloneFormValue(row);
|
|
152
|
+
const children =
|
|
153
|
+
field.type === "array"
|
|
154
|
+
? (field.nested?.fields ?? [])
|
|
155
|
+
: blocks.find((block) => block.slug === row.blockType)?.fields;
|
|
156
|
+
if (children === undefined) return cloneFormValue(row);
|
|
157
|
+
|
|
158
|
+
const prior = typeof row._key === "string" ? previousRows.get(row._key) : undefined;
|
|
159
|
+
return submissionRecord(
|
|
160
|
+
children,
|
|
161
|
+
row,
|
|
162
|
+
`${path}.${index}`,
|
|
163
|
+
include,
|
|
164
|
+
field.type === "blocks" && prior?.blockType !== row.blockType ? undefined : prior,
|
|
165
|
+
isInherited
|
|
127
166
|
);
|
|
128
|
-
return block === undefined
|
|
129
|
-
? cloneFormValue(row)
|
|
130
|
-
: submissionRecord(block.fields, row, `${path}.${index}`, include);
|
|
131
167
|
});
|
|
132
168
|
} else if (field.type === "plugin" && field.plugin?.embeddedTrees !== undefined) {
|
|
169
|
+
const priorOccurrences = embeddedOccurrences(field, previous, path).occurrences;
|
|
133
170
|
submitted[field.name] = transformEmbeddedPayloads(
|
|
134
171
|
field,
|
|
135
172
|
cloneFormValue(value),
|
|
@@ -139,7 +176,17 @@ function submissionRecord(
|
|
|
139
176
|
occurrence.block.fields,
|
|
140
177
|
occurrence.payload,
|
|
141
178
|
occurrence.path,
|
|
142
|
-
include
|
|
179
|
+
include,
|
|
180
|
+
occurrence.identity === undefined
|
|
181
|
+
? undefined
|
|
182
|
+
: priorOccurrences.find(
|
|
183
|
+
(prior) =>
|
|
184
|
+
prior.tree.key === occurrence.tree.key &&
|
|
185
|
+
prior.case.tagValue === occurrence.case.tagValue &&
|
|
186
|
+
prior.block.slug === occurrence.block.slug &&
|
|
187
|
+
prior.identity === occurrence.identity
|
|
188
|
+
)?.payload,
|
|
189
|
+
isInherited
|
|
143
190
|
),
|
|
144
191
|
[occurrence.case.discriminator]: occurrence.block.slug,
|
|
145
192
|
...(occurrence.identity === undefined
|
|
@@ -455,7 +455,7 @@ export class AdminRuntime {
|
|
|
455
455
|
const accessAuthoritative = await this.refreshAccess(this.contentLocale);
|
|
456
456
|
if (!accessAuthoritative || this.session === undefined || this.adminAccessAllowed) return;
|
|
457
457
|
try {
|
|
458
|
-
await this.client.logout();
|
|
458
|
+
await this.client.auth.logout();
|
|
459
459
|
} catch {
|
|
460
460
|
// The local session is still denied even if server cleanup fails.
|
|
461
461
|
}
|
|
@@ -471,21 +471,18 @@ export class AdminRuntime {
|
|
|
471
471
|
if (request !== this.#manifestRequest) return;
|
|
472
472
|
const admin = this.#installManifest(manifest);
|
|
473
473
|
if (admin !== undefined) {
|
|
474
|
-
const bootstrap = await this.client.
|
|
474
|
+
const bootstrap = await this.client.auth.bootstrap({
|
|
475
|
+
collection: admin.userCollectionSlug,
|
|
476
|
+
});
|
|
475
477
|
if (request !== this.#manifestRequest) return;
|
|
476
478
|
this.authBootstrapAvailable = bootstrap.available;
|
|
477
479
|
if (bootstrap.available) {
|
|
478
480
|
this.session = undefined;
|
|
479
481
|
} else {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
} catch (error) {
|
|
485
|
-
if (request !== this.#manifestRequest) return;
|
|
486
|
-
if (!(error instanceof RiduError) || error.code !== "access_denied") throw error;
|
|
487
|
-
this.session = undefined;
|
|
488
|
-
}
|
|
482
|
+
// getSession resolves null without a session and rejects on an outage.
|
|
483
|
+
const session = await this.client.auth.getSession();
|
|
484
|
+
if (request !== this.#manifestRequest) return;
|
|
485
|
+
this.session = session?.collection === admin.userCollectionSlug ? session : undefined;
|
|
489
486
|
}
|
|
490
487
|
} else {
|
|
491
488
|
this.session = undefined;
|
|
@@ -513,7 +510,9 @@ export class AdminRuntime {
|
|
|
513
510
|
this.session = undefined;
|
|
514
511
|
}
|
|
515
512
|
if (admin !== undefined && this.session === undefined) {
|
|
516
|
-
const bootstrap = await this.client.
|
|
513
|
+
const bootstrap = await this.client.auth.bootstrap({
|
|
514
|
+
collection: admin.userCollectionSlug,
|
|
515
|
+
});
|
|
517
516
|
if (request !== this.#manifestRequest) return;
|
|
518
517
|
this.authBootstrapAvailable = bootstrap.available;
|
|
519
518
|
} else {
|
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
const request = beginProfileRequest();
|
|
337
337
|
|
|
338
338
|
try {
|
|
339
|
-
await runtime.client.logout({ signal: request.signal });
|
|
339
|
+
await runtime.client.auth.logout({ signal: request.signal });
|
|
340
340
|
if (!ownsProfileRequest(request, owner)) return;
|
|
341
341
|
|
|
342
342
|
await runtime.hardNavigate(adminRoutePatterns.login);
|
|
@@ -47,8 +47,10 @@ export class AccountSecurityController {
|
|
|
47
47
|
this.error = undefined;
|
|
48
48
|
try {
|
|
49
49
|
const [sessions, apiKeys] = await Promise.all([
|
|
50
|
-
this.client.sessions({ signal: request.signal }),
|
|
51
|
-
this.apiKeysEnabled
|
|
50
|
+
this.client.auth.sessions({ signal: request.signal }),
|
|
51
|
+
this.apiKeysEnabled
|
|
52
|
+
? this.client.auth.apiKeys({ signal: request.signal })
|
|
53
|
+
: Promise.resolve([]),
|
|
52
54
|
]);
|
|
53
55
|
if (request.signal.aborted || generation !== this.#generation) return;
|
|
54
56
|
this.sessions = sessions;
|
|
@@ -64,7 +66,7 @@ export class AccountSecurityController {
|
|
|
64
66
|
revokeSession = async (id: string) => {
|
|
65
67
|
return this.#run(
|
|
66
68
|
"session:" + id,
|
|
67
|
-
(signal) => this.client.revokeSession(id, { signal }),
|
|
69
|
+
(signal) => this.client.auth.revokeSession(id, { signal }),
|
|
68
70
|
() => (this.sessions = this.sessions.filter((session) => session.id !== id))
|
|
69
71
|
);
|
|
70
72
|
};
|
|
@@ -72,7 +74,7 @@ export class AccountSecurityController {
|
|
|
72
74
|
logoutAll = async () => {
|
|
73
75
|
return this.#run(
|
|
74
76
|
"logout-all",
|
|
75
|
-
(signal) => this.client.logoutAll({ signal }),
|
|
77
|
+
(signal) => this.client.auth.logoutAll({ signal }),
|
|
76
78
|
() => (this.sessions = [])
|
|
77
79
|
);
|
|
78
80
|
};
|
|
@@ -80,7 +82,7 @@ export class AccountSecurityController {
|
|
|
80
82
|
createAPIKey = async (name: string, expiresAt?: string) => {
|
|
81
83
|
return this.#run(
|
|
82
84
|
"api-key:create",
|
|
83
|
-
(signal) => this.client.createAPIKey({ name, expiresAt }, { signal }),
|
|
85
|
+
(signal) => this.client.auth.createAPIKey({ name, expiresAt }, { signal }),
|
|
84
86
|
(created) => {
|
|
85
87
|
this.createdAPIKey = created;
|
|
86
88
|
this.apiKeys = [
|
|
@@ -103,7 +105,7 @@ export class AccountSecurityController {
|
|
|
103
105
|
revokeAPIKey = async (id: string) => {
|
|
104
106
|
return this.#run(
|
|
105
107
|
"api-key:" + id,
|
|
106
|
-
(signal) => this.client.revokeAPIKey(id, { signal }),
|
|
108
|
+
(signal) => this.client.auth.revokeAPIKey(id, { signal }),
|
|
107
109
|
() => {
|
|
108
110
|
this.apiKeys = this.apiKeys.filter((key) => key.id !== id);
|
|
109
111
|
if (this.createdAPIKey?.id === id) this.createdAPIKey = undefined;
|
|
@@ -113,7 +115,7 @@ export class AccountSecurityController {
|
|
|
113
115
|
|
|
114
116
|
changePassword = async (currentPassword: string, password: string) => {
|
|
115
117
|
return this.#run("password", (signal) =>
|
|
116
|
-
this.client.changePassword(currentPassword, password, { signal })
|
|
118
|
+
this.client.auth.changePassword({ currentPassword, password }, { signal })
|
|
117
119
|
);
|
|
118
120
|
};
|
|
119
121
|
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
hostRequest = request;
|
|
195
195
|
|
|
196
196
|
try {
|
|
197
|
-
await runtime.client.logout({ signal: request.signal });
|
|
197
|
+
await runtime.client.auth.logout({ signal: request.signal });
|
|
198
198
|
if (request.signal.aborted || runtime.session?.id !== owner) return;
|
|
199
199
|
|
|
200
200
|
await runtime.hardNavigate(adminRoutePatterns.login);
|
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import shell from "shiki/langs/shellscript.mjs";
|
|
6
|
-
import go from "shiki/langs/go.mjs";
|
|
7
|
-
import { riduCodeTheme } from "@riducms/ui/code-theme";
|
|
2
|
+
import { tokenize as tokenizeTypeScript } from "@twinkleplop/typescript";
|
|
3
|
+
import { tokenize as tokenizeBash } from "@twinkleplop/bash";
|
|
4
|
+
import { tokenize as tokenizeGo } from "@twinkleplop/go";
|
|
8
5
|
import "@fontsource-variable/martian-mono";
|
|
6
|
+
import type { ReferenceLanguage } from "@admin/features/api-reference/api-reference-examples";
|
|
7
|
+
|
|
8
|
+
// Keep the grammars in the drawer's lazy chunk and reuse their tokenizers across examples.
|
|
9
|
+
const tokenizers = {
|
|
10
|
+
typescript: tokenizeTypeScript(),
|
|
11
|
+
curl: tokenizeBash(),
|
|
12
|
+
go: tokenizeGo(),
|
|
13
|
+
};
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
function highlightedTokens(code: string, language: ReferenceLanguage) {
|
|
16
|
+
const { tokens, token_types } = tokenizers[language](code);
|
|
17
|
+
const parts: { content: string; type?: string }[] = [];
|
|
18
|
+
let offset = 0;
|
|
19
|
+
|
|
20
|
+
for (let index = 0; index < tokens.length; index += 3) {
|
|
21
|
+
const start = tokens[index + 1];
|
|
22
|
+
const end = tokens[index + 2];
|
|
23
|
+
if (start > offset) parts.push({ content: code.slice(offset, start) });
|
|
24
|
+
parts.push({ content: code.slice(start, end), type: token_types[tokens[index]] });
|
|
25
|
+
offset = end;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (offset < code.length) parts.push({ content: code.slice(offset) });
|
|
29
|
+
return parts;
|
|
30
|
+
}
|
|
16
31
|
</script>
|
|
17
32
|
|
|
18
33
|
<script lang="ts">
|
|
19
|
-
import type { ReferenceLanguage } from "@admin/features/api-reference/api-reference-examples";
|
|
20
|
-
|
|
21
34
|
let { code, language }: { code: string; language: ReferenceLanguage } = $props();
|
|
22
|
-
|
|
23
|
-
const highlighted = $derived(
|
|
24
|
-
highlighter.codeToTokens(code, {
|
|
25
|
-
lang: language === "curl" ? "shellscript" : language,
|
|
26
|
-
theme: riduCodeTheme.name,
|
|
27
|
-
})
|
|
28
|
-
);
|
|
35
|
+
const highlighted = $derived(highlightedTokens(code, language));
|
|
29
36
|
</script>
|
|
30
37
|
|
|
31
38
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex (Scrollable examples need a keyboard scroll target.) -->
|
|
32
|
-
<pre
|
|
33
|
-
|
|
34
|
-
dir="ltr"
|
|
35
|
-
tabindex="0"
|
|
36
|
-
style:--code-block-foreground={highlighted.fg}><code>{#each highlighted.tokens as line, index}{#if index > 0}{"\n"}{/if}{#each line as token}<span
|
|
37
|
-
style:color={token.color}
|
|
38
|
-
style:font-style={token.fontStyle && token.fontStyle & 1 ? "italic" : undefined}
|
|
39
|
-
style:font-weight={token.fontStyle && token.fontStyle & 2
|
|
40
|
-
? "700"
|
|
41
|
-
: undefined}>{token.content}</span>{/each}{/each}</code></pre>
|
|
39
|
+
<pre class="ridu-api-reference__code" dir="ltr" tabindex="0"><code>{#each highlighted as token}<span
|
|
40
|
+
class={token.type}>{token.content}</span>{/each}</code></pre>
|
|
42
41
|
|
|
43
42
|
<style lang="scss">
|
|
44
43
|
@layer ridu.components {
|
|
@@ -47,7 +46,8 @@
|
|
|
47
46
|
padding: 18px 20px;
|
|
48
47
|
max-height: 430px;
|
|
49
48
|
overflow: auto;
|
|
50
|
-
|
|
49
|
+
background: #0d0a0c;
|
|
50
|
+
color: #e9e6e1;
|
|
51
51
|
border: 1px solid var(--border);
|
|
52
52
|
border-radius: var(--radius-sm);
|
|
53
53
|
font-family: "Martian Mono Variable", "Martian Mono", ui-monospace, monospace;
|
|
@@ -59,6 +59,56 @@
|
|
|
59
59
|
code {
|
|
60
60
|
font: inherit;
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
:global(.comment) {
|
|
64
|
+
color: #7d7a8c;
|
|
65
|
+
font-style: italic;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:global(.keyword),
|
|
69
|
+
:global(.decorator) {
|
|
70
|
+
color: #ff7eb6;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:global(.string),
|
|
74
|
+
:global(.string_escape),
|
|
75
|
+
:global(.template),
|
|
76
|
+
:global(.regex) {
|
|
77
|
+
color: #b5e48c;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:global(.number),
|
|
81
|
+
:global(.boolean),
|
|
82
|
+
:global(.constant),
|
|
83
|
+
:global(.property) {
|
|
84
|
+
color: #c4a5ff;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:global(.function) {
|
|
88
|
+
color: #8fb4ff;
|
|
89
|
+
font-weight: 700;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:global(.type),
|
|
93
|
+
:global(.class_name) {
|
|
94
|
+
color: #7fdbec;
|
|
95
|
+
font-style: italic;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
:global(.parameter) {
|
|
99
|
+
color: #ffb977;
|
|
100
|
+
font-style: italic;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
:global(.builtin),
|
|
104
|
+
:global(.namespace) {
|
|
105
|
+
color: #8fb4ff;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:global(.punctuation),
|
|
109
|
+
:global(.operator) {
|
|
110
|
+
color: #80807e;
|
|
111
|
+
}
|
|
62
112
|
}
|
|
63
113
|
}
|
|
64
114
|
</style>
|
|
@@ -127,7 +127,7 @@ export function referenceCode(
|
|
|
127
127
|
break;
|
|
128
128
|
case "create":
|
|
129
129
|
if (request.authCreate)
|
|
130
|
-
call = `const doc = await ridu.
|
|
130
|
+
call = `const doc = await ridu.auth.createUser({\n collection: ${slug},\n data: ${data},\n password: "REPLACE_WITH_A_PASSWORD",\n});`;
|
|
131
131
|
else if (request.uploadCreate)
|
|
132
132
|
call = `async function createUpload(file: File) {\n const data: RiduConfig["collections"][${slug}]["create"] = ${data};\n return ridu.upload(${slug}, file, { data });\n}`;
|
|
133
133
|
else call = `const doc = await ridu.create(${slug}, ${data});`;
|
|
@@ -77,9 +77,10 @@ export class CreateFirstUserController {
|
|
|
77
77
|
let accountCreated = false;
|
|
78
78
|
try {
|
|
79
79
|
await this.form.submit(this.fields, true, (values) =>
|
|
80
|
-
this.options.runtime.client.
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
this.options.runtime.client.auth.createUser(
|
|
81
|
+
{ collection: collection.slug, data: values, password: this.password },
|
|
82
|
+
{ signal: request.signal }
|
|
83
|
+
)
|
|
83
84
|
);
|
|
84
85
|
accountCreated = true;
|
|
85
86
|
if (!this.#current(generation, request)) return undefined;
|
|
@@ -88,9 +89,8 @@ export class CreateFirstUserController {
|
|
|
88
89
|
if (typeof identity !== "string" || identity.trim() === "") {
|
|
89
90
|
throw new Error(this.options.runtime.i18n.t("auth:firstAccountIdentityUnavailable"));
|
|
90
91
|
}
|
|
91
|
-
await this.options.runtime.client.login(
|
|
92
|
-
collection.slug,
|
|
93
|
-
{ email: identity, password: this.password },
|
|
92
|
+
await this.options.runtime.client.auth.login(
|
|
93
|
+
{ collection: collection.slug, email: identity, password: this.password },
|
|
94
94
|
{ signal: request.signal }
|
|
95
95
|
);
|
|
96
96
|
if (!this.#current(generation, request)) return undefined;
|
|
@@ -101,7 +101,7 @@ export class CreateFirstUserController {
|
|
|
101
101
|
this.options.runtime.collectionOperations[collection.slug]?.admin !== true
|
|
102
102
|
) {
|
|
103
103
|
try {
|
|
104
|
-
await this.options.runtime.client.logout({ signal: request.signal });
|
|
104
|
+
await this.options.runtime.client.auth.logout({ signal: request.signal });
|
|
105
105
|
} catch {
|
|
106
106
|
// The setup route remains closed even if cookie cleanup fails.
|
|
107
107
|
}
|
|
@@ -133,9 +133,10 @@ export class CreateFirstUserController {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
try {
|
|
136
|
-
const bootstrap = await this.options.runtime.client.
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
const bootstrap = await this.options.runtime.client.auth.bootstrap(
|
|
137
|
+
{ collection: collection.slug },
|
|
138
|
+
{ signal: request.signal }
|
|
139
|
+
);
|
|
139
140
|
if (!this.#current(generation, request)) return undefined;
|
|
140
141
|
if (!bootstrap.available) {
|
|
141
142
|
await this.#continueToLogin("completed");
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
throw new Error(runtime.i18n.t("auth:configuredCollectionUnavailable"));
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
await runtime.client.login(authCollection.slug, credentials);
|
|
53
|
+
await runtime.client.auth.login({ collection: authCollection.slug, ...credentials });
|
|
54
54
|
const destinationLocale = runtime.contentLocaleForPath(postLoginPath);
|
|
55
55
|
const destinationAccessAuthoritative = await runtime.refreshAccess(
|
|
56
56
|
destinationLocale ?? runtime.contentLocale
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
runtime.collectionOperations[authCollection.slug]?.admin !== true
|
|
61
61
|
) {
|
|
62
62
|
try {
|
|
63
|
-
await runtime.client.logout();
|
|
63
|
+
await runtime.client.auth.logout();
|
|
64
64
|
} catch {
|
|
65
65
|
// The shell remains denied even if server cleanup fails.
|
|
66
66
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
error = undefined;
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
await runtime.client.logout();
|
|
21
|
+
await runtime.client.auth.logout();
|
|
22
22
|
await runtime.hardNavigate(adminRoutePatterns.login);
|
|
23
23
|
} catch (cause) {
|
|
24
24
|
error = cause instanceof Error ? cause.message : runtime.i18n.t("account:signOutFailed");
|
|
@@ -67,9 +67,12 @@
|
|
|
67
67
|
pending = true;
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
-
await runtime.client.resetPassword(
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
await runtime.client.auth.resetPassword(
|
|
71
|
+
{ collection: authCollection.slug, token, password },
|
|
72
|
+
{
|
|
73
|
+
signal: activeRequest.signal,
|
|
74
|
+
}
|
|
75
|
+
);
|
|
73
76
|
if (!current()) return;
|
|
74
77
|
complete = true;
|
|
75
78
|
password = "";
|
|
@@ -43,9 +43,12 @@
|
|
|
43
43
|
error = undefined;
|
|
44
44
|
|
|
45
45
|
try {
|
|
46
|
-
await runtime.client.verifyEmail(
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
await runtime.client.auth.verifyEmail(
|
|
47
|
+
{ collection: authCollection.slug, token },
|
|
48
|
+
{
|
|
49
|
+
signal: activeRequest.signal,
|
|
50
|
+
}
|
|
51
|
+
);
|
|
49
52
|
if (!current()) return;
|
|
50
53
|
complete = true;
|
|
51
54
|
} catch (cause) {
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
saveFormDraft,
|
|
12
12
|
takeFormDraft,
|
|
13
13
|
} from "@admin/core/forms/form-draft-recovery";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
changedFormValues,
|
|
16
|
+
documentFormValues,
|
|
17
|
+
initialFormValues,
|
|
18
|
+
} from "@admin/core/forms/form-schema";
|
|
15
19
|
import { invalidFieldLabels } from "@admin/core/forms/form-validation";
|
|
16
20
|
import type { NotificationCenter } from "@admin/core/notifications/notification-center.svelte";
|
|
17
21
|
import { samePreparedCreateValues } from "@admin/core/bootstrap/admin-bootstrap";
|
|
@@ -395,9 +399,10 @@ export class DocumentController {
|
|
|
395
399
|
this.#forceUnlockRequest = request;
|
|
396
400
|
this.unlockOperation = true;
|
|
397
401
|
try {
|
|
398
|
-
await runtime.client.forceUnlock(
|
|
399
|
-
|
|
400
|
-
|
|
402
|
+
await runtime.client.auth.forceUnlock(
|
|
403
|
+
{ collection: this.collection.slug, id: this.currentDocument.id },
|
|
404
|
+
{ signal: request.signal }
|
|
405
|
+
);
|
|
401
406
|
if (request.signal.aborted) return;
|
|
402
407
|
notifications.success({ title: runtime.i18n.t("documents:accountUnlocked") });
|
|
403
408
|
} catch (cause) {
|
|
@@ -500,6 +505,10 @@ export class DocumentController {
|
|
|
500
505
|
const wasCreating = this.creating;
|
|
501
506
|
try {
|
|
502
507
|
const saved = await this.form.submit(this.validationFields, this.creating, async (values) => {
|
|
508
|
+
if (!wasCreating)
|
|
509
|
+
values = changedFormValues(this.validationFields, values, this.form.original, (path) =>
|
|
510
|
+
this.form.isInherited(path)
|
|
511
|
+
);
|
|
503
512
|
if (this.globalResource) {
|
|
504
513
|
if (publishingChanges) {
|
|
505
514
|
return this.options.runtime.client.publishGlobalChanges(collectionSlug, values, {
|
|
@@ -545,10 +554,10 @@ export class DocumentController {
|
|
|
545
554
|
if (this.collection?.capabilities.auth) {
|
|
546
555
|
if (password === undefined)
|
|
547
556
|
throw new Error(this.options.runtime.i18n.t("documents:enterNewAccountPassword"));
|
|
548
|
-
return this.options.runtime.client.
|
|
549
|
-
|
|
550
|
-
locale: this.contentLocale
|
|
551
|
-
|
|
557
|
+
return this.options.runtime.client.auth.createUser(
|
|
558
|
+
{ collection: collectionSlug, data: values, password },
|
|
559
|
+
{ signal: request.signal, locale: this.contentLocale }
|
|
560
|
+
);
|
|
552
561
|
}
|
|
553
562
|
if (!this.uploadCollection) {
|
|
554
563
|
return this.options.runtime.client.create(collectionSlug, values, {
|
|
@@ -7,7 +7,11 @@ import { RiduError } from "@riducms/sdk";
|
|
|
7
7
|
|
|
8
8
|
import type { AdminDocument } from "@admin/core/api/admin-client";
|
|
9
9
|
import { FormController, FormValidationError } from "@admin/core/forms/form-controller.svelte";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
changedFormValues,
|
|
12
|
+
documentFormValues,
|
|
13
|
+
initialFormValues,
|
|
14
|
+
} from "@admin/core/forms/form-schema";
|
|
11
15
|
import { invalidFieldLabels } from "@admin/core/forms/form-validation";
|
|
12
16
|
import type { NotificationCenter } from "@admin/core/notifications/notification-center.svelte";
|
|
13
17
|
import type { AdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
@@ -450,6 +454,9 @@ export class ReferenceBrowserWorkflow {
|
|
|
450
454
|
const saved = await this.form.submit(this.validationFields, this.creating, async (values) => {
|
|
451
455
|
if (this.options.readOnly) throw new Error("This field is read-only.");
|
|
452
456
|
if (this.editorDocument !== undefined) {
|
|
457
|
+
values = changedFormValues(this.validationFields, values, this.form.original, (path) =>
|
|
458
|
+
this.form.isInherited(path)
|
|
459
|
+
);
|
|
453
460
|
if (this.collection.capabilities.upload) {
|
|
454
461
|
return this.options.runtime.client.updateUpload(
|
|
455
462
|
this.collection.slug,
|
|
@@ -485,10 +492,8 @@ export class ReferenceBrowserWorkflow {
|
|
|
485
492
|
);
|
|
486
493
|
}
|
|
487
494
|
if (this.collection.capabilities.auth) {
|
|
488
|
-
return this.options.runtime.client.
|
|
489
|
-
this.collection.slug,
|
|
490
|
-
values,
|
|
491
|
-
this.newUserPassword,
|
|
495
|
+
return this.options.runtime.client.auth.createUser(
|
|
496
|
+
{ collection: this.collection.slug, data: values, password: this.newUserPassword },
|
|
492
497
|
{ signal: request.signal, locale: this.options.locale }
|
|
493
498
|
);
|
|
494
499
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { flushSync } from "svelte";
|
|
2
3
|
import {
|
|
3
4
|
Combobox,
|
|
4
5
|
ComboboxInput,
|
|
@@ -33,13 +34,16 @@
|
|
|
33
34
|
const values = $derived(selectManyValues(form.get(field.path)));
|
|
34
35
|
const options = $derived(field.select?.options ?? []);
|
|
35
36
|
const filtered = $derived(
|
|
36
|
-
options.filter(
|
|
37
|
-
option
|
|
38
|
-
.
|
|
39
|
-
.
|
|
37
|
+
options.filter(
|
|
38
|
+
(option) =>
|
|
39
|
+
(!hasMany || !values.includes(option.value)) &&
|
|
40
|
+
option.label
|
|
41
|
+
.toLocaleLowerCase(runtime.i18n.language)
|
|
42
|
+
.includes(query.toLocaleLowerCase(runtime.i18n.language))
|
|
40
43
|
)
|
|
41
44
|
);
|
|
42
45
|
const selectedLabel = $derived(options.find((option) => option.value === value)?.label ?? "");
|
|
46
|
+
const inputValue = $derived(open || hasMany ? query : selectedLabel);
|
|
43
47
|
const placeholder = $derived(
|
|
44
48
|
field.admin.placeholder ?? runtime.i18n.t("fields:select", { label: field.admin.label })
|
|
45
49
|
);
|
|
@@ -58,6 +62,7 @@
|
|
|
58
62
|
|
|
59
63
|
function setValues(next: string[]) {
|
|
60
64
|
if (!editingBlocked) form.set(field.path, [...next]);
|
|
65
|
+
query = "";
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
function setValue(next: string) {
|
|
@@ -80,7 +85,7 @@
|
|
|
80
85
|
if (!next) query = "";
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
|
-
inputValue
|
|
88
|
+
{inputValue}
|
|
84
89
|
>
|
|
85
90
|
<div
|
|
86
91
|
bind:this={anchor}
|
|
@@ -114,17 +119,26 @@
|
|
|
114
119
|
autocorrect="off"
|
|
115
120
|
autocapitalize="none"
|
|
116
121
|
spellcheck={false}
|
|
117
|
-
{placeholder}
|
|
122
|
+
placeholder={hasMany && values.length > 0 ? "" : placeholder}
|
|
118
123
|
{...controlARIA}
|
|
119
124
|
aria-label={field.admin.label}
|
|
120
125
|
aria-required={field.required}
|
|
121
126
|
clearOnDeselect
|
|
122
127
|
onpointerdown={() => (open = true)}
|
|
123
128
|
oninput={(event) => {
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
const next = event.currentTarget.value;
|
|
130
|
+
// Filter before Bits chooses its keyboard highlight from the rendered options.
|
|
131
|
+
flushSync(() => {
|
|
132
|
+
query = next;
|
|
133
|
+
open = true;
|
|
134
|
+
});
|
|
126
135
|
}}
|
|
127
|
-
|
|
136
|
+
>
|
|
137
|
+
{#snippet child({ props })}
|
|
138
|
+
<!-- Bits writes the picked label internally; tags already display it. -->
|
|
139
|
+
<input {...props} value={inputValue} />
|
|
140
|
+
{/snippet}
|
|
141
|
+
</ComboboxInput>
|
|
128
142
|
</div>
|
|
129
143
|
{#if !hasMany && value}
|
|
130
144
|
<button
|
|
@@ -158,9 +172,11 @@
|
|
|
158
172
|
{option.label}
|
|
159
173
|
</ComboboxItem>
|
|
160
174
|
{:else}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
{#if query.trim()}
|
|
176
|
+
<p class="ridu-combobox-message">
|
|
177
|
+
{runtime.i18n.t("fields:noMatch", { label: field.admin.label })}
|
|
178
|
+
</p>
|
|
179
|
+
{/if}
|
|
164
180
|
{/each}
|
|
165
181
|
</ComboboxViewport>
|
|
166
182
|
</ComboboxContent>
|