@axium/client 0.19.8 → 0.20.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/dist/access.js +2 -1
- package/dist/cache.d.ts +18 -0
- package/dist/cache.js +34 -0
- package/dist/locales.d.ts +57 -57
- package/dist/user.d.ts +1 -1
- package/dist/user.js +3 -8
- package/lib/AccessControlDialog.svelte +50 -28
- package/lib/AppMenu.svelte +2 -2
- package/lib/Login.svelte +2 -2
- package/lib/Logout.svelte +2 -2
- package/lib/NumberBar.svelte +1 -1
- package/lib/Register.svelte +3 -3
- package/lib/SessionList.svelte +8 -9
- package/lib/Upload.svelte +1 -1
- package/lib/UserCard.svelte +2 -2
- package/lib/UserDiscovery.svelte +2 -3
- package/lib/UserMenu.svelte +4 -4
- package/lib/Version.svelte +3 -3
- package/lib/ZodInput.svelte +1 -1
- package/locales/en.json +57 -57
- package/package.json +2 -2
package/dist/access.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { fetchAPI } from './requests.js';
|
|
2
|
+
import { useCache } from './cache.js';
|
|
2
3
|
export async function updateACL(itemType, itemId, target, permissions) {
|
|
3
4
|
return await fetchAPI('PATCH', 'acl/:itemType/:itemId', { target, permissions }, itemType, itemId);
|
|
4
5
|
}
|
|
5
6
|
export async function getACL(itemType, itemId) {
|
|
6
|
-
return await fetchAPI('GET', 'acl/:itemType/:itemId', {}, itemType, itemId);
|
|
7
|
+
return await useCache('acl', `${itemType}/${itemId}`, () => fetchAPI('GET', 'acl/:itemType/:itemId', {}, itemType, itemId));
|
|
7
8
|
}
|
|
8
9
|
export async function addToACL(itemType, itemId, target) {
|
|
9
10
|
return await fetchAPI('PUT', 'acl/:itemType/:itemId', target, itemType, itemId);
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CacheOptions {
|
|
2
|
+
/** Maximum number of items the cache can hold */
|
|
3
|
+
itemLimit: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Create a cache manually. Only useful when you want to specify different options.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createCache(cacheName: string, options?: Partial<CacheOptions>): void;
|
|
9
|
+
/**
|
|
10
|
+
* Use a cache for some arbitrary operation.
|
|
11
|
+
* This is primarily intended for de-duplicating API requests
|
|
12
|
+
* @param cacheName The name of the cache to use, e.g. `'users'`
|
|
13
|
+
* @param key The key for the item in the cache. This can be anything used for the key of a `Map`
|
|
14
|
+
* @param miss The function to run on a cache miss
|
|
15
|
+
* @remarks
|
|
16
|
+
* Note that a cache will automatically be created if it doesn't already exist
|
|
17
|
+
*/
|
|
18
|
+
export declare function useCache<Key, Result>(cacheName: string, key: Key, miss: () => Result): Result;
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const _caches = Object.create(null);
|
|
2
|
+
const defaultCacheOptions = {
|
|
3
|
+
itemLimit: 10_000,
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Create a cache manually. Only useful when you want to specify different options.
|
|
7
|
+
*/
|
|
8
|
+
export function createCache(cacheName, options = {}) {
|
|
9
|
+
if (_caches[cacheName])
|
|
10
|
+
throw new Error('Multiple caches with the same name are not allowed');
|
|
11
|
+
_caches[cacheName] = {
|
|
12
|
+
items: new Map(),
|
|
13
|
+
...defaultCacheOptions,
|
|
14
|
+
...options,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Use a cache for some arbitrary operation.
|
|
19
|
+
* This is primarily intended for de-duplicating API requests
|
|
20
|
+
* @param cacheName The name of the cache to use, e.g. `'users'`
|
|
21
|
+
* @param key The key for the item in the cache. This can be anything used for the key of a `Map`
|
|
22
|
+
* @param miss The function to run on a cache miss
|
|
23
|
+
* @remarks
|
|
24
|
+
* Note that a cache will automatically be created if it doesn't already exist
|
|
25
|
+
*/
|
|
26
|
+
export function useCache(cacheName, key, miss) {
|
|
27
|
+
const cache = (_caches[cacheName] ||= { items: new Map(), ...defaultCacheOptions });
|
|
28
|
+
const cached = cache.items.get(key);
|
|
29
|
+
if (cached)
|
|
30
|
+
return cached;
|
|
31
|
+
const result = miss();
|
|
32
|
+
cache.items.set(key, result);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
package/dist/locales.d.ts
CHANGED
|
@@ -5,63 +5,63 @@ import type { FlattenKeys, GetByString, Split, UnionToIntersection } from 'utili
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function extendLocale(locale: string, data: object): void;
|
|
7
7
|
declare let currentLoaded: {
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
};
|
|
8
|
+
readonly AccessControlDialog: {
|
|
9
|
+
readonly named_title: "Permissions for <strong>{name}</strong>";
|
|
10
|
+
readonly owner: "Owner";
|
|
11
|
+
readonly title: "Permissions";
|
|
12
|
+
readonly remove: "Remove";
|
|
13
|
+
readonly toast_removed: "Removed access";
|
|
14
|
+
readonly public_target: "Everyone";
|
|
15
|
+
readonly add_public: "Add Public Access";
|
|
16
|
+
};
|
|
17
|
+
readonly AppMenu: {
|
|
18
|
+
readonly failed: "Couldn't load apps.";
|
|
19
|
+
readonly none: "No apps available.";
|
|
20
|
+
};
|
|
21
|
+
readonly Login: {
|
|
22
|
+
readonly email: "Email";
|
|
23
|
+
readonly register: "Register instead";
|
|
24
|
+
};
|
|
25
|
+
readonly Logout: {
|
|
26
|
+
readonly back: "Take me back";
|
|
27
|
+
readonly question: "Are you sure you want to log out?";
|
|
28
|
+
};
|
|
29
|
+
readonly Register: {
|
|
30
|
+
readonly email: "Email";
|
|
31
|
+
readonly login: "Login instead";
|
|
32
|
+
readonly name: "Display Name";
|
|
33
|
+
};
|
|
34
|
+
readonly SessionList: {
|
|
35
|
+
readonly created: "Created {date}";
|
|
36
|
+
readonly current: "Current";
|
|
37
|
+
readonly elevated: "Elevated";
|
|
38
|
+
readonly expires: "Expires {date}";
|
|
39
|
+
readonly logout_all_question: "Are you sure you want to log out all sessions?";
|
|
40
|
+
readonly logout_all_submit: "Logout All Sessions";
|
|
41
|
+
readonly logout_all_trigger: "Logout All";
|
|
42
|
+
readonly logout_single: "Are you sure you want to log out this session?";
|
|
43
|
+
};
|
|
44
|
+
readonly Upload: {
|
|
45
|
+
readonly upload: "Upload";
|
|
46
|
+
};
|
|
47
|
+
readonly UserCard: {
|
|
48
|
+
readonly you: "(You)";
|
|
49
|
+
};
|
|
50
|
+
readonly UserDiscovery: {
|
|
51
|
+
readonly no_results: "No results";
|
|
52
|
+
readonly placeholder: "Add users and roles";
|
|
53
|
+
};
|
|
54
|
+
readonly UserMenu: {
|
|
55
|
+
readonly account: "Your Account";
|
|
56
|
+
readonly admin: "Administration";
|
|
57
|
+
};
|
|
58
|
+
readonly Version: {
|
|
59
|
+
readonly error: "Latest unknown";
|
|
60
|
+
readonly latest: "Latest";
|
|
61
|
+
readonly upgrade: "<span class=\"version\">{latest}</span> available";
|
|
62
|
+
};
|
|
63
|
+
readonly ZodInput: {
|
|
64
|
+
readonly invalid_type: "Invalid input type: {type}";
|
|
65
65
|
};
|
|
66
66
|
readonly generic: {
|
|
67
67
|
readonly action_irreversible: "This action can't be undone.";
|
package/dist/user.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function logout(userId: string, ...sessionId: string[]): Promise<
|
|
|
14
14
|
export declare function logoutAll(userId: string): Promise<Session[]>;
|
|
15
15
|
export declare function logoutCurrentSession(): Promise<Session>;
|
|
16
16
|
export declare function register(_data: Record<string, unknown>): Promise<void>;
|
|
17
|
-
export declare function userInfo(userId: string
|
|
17
|
+
export declare function userInfo(userId: string): Promise<UserPublic & Partial<User>>;
|
|
18
18
|
export declare function updateUser(userId: string, data: Record<string, FormDataEntryValue>): Promise<User>;
|
|
19
19
|
export declare function fullUserInfo(userId: string): Promise<User & {
|
|
20
20
|
sessions: Session[];
|
package/dist/user.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { startAuthentication, startRegistration } from '@simplewebauthn/browser';
|
|
2
2
|
import * as z from 'zod';
|
|
3
3
|
import { fetchAPI } from './requests.js';
|
|
4
|
+
import { useCache } from './cache.js';
|
|
4
5
|
export async function login(userId) {
|
|
5
6
|
const options = await fetchAPI('OPTIONS', 'users/:id/auth', { type: 'login' }, userId);
|
|
6
7
|
const response = await startAuthentication({ optionsJSON: options });
|
|
@@ -59,15 +60,9 @@ function _checkId(userId) {
|
|
|
59
60
|
throw e instanceof z.core.$ZodError ? z.prettifyError(e) : e;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
export async function userInfo(userId, noCache = false) {
|
|
63
|
+
export async function userInfo(userId) {
|
|
64
64
|
_checkId(userId);
|
|
65
|
-
|
|
66
|
-
if (!noCache && cached)
|
|
67
|
-
return await cached;
|
|
68
|
-
const result = fetchAPI('GET', 'users/:id', {}, userId);
|
|
69
|
-
userCache.set(userId, result);
|
|
70
|
-
return await result;
|
|
65
|
+
return await useCache('user', userId, () => fetchAPI('GET', 'users/:id', {}, userId));
|
|
71
66
|
}
|
|
72
67
|
export async function updateUser(userId, data) {
|
|
73
68
|
_checkId(userId);
|
|
@@ -1,43 +1,32 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { addToACL, getACL, removeFromACL, text, updateACL, userInfo } from '@axium/client';
|
|
3
|
-
import type { AccessControllable, AccessTarget,
|
|
4
|
-
import { getTarget, pickPermissions } from '@axium/core';
|
|
5
|
-
import { errorText } from '@axium/core/io';
|
|
3
|
+
import type { AccessControllable, AccessTarget, UserPublic } from '@axium/core';
|
|
4
|
+
import { checkAndMatchACL, getTarget, pickPermissions } from '@axium/core';
|
|
6
5
|
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
7
6
|
import Icon from './Icon.svelte';
|
|
8
7
|
import UserCard from './UserCard.svelte';
|
|
9
8
|
import UserDiscovery from './UserDiscovery.svelte';
|
|
10
9
|
import { closeOnBackGesture } from './attachments.js';
|
|
11
|
-
import { toastStatus } from './toast.js';
|
|
10
|
+
import { toast, toastStatus } from './toast.js';
|
|
12
11
|
|
|
13
12
|
interface Props extends HTMLDialogAttributes {
|
|
14
|
-
|
|
13
|
+
user?: UserPublic;
|
|
15
14
|
dialog?: HTMLDialogElement;
|
|
16
15
|
itemType: string;
|
|
17
|
-
item: { name?: string; user?:
|
|
16
|
+
item: { name?: string; user?: UserPublic; id: string } & AccessControllable;
|
|
18
17
|
}
|
|
19
|
-
let { item, itemType,
|
|
20
|
-
|
|
21
|
-
let error = $state<string>();
|
|
18
|
+
let { item, itemType, user, dialog = $bindable(), ...rest }: Props = $props();
|
|
22
19
|
|
|
23
20
|
const acl = $state(item.acl ?? (await getACL(itemType, item.id)));
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
const control = await addToACL(itemType, item.id, target);
|
|
27
|
-
if (control.userId) control.user = await userInfo(control.userId);
|
|
28
|
-
acl.push(control);
|
|
29
|
-
}
|
|
22
|
+
const editable = $derived(!!user && (item.userId === user.id || !checkAndMatchACL(item.acl || [], user, { manage: true }).size));
|
|
30
23
|
</script>
|
|
31
24
|
|
|
32
25
|
<dialog bind:this={dialog} {...rest} onclick={e => e.stopPropagation()} {@attach closeOnBackGesture}>
|
|
33
26
|
{#if item.name}
|
|
34
|
-
<h3>{@html text('
|
|
27
|
+
<h3>{@html text('AccessControlDialog.named_title', { $html: true, name: item.name })}</h3>
|
|
35
28
|
{:else}
|
|
36
|
-
<h3>{text('
|
|
37
|
-
{/if}
|
|
38
|
-
|
|
39
|
-
{#if error}
|
|
40
|
-
<div class="error">{error}</div>
|
|
29
|
+
<h3>{text('AccessControlDialog.title')}</h3>
|
|
41
30
|
{/if}
|
|
42
31
|
|
|
43
32
|
<div class="AccessControl text">
|
|
@@ -46,7 +35,7 @@
|
|
|
46
35
|
{:else if item}
|
|
47
36
|
{#await userInfo(item.userId) then user}<UserCard {user} />{:catch}<i>{text('generic.unknown')}</i>{/await}
|
|
48
37
|
{/if}
|
|
49
|
-
<span>{text('
|
|
38
|
+
<span>{text('AccessControlDialog.owner')}</span>
|
|
50
39
|
</div>
|
|
51
40
|
|
|
52
41
|
{#each acl as control, i (control.userId || control.role || control.tag)}
|
|
@@ -55,13 +44,13 @@
|
|
|
55
44
|
const updated = await updateACL(itemType, item.id, getTarget(control), { [key]: e.currentTarget.checked });
|
|
56
45
|
Object.assign(control, updated);
|
|
57
46
|
} catch (e) {
|
|
58
|
-
error
|
|
47
|
+
toast('error', e);
|
|
59
48
|
}
|
|
60
49
|
}}
|
|
61
50
|
<div class="AccessControl">
|
|
62
51
|
<div class="target">
|
|
63
|
-
{#if control.
|
|
64
|
-
<UserCard user={control.user} />
|
|
52
|
+
{#if control.userId}
|
|
53
|
+
<UserCard user={(control.user ||= await userInfo(control.userId))} />
|
|
65
54
|
{:else if control.role}
|
|
66
55
|
<span class="icon-text">
|
|
67
56
|
<Icon i="at" />
|
|
@@ -73,7 +62,7 @@
|
|
|
73
62
|
<span>{control.tag}</span>
|
|
74
63
|
</span>
|
|
75
64
|
{:else}
|
|
76
|
-
<
|
|
65
|
+
<span>{text('AccessControlDialog.public_target')}</span>
|
|
77
66
|
{/if}
|
|
78
67
|
{#if editable}
|
|
79
68
|
<button
|
|
@@ -81,11 +70,11 @@
|
|
|
81
70
|
onclick={() =>
|
|
82
71
|
toastStatus(
|
|
83
72
|
removeFromACL(itemType, item.id, getTarget(control)).then(() => acl.splice(i, 1)),
|
|
84
|
-
text('
|
|
73
|
+
text('AccessControlDialog.toast_removed')
|
|
85
74
|
)}
|
|
86
75
|
>
|
|
87
76
|
<Icon i="user-minus" />
|
|
88
|
-
<span>{text('
|
|
77
|
+
<span>{text('AccessControlDialog.remove')}</span>
|
|
89
78
|
</button>
|
|
90
79
|
{/if}
|
|
91
80
|
</div>
|
|
@@ -108,7 +97,34 @@
|
|
|
108
97
|
</div>
|
|
109
98
|
{/each}
|
|
110
99
|
|
|
111
|
-
|
|
100
|
+
{#if editable}
|
|
101
|
+
<div class="actions">
|
|
102
|
+
{#if !acl.find(c => !c.userId && !c.role && !c.tag)}
|
|
103
|
+
<button
|
|
104
|
+
class="icon-text"
|
|
105
|
+
onclick={() =>
|
|
106
|
+
addToACL(itemType, item.id, null)
|
|
107
|
+
.then(ac => acl.push(ac))
|
|
108
|
+
.catch(e => toast('error', e))}
|
|
109
|
+
>
|
|
110
|
+
<Icon i="globe" />
|
|
111
|
+
<span>{text('AccessControlDialog.add_public')}</span>
|
|
112
|
+
</button>
|
|
113
|
+
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
<UserDiscovery
|
|
116
|
+
onSelect={async (target: AccessTarget) => {
|
|
117
|
+
try {
|
|
118
|
+
const control = await addToACL(itemType, item.id, target);
|
|
119
|
+
if (control.userId) control.user = await userInfo(control.userId);
|
|
120
|
+
acl.push(control);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
toast('error', e);
|
|
123
|
+
}
|
|
124
|
+
}}
|
|
125
|
+
excludeTargets={acl.map(getTarget).filter<string>((t): t is string => !!t)}
|
|
126
|
+
/>
|
|
127
|
+
{/if}
|
|
112
128
|
|
|
113
129
|
<div>
|
|
114
130
|
<button class="done" onclick={() => dialog!.close()}>{text('generic.done')}</button>
|
|
@@ -132,6 +148,12 @@
|
|
|
132
148
|
gap: 0.1em;
|
|
133
149
|
}
|
|
134
150
|
|
|
151
|
+
.actions {
|
|
152
|
+
display: flex;
|
|
153
|
+
gap: 1em;
|
|
154
|
+
align-items: center;
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
.AccessControl {
|
|
136
158
|
display: grid;
|
|
137
159
|
gap: 1em;
|
package/lib/AppMenu.svelte
CHANGED
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
<span>{text('app_name.' + app.id, { $default: app.name })}</span>
|
|
28
28
|
</a>
|
|
29
29
|
{:else}
|
|
30
|
-
<i>{text('
|
|
30
|
+
<i>{text('AppMenu.none')}</i>
|
|
31
31
|
{/each}
|
|
32
32
|
{:catch}
|
|
33
|
-
<i>{text('
|
|
33
|
+
<i>{text('AppMenu.failed')}</i>
|
|
34
34
|
{/await}
|
|
35
35
|
</Popover>
|
package/lib/Login.svelte
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
|
|
19
19
|
<FormDialog bind:dialog submitText={text('generic.login')} {submit} pageMode={fullPage}>
|
|
20
20
|
<div>
|
|
21
|
-
<label for="email">{text('
|
|
21
|
+
<label for="email">{text('Login.email')}</label>
|
|
22
22
|
<input name="email" type="email" required />
|
|
23
23
|
</div>
|
|
24
24
|
{#snippet footer()}
|
|
25
25
|
{#if fullPage}
|
|
26
26
|
<div class="footer">
|
|
27
|
-
<a href="/register">{text('
|
|
27
|
+
<a href="/register">{text('Login.register')}</a>
|
|
28
28
|
</div>
|
|
29
29
|
{/if}
|
|
30
30
|
{/snippet}
|
package/lib/Logout.svelte
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
submitText={text('generic.logout')}
|
|
12
12
|
submit={() => logoutCurrentSession().then(() => (window.location.href = '/'))}
|
|
13
13
|
>
|
|
14
|
-
<p>{text('
|
|
14
|
+
<p>{text('Logout.question')}</p>
|
|
15
15
|
{#if fullPage}
|
|
16
16
|
<button
|
|
17
17
|
command="close"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
onclick={e => {
|
|
20
20
|
e.preventDefault();
|
|
21
21
|
history.back();
|
|
22
|
-
}}>{text('
|
|
22
|
+
}}>{text('Logout.back')}</button
|
|
23
23
|
>
|
|
24
24
|
{/if}
|
|
25
25
|
</FormDialog>
|
package/lib/NumberBar.svelte
CHANGED
package/lib/Register.svelte
CHANGED
|
@@ -14,17 +14,17 @@
|
|
|
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('Register.name')}</label>
|
|
18
18
|
<input name="name" type="text" required />
|
|
19
19
|
</div>
|
|
20
20
|
<div>
|
|
21
|
-
<label for="email">{text('
|
|
21
|
+
<label for="email">{text('Register.email')}</label>
|
|
22
22
|
<input name="email" type="email" required />
|
|
23
23
|
</div>
|
|
24
24
|
{#snippet footer()}
|
|
25
25
|
{#if fullPage}
|
|
26
26
|
<div class="footer">
|
|
27
|
-
<a href="/login">{text('
|
|
27
|
+
<a href="/login">{text('Register.login')}</a>
|
|
28
28
|
</div>
|
|
29
29
|
{/if}
|
|
30
30
|
{/snippet}
|
package/lib/SessionList.svelte
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
<p>
|
|
18
18
|
{session.name ?? `${session.id.slice(0, 4)}...${session.id.slice(-4)}`}
|
|
19
19
|
{#if session.id == currentSession?.id}
|
|
20
|
-
<span class="current">{text('
|
|
20
|
+
<span class="current">{text('SessionList.current')}</span>
|
|
21
21
|
{/if}
|
|
22
22
|
{#if session.elevated}
|
|
23
|
-
<span class="elevated">{text('
|
|
23
|
+
<span class="elevated">{text('SessionList.elevated')}</span>
|
|
24
24
|
{/if}
|
|
25
25
|
</p>
|
|
26
|
-
<p class="timestamp">{text('
|
|
27
|
-
<p class="timestamp">{text('
|
|
26
|
+
<p class="timestamp">{text('SessionList.created', { date: session.created.toLocaleString() })}</p>
|
|
27
|
+
<p class="timestamp">{text('SessionList.expires', { date: session.expires.toLocaleString() })}</p>
|
|
28
28
|
<button command="show-modal" commandfor={'logout-session:' + session.id} class="logout icon-text">
|
|
29
29
|
<Icon i="right-from-bracket" --size="16px" />
|
|
30
30
|
<span class="mobile-only">{text('generic.logout')}</span>
|
|
@@ -39,18 +39,17 @@
|
|
|
39
39
|
}}
|
|
40
40
|
submitText={text('generic.logout')}
|
|
41
41
|
>
|
|
42
|
-
<p>{text('
|
|
42
|
+
<p>{text('SessionList.logout_single')}</p>
|
|
43
43
|
</FormDialog>
|
|
44
44
|
{/each}
|
|
45
|
-
<button command="show-modal" commandfor="logout-all" class="danger inline-button">{text('
|
|
46
|
-
>
|
|
45
|
+
<button command="show-modal" commandfor="logout-all" class="danger inline-button">{text('SessionList.logout_all_trigger')}</button>
|
|
47
46
|
<FormDialog
|
|
48
47
|
id="logout-all"
|
|
49
48
|
submit={() => logoutAll(user.id).then(() => (redirectAfterLogoutAll ? (window.location.href = '/') : null))}
|
|
50
|
-
submitText={text('
|
|
49
|
+
submitText={text('SessionList.logout_all_submit')}
|
|
51
50
|
submitDanger
|
|
52
51
|
>
|
|
53
|
-
<p>{text('
|
|
52
|
+
<p>{text('SessionList.logout_all_question')}</p>
|
|
54
53
|
</FormDialog>
|
|
55
54
|
|
|
56
55
|
<style>
|
package/lib/Upload.svelte
CHANGED
package/lib/UserCard.svelte
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
user,
|
|
8
8
|
compact = false,
|
|
9
9
|
self = false,
|
|
10
|
-
href
|
|
10
|
+
href,
|
|
11
11
|
you = false,
|
|
12
12
|
}: {
|
|
13
13
|
user: Partial<User>;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{/if}
|
|
29
29
|
{user.name}
|
|
30
30
|
{#if self && you}
|
|
31
|
-
<span class="subtle">{text('
|
|
31
|
+
<span class="subtle">{text('UserCard.you')}</span>
|
|
32
32
|
{/if}
|
|
33
33
|
</a>
|
|
34
34
|
|
package/lib/UserDiscovery.svelte
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { text } from '@axium/client';
|
|
3
3
|
import { fetchAPI } from '@axium/client/requests';
|
|
4
4
|
import { getUserImage, type UserPublic } from '@axium/core';
|
|
5
|
-
import { colorHashRGB } from '@axium/core/color';
|
|
6
5
|
import { errorText } from '@axium/core/io';
|
|
7
6
|
import Icon from './Icon.svelte';
|
|
8
7
|
|
|
@@ -56,7 +55,7 @@
|
|
|
56
55
|
}
|
|
57
56
|
</script>
|
|
58
57
|
|
|
59
|
-
<input bind:value type="text" placeholder={text('
|
|
58
|
+
<input bind:value type="text" placeholder={text('UserDiscovery.placeholder')} {oninput} />
|
|
60
59
|
{#if !gotError && value}
|
|
61
60
|
<!-- Don't show results when we can't use the discovery API -->
|
|
62
61
|
<div class="results">
|
|
@@ -79,7 +78,7 @@
|
|
|
79
78
|
</div>
|
|
80
79
|
{/if}
|
|
81
80
|
{:else}
|
|
82
|
-
<i>{text('
|
|
81
|
+
<i>{text('UserDiscovery.no_results')}</i>
|
|
83
82
|
{/each}
|
|
84
83
|
</div>
|
|
85
84
|
{/if}
|
package/lib/UserMenu.svelte
CHANGED
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
|
|
21
21
|
<a class="menu-item" href="/account">
|
|
22
22
|
<Icon i="user" --size="1.5em" />
|
|
23
|
-
<span>{text('
|
|
23
|
+
<span>{text('UserMenu.account')}</span>
|
|
24
24
|
</a>
|
|
25
25
|
|
|
26
26
|
{#if user.isAdmin}
|
|
27
27
|
<a class="menu-item" href="/admin">
|
|
28
28
|
<Icon i="gear-complex" --size="1.5em" />
|
|
29
|
-
<span>{text('
|
|
29
|
+
<span>{text('UserMenu.admin')}</span>
|
|
30
30
|
</a>
|
|
31
31
|
{/if}
|
|
32
32
|
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
<span>{text('app_name.' + app.id, { $default: app.name })}</span>
|
|
46
46
|
</a>
|
|
47
47
|
{:else}
|
|
48
|
-
<i>{text('
|
|
48
|
+
<i>{text('AppMenu.none')}</i>
|
|
49
49
|
{/each}
|
|
50
50
|
{:catch}
|
|
51
|
-
<i>{text('
|
|
51
|
+
<i>{text('AppMenu.failed')}</i>
|
|
52
52
|
{/await}
|
|
53
53
|
|
|
54
54
|
<button class="menu-item logout reset" command="show-modal" commandfor="logout">
|
package/lib/Version.svelte
CHANGED
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
{#await _latest then latest}
|
|
15
15
|
<span class="latest">
|
|
16
16
|
{#if ltVersion(v, latest)}
|
|
17
|
-
{@html text('
|
|
17
|
+
{@html text('Version.upgrade', { $html: true, latest })}
|
|
18
18
|
{:else}
|
|
19
|
-
{text('
|
|
19
|
+
{text('Version.latest')}
|
|
20
20
|
{/if}
|
|
21
21
|
</span>
|
|
22
22
|
{:catch}
|
|
23
|
-
<span class="latest error">{text('
|
|
23
|
+
<span class="latest error">{text('Version.error')}</span>
|
|
24
24
|
{/await}
|
|
25
25
|
{/if}
|
|
26
26
|
|
package/lib/ZodInput.svelte
CHANGED
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
</div>
|
|
190
190
|
{:else}
|
|
191
191
|
<!-- No idea how to render this -->
|
|
192
|
-
<i class="error">{text('
|
|
192
|
+
<i class="error">{text('ZodInput.invalid_type', { type: JSON.stringify((schema as ZodPref)?.def?.type) })}</i>
|
|
193
193
|
{/if}
|
|
194
194
|
|
|
195
195
|
<style>
|
package/locales/en.json
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
2
|
+
"AccessControlDialog": {
|
|
3
|
+
"named_title": "Permissions for <strong>{name}</strong>",
|
|
4
|
+
"owner": "Owner",
|
|
5
|
+
"title": "Permissions",
|
|
6
|
+
"remove": "Remove",
|
|
7
|
+
"toast_removed": "Removed access",
|
|
8
|
+
"public_target": "Everyone",
|
|
9
|
+
"add_public": "Add Public Access"
|
|
10
|
+
},
|
|
11
|
+
"AppMenu": {
|
|
12
|
+
"failed": "Couldn't load apps.",
|
|
13
|
+
"none": "No apps available."
|
|
14
|
+
},
|
|
15
|
+
"Login": {
|
|
16
|
+
"email": "Email",
|
|
17
|
+
"register": "Register instead"
|
|
18
|
+
},
|
|
19
|
+
"Logout": {
|
|
20
|
+
"back": "Take me back",
|
|
21
|
+
"question": "Are you sure you want to log out?"
|
|
22
|
+
},
|
|
23
|
+
"Register": {
|
|
24
|
+
"email": "Email",
|
|
25
|
+
"login": "Login instead",
|
|
26
|
+
"name": "Display Name"
|
|
27
|
+
},
|
|
28
|
+
"SessionList": {
|
|
29
|
+
"created": "Created {date}",
|
|
30
|
+
"current": "Current",
|
|
31
|
+
"elevated": "Elevated",
|
|
32
|
+
"expires": "Expires {date}",
|
|
33
|
+
"logout_all_question": "Are you sure you want to log out all sessions?",
|
|
34
|
+
"logout_all_submit": "Logout All Sessions",
|
|
35
|
+
"logout_all_trigger": "Logout All",
|
|
36
|
+
"logout_single": "Are you sure you want to log out this session?"
|
|
37
|
+
},
|
|
38
|
+
"Upload": {
|
|
39
|
+
"upload": "Upload"
|
|
40
|
+
},
|
|
41
|
+
"UserCard": {
|
|
42
|
+
"you": "(You)"
|
|
43
|
+
},
|
|
44
|
+
"UserDiscovery": {
|
|
45
|
+
"no_results": "No results",
|
|
46
|
+
"placeholder": "Add users and roles"
|
|
47
|
+
},
|
|
48
|
+
"UserMenu": {
|
|
49
|
+
"account": "Your Account",
|
|
50
|
+
"admin": "Administration"
|
|
51
|
+
},
|
|
52
|
+
"Version": {
|
|
53
|
+
"error": "Latest unknown",
|
|
54
|
+
"latest": "Latest",
|
|
55
|
+
"upgrade": "<span class=\"version\">{latest}</span> available"
|
|
56
|
+
},
|
|
57
|
+
"ZodInput": {
|
|
58
|
+
"invalid_type": "Invalid input type: {type}"
|
|
59
59
|
},
|
|
60
60
|
"generic": {
|
|
61
61
|
"action_irreversible": "This action can't be undone.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"author": "James Prevett <jp@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"build": "tsc"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@axium/core": ">=0.
|
|
48
|
+
"@axium/core": ">=0.23.0",
|
|
49
49
|
"svelte": "^5.36.0",
|
|
50
50
|
"utilium": "^2.8.0",
|
|
51
51
|
"zod": "^4.0.5"
|