@riducms/admin 0.3.0 → 0.4.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/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 +8 -7
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +2 -4
- 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.4.0",
|
|
25
|
+
"@riducms/protocol": "0.4.0",
|
|
26
|
+
"@riducms/sdk": "0.4.0",
|
|
27
|
+
"@riducms/translations": "0.4.0",
|
|
28
|
+
"@riducms/ui": "0.4.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.4.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.4.0"
|
|
76
78
|
}
|
|
@@ -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) {
|
|
@@ -395,9 +395,10 @@ export class DocumentController {
|
|
|
395
395
|
this.#forceUnlockRequest = request;
|
|
396
396
|
this.unlockOperation = true;
|
|
397
397
|
try {
|
|
398
|
-
await runtime.client.forceUnlock(
|
|
399
|
-
|
|
400
|
-
|
|
398
|
+
await runtime.client.auth.forceUnlock(
|
|
399
|
+
{ collection: this.collection.slug, id: this.currentDocument.id },
|
|
400
|
+
{ signal: request.signal }
|
|
401
|
+
);
|
|
401
402
|
if (request.signal.aborted) return;
|
|
402
403
|
notifications.success({ title: runtime.i18n.t("documents:accountUnlocked") });
|
|
403
404
|
} catch (cause) {
|
|
@@ -545,10 +546,10 @@ export class DocumentController {
|
|
|
545
546
|
if (this.collection?.capabilities.auth) {
|
|
546
547
|
if (password === undefined)
|
|
547
548
|
throw new Error(this.options.runtime.i18n.t("documents:enterNewAccountPassword"));
|
|
548
|
-
return this.options.runtime.client.
|
|
549
|
-
|
|
550
|
-
locale: this.contentLocale
|
|
551
|
-
|
|
549
|
+
return this.options.runtime.client.auth.createUser(
|
|
550
|
+
{ collection: collectionSlug, data: values, password },
|
|
551
|
+
{ signal: request.signal, locale: this.contentLocale }
|
|
552
|
+
);
|
|
552
553
|
}
|
|
553
554
|
if (!this.uploadCollection) {
|
|
554
555
|
return this.options.runtime.client.create(collectionSlug, values, {
|
|
@@ -485,10 +485,8 @@ export class ReferenceBrowserWorkflow {
|
|
|
485
485
|
);
|
|
486
486
|
}
|
|
487
487
|
if (this.collection.capabilities.auth) {
|
|
488
|
-
return this.options.runtime.client.
|
|
489
|
-
this.collection.slug,
|
|
490
|
-
values,
|
|
491
|
-
this.newUserPassword,
|
|
488
|
+
return this.options.runtime.client.auth.createUser(
|
|
489
|
+
{ collection: this.collection.slug, data: values, password: this.newUserPassword },
|
|
492
490
|
{ signal: request.signal, locale: this.options.locale }
|
|
493
491
|
);
|
|
494
492
|
}
|
|
@@ -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>
|