@axium/client 0.33.5 → 0.35.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/assets/styles.css +36 -0
- package/dist/cli/cache.d.ts +2 -2
- package/dist/cli/config.d.ts +2 -2
- package/dist/locales.d.ts +6 -4
- package/dist/user.d.ts +6 -3
- package/dist/user.js +11 -16
- package/lib/AccessControlDialog.svelte +2 -2
- package/lib/FormDialog.svelte +2 -2
- package/lib/InlineEdit.svelte +117 -0
- package/lib/Login.svelte +7 -7
- package/lib/Register.svelte +3 -3
- package/lib/Upload.svelte +2 -1
- package/lib/index.ts +1 -0
- package/lib/toast.ts +71 -1
- package/locales/en.json +7 -5
- package/package.json +2 -2
- package/styles/account.css +1 -1
package/assets/styles.css
CHANGED
|
@@ -441,6 +441,42 @@ div.toast {
|
|
|
441
441
|
border: var(--border-accent);
|
|
442
442
|
background-color: var(--bg-accent);
|
|
443
443
|
}
|
|
444
|
+
|
|
445
|
+
&.progress {
|
|
446
|
+
border: var(--border-accent);
|
|
447
|
+
background-color: var(--bg-accent);
|
|
448
|
+
display: flex;
|
|
449
|
+
flex-direction: column;
|
|
450
|
+
gap: 0.25em;
|
|
451
|
+
text-align: left;
|
|
452
|
+
min-width: 16em;
|
|
453
|
+
|
|
454
|
+
.toast-header {
|
|
455
|
+
display: flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
justify-content: space-between;
|
|
458
|
+
gap: 1em;
|
|
459
|
+
|
|
460
|
+
button {
|
|
461
|
+
display: inline-flex;
|
|
462
|
+
cursor: pointer;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.subtle {
|
|
467
|
+
opacity: 69%;
|
|
468
|
+
font-size: 0.85em;
|
|
469
|
+
|
|
470
|
+
&:empty {
|
|
471
|
+
display: none;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
progress {
|
|
476
|
+
width: 100%;
|
|
477
|
+
height: 4px;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
444
480
|
}
|
|
445
481
|
|
|
446
482
|
@media (width >= 700px) {
|
package/dist/cli/cache.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export declare const meta: Handle<z.ZodObject<{
|
|
|
34
34
|
user: z.ZodObject<{
|
|
35
35
|
id: z.ZodUUID;
|
|
36
36
|
name: z.ZodString;
|
|
37
|
-
username: z.
|
|
38
|
-
email: z.ZodEmail
|
|
37
|
+
username: z.ZodString;
|
|
38
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodEmail>>;
|
|
39
39
|
emailVerified: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
40
40
|
preferences: z.ZodLazy<z.ZodObject<{
|
|
41
41
|
debug: z.ZodDefault<z.ZodBoolean>;
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare function session(): {
|
|
|
10
10
|
user: {
|
|
11
11
|
id: string;
|
|
12
12
|
name: string;
|
|
13
|
-
|
|
13
|
+
username: string;
|
|
14
14
|
preferences: {
|
|
15
15
|
debug: boolean;
|
|
16
16
|
};
|
|
@@ -19,7 +19,7 @@ export declare function session(): {
|
|
|
19
19
|
registeredAt: Date;
|
|
20
20
|
isAdmin: boolean;
|
|
21
21
|
isSuspended: boolean;
|
|
22
|
-
|
|
22
|
+
email?: string | null | undefined;
|
|
23
23
|
emailVerified?: Date | null | undefined;
|
|
24
24
|
};
|
|
25
25
|
name?: string | null | undefined;
|
package/dist/locales.d.ts
CHANGED
|
@@ -29,7 +29,6 @@ declare let currentLoaded: {
|
|
|
29
29
|
readonly no_results: "No results";
|
|
30
30
|
};
|
|
31
31
|
readonly Login: {
|
|
32
|
-
readonly email: "Email";
|
|
33
32
|
readonly register: "Register instead";
|
|
34
33
|
};
|
|
35
34
|
readonly Logout: {
|
|
@@ -37,9 +36,7 @@ declare let currentLoaded: {
|
|
|
37
36
|
readonly question: "Are you sure you want to log out?";
|
|
38
37
|
};
|
|
39
38
|
readonly Register: {
|
|
40
|
-
readonly email: "Email";
|
|
41
39
|
readonly login: "Login instead";
|
|
42
|
-
readonly name: "Display Name";
|
|
43
40
|
};
|
|
44
41
|
readonly SessionList: {
|
|
45
42
|
readonly created: "Created {date}";
|
|
@@ -84,6 +81,8 @@ declare let currentLoaded: {
|
|
|
84
81
|
readonly none: "None";
|
|
85
82
|
readonly ok: "Okay";
|
|
86
83
|
readonly preferences: "Preferences";
|
|
84
|
+
readonly recovery: "Recovery";
|
|
85
|
+
readonly recovery_method_disabled: "This recovery method is disabled and can't be used.";
|
|
87
86
|
readonly register: "Register";
|
|
88
87
|
readonly rename: "Rename";
|
|
89
88
|
readonly sessions: "Sessions";
|
|
@@ -91,7 +90,7 @@ declare let currentLoaded: {
|
|
|
91
90
|
readonly success: "Success";
|
|
92
91
|
readonly unknown: "Unknown";
|
|
93
92
|
readonly unnamed: "Unnamed";
|
|
94
|
-
readonly user_display_name: "
|
|
93
|
+
readonly user_display_name: "Display name";
|
|
95
94
|
readonly username: "Username";
|
|
96
95
|
readonly yes: "Yes";
|
|
97
96
|
};
|
|
@@ -127,8 +126,11 @@ declare let currentLoaded: {
|
|
|
127
126
|
readonly toast_updated: "Profile picture updated";
|
|
128
127
|
};
|
|
129
128
|
readonly preferences: "Preferences";
|
|
129
|
+
readonly remove_email: "Remove email";
|
|
130
|
+
readonly remove_email_confirm: "Are you sure you want to remove your recovery email?";
|
|
130
131
|
readonly sessions: "Sessions";
|
|
131
132
|
readonly title: "Your Account";
|
|
133
|
+
readonly toast_username_updated: "Username updated";
|
|
132
134
|
readonly user_id: "User ID";
|
|
133
135
|
readonly user_id_hint: "This is your UUID. It can't be changed.";
|
|
134
136
|
readonly verification_sent: "Verification email sent";
|
package/dist/user.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NewSessionResponse, Passkey, PasskeyChangeable, Session, User, UserChangeable, UserPublic, Verification } from '@axium/core';
|
|
1
|
+
import type { AuthInfo, NewSessionResponse, Passkey, PasskeyChangeable, Session, User, UserChangeable, UserPublic, Verification } from '@axium/core';
|
|
2
2
|
import * as z from 'zod';
|
|
3
3
|
import Cache from './cache.js';
|
|
4
4
|
export declare function login(userId: string): Promise<NewSessionResponse>;
|
|
@@ -6,7 +6,7 @@ export declare function login(userId: string): Promise<NewSessionResponse>;
|
|
|
6
6
|
* Create an elevated session for the user to perform sensitive actions.
|
|
7
7
|
*/
|
|
8
8
|
export declare function elevate(userId: string): Promise<void>;
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function loginByUsername(username: string): Promise<NewSessionResponse>;
|
|
10
10
|
export declare function getCurrentSession(): Promise<Session & {
|
|
11
11
|
user: User;
|
|
12
12
|
}>;
|
|
@@ -27,7 +27,10 @@ export declare function fullUserInfo(userId: string): Promise<User & {
|
|
|
27
27
|
* @param deletingId The UUID of the user performing the deletion (for authentication). Defaults to userId.
|
|
28
28
|
*/
|
|
29
29
|
export declare function deleteUser(userId: string, deletingId?: string): Promise<User>;
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Get which authentication-related features are available, e.g. account recovery methods.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getAuthInfo(userId: string): Promise<AuthInfo>;
|
|
31
34
|
export declare function sendVerificationEmail(userId: string): Promise<Verification>;
|
|
32
35
|
export declare function verifyEmail(userId: string, token: string): Promise<void>;
|
|
33
36
|
export declare function getPasskeys(userId: string): Promise<Passkey[]>;
|
package/dist/user.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { UserRegistrationInit } from '@axium/core/user';
|
|
1
2
|
import { startAuthentication, startRegistration } from '@simplewebauthn/browser';
|
|
2
3
|
import * as z from 'zod';
|
|
3
|
-
import { fetchAPI } from './requests.js';
|
|
4
4
|
import Cache from './cache.js';
|
|
5
|
+
import { fetchAPI } from './requests.js';
|
|
5
6
|
export async function login(userId) {
|
|
6
7
|
const options = await fetchAPI('PUT', 'users/:id/auth', { type: 'login' }, userId);
|
|
7
8
|
const response = await startAuthentication({ optionsJSON: options });
|
|
@@ -15,11 +16,8 @@ export async function elevate(userId) {
|
|
|
15
16
|
const response = await startAuthentication({ optionsJSON: options });
|
|
16
17
|
await fetchAPI('POST', 'users/:id/auth', response, userId);
|
|
17
18
|
}
|
|
18
|
-
export async function
|
|
19
|
-
const { id: userId } = await fetchAPI('POST', 'user_id', {
|
|
20
|
-
using: 'email',
|
|
21
|
-
value: email,
|
|
22
|
-
});
|
|
19
|
+
export async function loginByUsername(username) {
|
|
20
|
+
const { id: userId } = await fetchAPI('POST', 'user_id', { using: 'username', value: username });
|
|
23
21
|
return await login(userId);
|
|
24
22
|
}
|
|
25
23
|
let _currentSession = null;
|
|
@@ -44,15 +42,10 @@ export async function logoutCurrentSession() {
|
|
|
44
42
|
return await fetchAPI('DELETE', 'session');
|
|
45
43
|
}
|
|
46
44
|
export async function register(_data) {
|
|
47
|
-
const data =
|
|
45
|
+
const data = UserRegistrationInit.parse(_data);
|
|
48
46
|
const { options, userId } = await fetchAPI('PUT', 'register', data);
|
|
49
47
|
const response = await startRegistration({ optionsJSON: options });
|
|
50
|
-
await fetchAPI('POST', 'register', {
|
|
51
|
-
userId,
|
|
52
|
-
name: data.name,
|
|
53
|
-
email: data.email,
|
|
54
|
-
response,
|
|
55
|
-
});
|
|
48
|
+
await fetchAPI('POST', 'register', { userId, response, ...data });
|
|
56
49
|
}
|
|
57
50
|
function _checkId(userId) {
|
|
58
51
|
try {
|
|
@@ -91,10 +84,12 @@ export async function deleteUser(userId, deletingId = userId) {
|
|
|
91
84
|
userCache.invalidate(userId);
|
|
92
85
|
return result;
|
|
93
86
|
}
|
|
94
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Get which authentication-related features are available, e.g. account recovery methods.
|
|
89
|
+
*/
|
|
90
|
+
export async function getAuthInfo(userId) {
|
|
95
91
|
_checkId(userId);
|
|
96
|
-
|
|
97
|
-
return enabled;
|
|
92
|
+
return await fetchAPI('OPTIONS', 'users/:id/auth', {}, userId);
|
|
98
93
|
}
|
|
99
94
|
export async function sendVerificationEmail(userId) {
|
|
100
95
|
_checkId(userId);
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
{#each acl as control, i (control.userId || control.role || control.tag)}
|
|
42
|
-
{
|
|
42
|
+
{const update = (key: string) => async (e: Event & { currentTarget: HTMLInputElement }) => {
|
|
43
43
|
try {
|
|
44
44
|
const updated = await updateACL(itemType, item.id, getTarget(control), { [key]: e.currentTarget.checked });
|
|
45
45
|
Object.assign(control, updated);
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
</div>
|
|
81
81
|
<div class="permissions">
|
|
82
82
|
{#each Object.entries(pickPermissions(control) as Record<string, boolean>) as [key, value]}
|
|
83
|
-
{
|
|
83
|
+
{const id = $derived(`${item.id}.${getTarget(control)}.${key}`)}
|
|
84
84
|
<span class="icon-text">
|
|
85
85
|
{#if editable}
|
|
86
86
|
<input {id} type="checkbox" onchange={update(key)} />
|
package/lib/FormDialog.svelte
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
/** Change the text displayed for the submit button */
|
|
25
25
|
submitText?: string;
|
|
26
26
|
/** Called on submission, this should do the actual submission */
|
|
27
|
-
submit?(data: Record<string, FormDataEntryValue>):
|
|
27
|
+
submit?(data: Record<string, FormDataEntryValue>): any;
|
|
28
28
|
/** Whether to display the dialog as a full-page form */
|
|
29
29
|
pageMode?: boolean;
|
|
30
30
|
submitDanger?: boolean;
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
function onsubmit(e: SubmitEvent & { currentTarget: HTMLFormElement }) {
|
|
48
48
|
e.preventDefault();
|
|
49
49
|
const data = Object.fromEntries(new FormData(e.currentTarget));
|
|
50
|
-
submit(data)
|
|
50
|
+
Promise.resolve(submit(data))
|
|
51
51
|
.then(result => {
|
|
52
52
|
if (!pageMode) dialog!.close();
|
|
53
53
|
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
3
|
+
import { prettifyError, type ZodType } from 'zod';
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
value,
|
|
8
|
+
commit,
|
|
9
|
+
close,
|
|
10
|
+
schema,
|
|
11
|
+
optional = false,
|
|
12
|
+
selectionRange,
|
|
13
|
+
placeholder,
|
|
14
|
+
confirmLabel = text('generic.change'),
|
|
15
|
+
}: {
|
|
16
|
+
/** The committed value being edited. */
|
|
17
|
+
value?: string | null;
|
|
18
|
+
/** Called with the new value when a change is confirmed. `null` means the value was cleared (only possible when `optional`). */
|
|
19
|
+
commit(value: string): unknown;
|
|
20
|
+
/** Called when editing ends, whether or not a change was committed. */
|
|
21
|
+
close(): void;
|
|
22
|
+
/** Validates the draft as the user types and before committing. Errors float above the input. */
|
|
23
|
+
schema?: ZodType;
|
|
24
|
+
/** Whether clearing the input commits `null` instead of closing without changes. */
|
|
25
|
+
optional?: boolean;
|
|
26
|
+
/** The range of the value to select when editing starts. Defaults to the entire value. */
|
|
27
|
+
selectionRange?: [number, number];
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
confirmLabel?: string;
|
|
30
|
+
} = $props();
|
|
31
|
+
|
|
32
|
+
let draft = $state(value ?? ''),
|
|
33
|
+
error = $state<string>();
|
|
34
|
+
|
|
35
|
+
function validate(val: string | null): boolean {
|
|
36
|
+
const result = val === null && optional ? undefined : schema?.safeParse(val);
|
|
37
|
+
error = result?.error ? prettifyError(result.error) : undefined;
|
|
38
|
+
return !result || result.success;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function confirm() {
|
|
42
|
+
const newValue = draft.trim() || null;
|
|
43
|
+
if (newValue === (value ?? null) || (newValue === null && !optional)) return close();
|
|
44
|
+
if (!validate(newValue)) return;
|
|
45
|
+
commit(newValue!);
|
|
46
|
+
close();
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<span
|
|
51
|
+
class="InlineEdit"
|
|
52
|
+
data-no-select
|
|
53
|
+
onclick={e => e.stopPropagation()}
|
|
54
|
+
{@attach span => {
|
|
55
|
+
const input = span.querySelector('input')!;
|
|
56
|
+
requestAnimationFrame(() => {
|
|
57
|
+
input.focus();
|
|
58
|
+
input.setSelectionRange(...(selectionRange ?? [0, draft.length]));
|
|
59
|
+
});
|
|
60
|
+
const onOutside = (e: PointerEvent) => !span.contains(e.target as Node) && close();
|
|
61
|
+
requestAnimationFrame(() => document.addEventListener('pointerdown', onOutside, true));
|
|
62
|
+
return () => document.removeEventListener('pointerdown', onOutside, true);
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{#if error}<span class="InlineEdit-error">{error}</span>{/if}
|
|
66
|
+
<input
|
|
67
|
+
bind:value={draft}
|
|
68
|
+
{placeholder}
|
|
69
|
+
class={[error && 'error']}
|
|
70
|
+
oninput={() => validate(draft.trim() || null)}
|
|
71
|
+
onkeydown={e => {
|
|
72
|
+
if (e.key == 'Enter') confirm();
|
|
73
|
+
else if (e.key == 'Escape') close();
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
<button class="reset InlineEdit-confirm" aria-label={confirmLabel} onclick={confirm}>
|
|
77
|
+
<Icon i="check" />
|
|
78
|
+
</button>
|
|
79
|
+
</span>
|
|
80
|
+
|
|
81
|
+
<style>
|
|
82
|
+
.InlineEdit {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 0.5em;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
anchor-scope: --inline-edit;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
input {
|
|
91
|
+
background: var(--bg-normal);
|
|
92
|
+
border: var(--border-accent);
|
|
93
|
+
border-radius: 0.25em;
|
|
94
|
+
padding: 0.1em 0.25em;
|
|
95
|
+
margin: -0.1em 0;
|
|
96
|
+
font: inherit;
|
|
97
|
+
color: inherit;
|
|
98
|
+
min-width: 0;
|
|
99
|
+
flex: 1 1 auto;
|
|
100
|
+
anchor-name: --inline-edit;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.InlineEdit-error {
|
|
104
|
+
position: fixed;
|
|
105
|
+
position-anchor: --inline-edit;
|
|
106
|
+
bottom: calc(anchor(top) - 0.3em);
|
|
107
|
+
left: anchor(left);
|
|
108
|
+
color: var(--fg-error);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.InlineEdit-confirm {
|
|
112
|
+
display: inline-flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
flex: 0 0 auto;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
}
|
|
117
|
+
</style>
|
package/lib/Login.svelte
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { loginByUsername, text } from '@axium/client';
|
|
3
3
|
import FormDialog from './FormDialog.svelte';
|
|
4
4
|
import authRedirect from './auth_redirect.js';
|
|
5
5
|
|
|
6
6
|
let { dialog = $bindable(), fullPage = false }: { dialog?: HTMLDialogElement; fullPage?: boolean } = $props();
|
|
7
7
|
|
|
8
|
-
async function submit(data: {
|
|
9
|
-
if (typeof data.
|
|
10
|
-
throw 'Tried to upload a file for
|
|
8
|
+
async function submit(data: { username: string }) {
|
|
9
|
+
if (typeof data.username != 'string') {
|
|
10
|
+
throw 'Tried to upload a file for a username. Huh?!';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
await
|
|
13
|
+
await loginByUsername(data.username);
|
|
14
14
|
const redirectAfter = await authRedirect();
|
|
15
15
|
if (fullPage && redirectAfter) location.href = redirectAfter;
|
|
16
16
|
}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
<FormDialog bind:dialog submitText={text('generic.login')} {submit} pageMode={fullPage}>
|
|
20
20
|
<div>
|
|
21
|
-
<label for="
|
|
22
|
-
<input name="
|
|
21
|
+
<label for="username">{text('generic.username')}</label>
|
|
22
|
+
<input name="username" type="text" required />
|
|
23
23
|
</div>
|
|
24
24
|
{#snippet footer()}
|
|
25
25
|
{#if fullPage}
|
package/lib/Register.svelte
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
<FormDialog bind:dialog submitText={text('generic.register')} {submit} pageMode={fullPage}>
|
|
16
16
|
<div>
|
|
17
|
-
<label for="name">{text('
|
|
17
|
+
<label for="name">{text('generic.user_display_name')}</label>
|
|
18
18
|
<input name="name" type="text" required />
|
|
19
19
|
</div>
|
|
20
20
|
<div>
|
|
21
|
-
<label for="
|
|
22
|
-
<input name="
|
|
21
|
+
<label for="username">{text('generic.username')}</label>
|
|
22
|
+
<input name="username" type="text" required />
|
|
23
23
|
</div>
|
|
24
24
|
{#snippet footer()}
|
|
25
25
|
{#if fullPage}
|
package/lib/Upload.svelte
CHANGED
package/lib/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { default as Discovery } from './Discovery.svelte';
|
|
|
9
9
|
export * as discovery from './Discovery.svelte';
|
|
10
10
|
export { default as FormDialog } from './FormDialog.svelte';
|
|
11
11
|
export { default as Icon } from './Icon.svelte';
|
|
12
|
+
export { default as InlineEdit } from './InlineEdit.svelte';
|
|
12
13
|
export { default as LocationSelect } from './LocationSelect.svelte';
|
|
13
14
|
export { default as Login } from './Login.svelte';
|
|
14
15
|
export { default as Logout } from './Logout.svelte';
|
package/lib/toast.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { debug, errorText } from 'ioium';
|
|
1
|
+
import { debug, errorText, useProgress } from 'ioium';
|
|
2
2
|
import { text } from '@axium/client/locales';
|
|
3
3
|
import { animate, onAnimationEnd } from '@axium/client/gui';
|
|
4
4
|
import Icon from './Icon.svelte';
|
|
@@ -83,4 +83,74 @@ export async function toastStatus(promise: Promise<unknown>, successMessage: str
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
let progressToast: HTMLDivElement | undefined,
|
|
87
|
+
progressMain: HTMLSpanElement,
|
|
88
|
+
progressMessage: HTMLSpanElement,
|
|
89
|
+
progressBar: HTMLProgressElement,
|
|
90
|
+
progressCancelButton: HTMLButtonElement,
|
|
91
|
+
progressCancel: (() => void) | undefined;
|
|
92
|
+
|
|
93
|
+
function warnBeforeUnload(e: BeforeUnloadEvent) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Register a callback used to cancel the operation shown in the progress toast.
|
|
99
|
+
* The toast's cancel button is only shown while a callback is registered.
|
|
100
|
+
*/
|
|
101
|
+
export function setProgressCancel(cancel?: () => void): void {
|
|
102
|
+
progressCancel = cancel;
|
|
103
|
+
if (progressToast) progressCancelButton.style.display = cancel ? '' : 'none';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
useProgress({
|
|
107
|
+
start(message: string): void {
|
|
108
|
+
if (!progressToast) {
|
|
109
|
+
progressToast = document.createElement('div');
|
|
110
|
+
progressToast.classList.add('toast', 'progress');
|
|
111
|
+
|
|
112
|
+
const header = document.createElement('div');
|
|
113
|
+
header.classList.add('toast-header');
|
|
114
|
+
progressToast.appendChild(header);
|
|
115
|
+
|
|
116
|
+
progressMain = document.createElement('span');
|
|
117
|
+
header.appendChild(progressMain);
|
|
118
|
+
|
|
119
|
+
progressCancelButton = document.createElement('button');
|
|
120
|
+
progressCancelButton.classList.add('reset');
|
|
121
|
+
progressCancelButton.onclick = () => progressCancel?.();
|
|
122
|
+
mount(Icon, { target: progressCancelButton, props: { i: 'xmark-large' } });
|
|
123
|
+
header.appendChild(progressCancelButton);
|
|
124
|
+
|
|
125
|
+
progressMessage = document.createElement('span');
|
|
126
|
+
progressMessage.classList.add('subtle');
|
|
127
|
+
progressToast.appendChild(progressMessage);
|
|
128
|
+
|
|
129
|
+
progressBar = document.createElement('progress');
|
|
130
|
+
progressToast.appendChild(progressBar);
|
|
131
|
+
|
|
132
|
+
list.appendChild(progressToast);
|
|
133
|
+
addEventListener('beforeunload', warnBeforeUnload);
|
|
134
|
+
}
|
|
135
|
+
progressMain.textContent = message;
|
|
136
|
+
progressMessage.textContent = '';
|
|
137
|
+
progressCancelButton.style.display = progressCancel ? '' : 'none';
|
|
138
|
+
progressBar.removeAttribute('value');
|
|
139
|
+
progressBar.max = 1;
|
|
140
|
+
},
|
|
141
|
+
progress(value: number, max: number, message?: any): void {
|
|
142
|
+
if (!progressToast) return;
|
|
143
|
+
progressBar.value = value;
|
|
144
|
+
progressBar.max = max;
|
|
145
|
+
progressMessage.textContent = message == undefined ? '' : String(message);
|
|
146
|
+
},
|
|
147
|
+
done(): void {
|
|
148
|
+
if (!progressToast) return;
|
|
149
|
+
progressToast.remove();
|
|
150
|
+
progressToast = undefined;
|
|
151
|
+
progressCancel = undefined;
|
|
152
|
+
removeEventListener('beforeunload', warnBeforeUnload);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
86
156
|
Object.assign(globalThis, { toast, toastStatus });
|
package/locales/en.json
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"no_results": "No results"
|
|
24
24
|
},
|
|
25
25
|
"Login": {
|
|
26
|
-
"email": "Email",
|
|
27
26
|
"register": "Register instead"
|
|
28
27
|
},
|
|
29
28
|
"Logout": {
|
|
@@ -31,9 +30,7 @@
|
|
|
31
30
|
"question": "Are you sure you want to log out?"
|
|
32
31
|
},
|
|
33
32
|
"Register": {
|
|
34
|
-
"
|
|
35
|
-
"login": "Login instead",
|
|
36
|
-
"name": "Display Name"
|
|
33
|
+
"login": "Login instead"
|
|
37
34
|
},
|
|
38
35
|
"SessionList": {
|
|
39
36
|
"created": "Created {date}",
|
|
@@ -78,6 +75,8 @@
|
|
|
78
75
|
"none": "None",
|
|
79
76
|
"ok": "Okay",
|
|
80
77
|
"preferences": "Preferences",
|
|
78
|
+
"recovery": "Recovery",
|
|
79
|
+
"recovery_method_disabled": "This recovery method is disabled and can't be used.",
|
|
81
80
|
"register": "Register",
|
|
82
81
|
"rename": "Rename",
|
|
83
82
|
"sessions": "Sessions",
|
|
@@ -85,7 +84,7 @@
|
|
|
85
84
|
"success": "Success",
|
|
86
85
|
"unknown": "Unknown",
|
|
87
86
|
"unnamed": "Unnamed",
|
|
88
|
-
"user_display_name": "
|
|
87
|
+
"user_display_name": "Display name",
|
|
89
88
|
"username": "Username",
|
|
90
89
|
"yes": "Yes"
|
|
91
90
|
},
|
|
@@ -121,8 +120,11 @@
|
|
|
121
120
|
"toast_updated": "Profile picture updated"
|
|
122
121
|
},
|
|
123
122
|
"preferences": "Preferences",
|
|
123
|
+
"remove_email": "Remove email",
|
|
124
|
+
"remove_email_confirm": "Are you sure you want to remove your recovery email?",
|
|
124
125
|
"sessions": "Sessions",
|
|
125
126
|
"title": "Your Account",
|
|
127
|
+
"toast_username_updated": "Username updated",
|
|
126
128
|
"user_id": "User ID",
|
|
127
129
|
"user_id_hint": "This is your UUID. It can't be changed.",
|
|
128
130
|
"verification_sent": "Verification email sent",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"author": "James Prevett <jp@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"build": "tsc"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@axium/core": ">=0.
|
|
52
|
+
"@axium/core": ">=0.37.0",
|
|
53
53
|
"ioium": "^1.7.0",
|
|
54
54
|
"semver": "^7.7.4",
|
|
55
55
|
"svelte": "^5.36.0",
|