@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 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);
@@ -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 component: {
9
- readonly AccessControlDialog: {
10
- readonly named_title: "Permissions for <strong>{name}</strong>";
11
- readonly owner: "Owner";
12
- readonly title: "Permissions";
13
- readonly remove: "Remove";
14
- readonly toast_removed: "Removed access";
15
- };
16
- readonly AppMenu: {
17
- readonly failed: "Couldn't load apps.";
18
- readonly none: "No apps available.";
19
- };
20
- readonly Login: {
21
- readonly email: "Email";
22
- readonly register: "Register instead";
23
- };
24
- readonly Logout: {
25
- readonly back: "Take me back";
26
- readonly question: "Are you sure you want to log out?";
27
- };
28
- readonly Register: {
29
- readonly email: "Email";
30
- readonly login: "Login instead";
31
- readonly name: "Display Name";
32
- };
33
- readonly SessionList: {
34
- readonly created: "Created {date}";
35
- readonly current: "Current";
36
- readonly elevated: "Elevated";
37
- readonly expires: "Expires {date}";
38
- readonly logout_all_question: "Are you sure you want to log out all sessions?";
39
- readonly logout_all_submit: "Logout All Sessions";
40
- readonly logout_all_trigger: "Logout All";
41
- readonly logout_single: "Are you sure you want to log out this session?";
42
- };
43
- readonly Upload: {
44
- readonly upload: "Upload";
45
- };
46
- readonly UserCard: {
47
- readonly you: "(You)";
48
- };
49
- readonly UserDiscovery: {
50
- readonly no_results: "No results";
51
- readonly placeholder: "Add users and roles";
52
- };
53
- readonly UserMenu: {
54
- readonly account: "Your Account";
55
- readonly admin: "Administration";
56
- };
57
- readonly Version: {
58
- readonly error: "Latest unknown";
59
- readonly latest: "Latest";
60
- readonly upgrade: "<span class=\"version\">{latest}</span> available";
61
- };
62
- readonly ZodInput: {
63
- readonly invalid_type: "Invalid input type: {type}";
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, noCache?: boolean): Promise<UserPublic & Partial<User>>;
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
- const userCache = new Map();
63
- export async function userInfo(userId, noCache = false) {
63
+ export async function userInfo(userId) {
64
64
  _checkId(userId);
65
- const cached = userCache.get(userId);
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, User } from '@axium/core';
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
- editable: boolean;
13
+ user?: UserPublic;
15
14
  dialog?: HTMLDialogElement;
16
15
  itemType: string;
17
- item: { name?: string; user?: User; id: string } & AccessControllable;
16
+ item: { name?: string; user?: UserPublic; id: string } & AccessControllable;
18
17
  }
19
- let { item, itemType, editable, dialog = $bindable(), ...rest }: Props = $props();
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
- async function onSelect(target: AccessTarget) {
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('component.AccessControlDialog.named_title', { $html: true, name: item.name })}</h3>
27
+ <h3>{@html text('AccessControlDialog.named_title', { $html: true, name: item.name })}</h3>
35
28
  {:else}
36
- <h3>{text('component.AccessControlDialog.title')}</h3>
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('component.AccessControlDialog.owner')}</span>
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 = errorText(e);
47
+ toast('error', e);
59
48
  }
60
49
  }}
61
50
  <div class="AccessControl">
62
51
  <div class="target">
63
- {#if control.user}
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
- <i>{text('generic.unknown')}</i>
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('component.AccessControlDialog.toast_removed')
73
+ text('AccessControlDialog.toast_removed')
85
74
  )}
86
75
  >
87
76
  <Icon i="user-minus" />
88
- <span>{text('component.AccessControlDialog.remove')}</span>
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
- <UserDiscovery {onSelect} excludeTargets={acl.map(getTarget)} />
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;
@@ -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('component.AppMenu.none')}</i>
30
+ <i>{text('AppMenu.none')}</i>
31
31
  {/each}
32
32
  {:catch}
33
- <i>{text('component.AppMenu.failed')}</i>
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('component.Login.email')}</label>
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('component.Login.register')}</a>
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('component.Logout.question')}</p>
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('component.Logout.back')}</button
22
+ }}>{text('Logout.back')}</button
23
23
  >
24
24
  {/if}
25
25
  </FormDialog>
@@ -6,7 +6,7 @@
6
6
  text,
7
7
  }: {
8
8
  min?: number;
9
- max: number | false;
9
+ max?: number | false;
10
10
  value: number;
11
11
  text: string;
12
12
  } = $props();
@@ -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('component.Register.name')}</label>
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('component.Register.email')}</label>
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('component.Register.login')}</a>
27
+ <a href="/login">{text('Register.login')}</a>
28
28
  </div>
29
29
  {/if}
30
30
  {/snippet}
@@ -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('component.SessionList.current')}</span>
20
+ <span class="current">{text('SessionList.current')}</span>
21
21
  {/if}
22
22
  {#if session.elevated}
23
- <span class="elevated">{text('component.SessionList.elevated')}</span>
23
+ <span class="elevated">{text('SessionList.elevated')}</span>
24
24
  {/if}
25
25
  </p>
26
- <p class="timestamp">{text('component.SessionList.created', { date: session.created.toLocaleString() })}</p>
27
- <p class="timestamp">{text('component.SessionList.expires', { date: session.expires.toLocaleString() })}</p>
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('component.SessionList.logout_single')}</p>
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('component.SessionList.logout_all_trigger')}</button
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('component.SessionList.logout_all_submit')}
49
+ submitText={text('SessionList.logout_all_submit')}
51
50
  submitDanger
52
51
  >
53
- <p>{text('component.SessionList.logout_all_question')}</p>
52
+ <p>{text('SessionList.logout_all_question')}</p>
54
53
  </FormDialog>
55
54
 
56
55
  <style>
package/lib/Upload.svelte CHANGED
@@ -46,7 +46,7 @@
46
46
  <Icon i="cloud-arrow-up" />
47
47
  {/if}
48
48
  {:else}
49
- <Icon i="upload" /> {text('component.Upload.upload')}
49
+ <Icon i="upload" /> {text('Upload.upload')}
50
50
  {/each}
51
51
  </label>
52
52
 
@@ -7,7 +7,7 @@
7
7
  user,
8
8
  compact = false,
9
9
  self = false,
10
- href = `/users/${user.id}`,
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('component.UserCard.you')}</span>
31
+ <span class="subtle">{text('UserCard.you')}</span>
32
32
  {/if}
33
33
  </a>
34
34
 
@@ -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('component.UserDiscovery.placeholder')} {oninput} />
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('component.UserDiscovery.no_results')}</i>
81
+ <i>{text('UserDiscovery.no_results')}</i>
83
82
  {/each}
84
83
  </div>
85
84
  {/if}
@@ -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('component.UserMenu.account')}</span>
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('component.UserMenu.admin')}</span>
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('component.AppMenu.none')}</i>
48
+ <i>{text('AppMenu.none')}</i>
49
49
  {/each}
50
50
  {:catch}
51
- <i>{text('component.AppMenu.failed')}</i>
51
+ <i>{text('AppMenu.failed')}</i>
52
52
  {/await}
53
53
 
54
54
  <button class="menu-item logout reset" command="show-modal" commandfor="logout">
@@ -14,13 +14,13 @@
14
14
  {#await _latest then latest}
15
15
  <span class="latest">
16
16
  {#if ltVersion(v, latest)}
17
- {@html text('component.Version.upgrade', { $html: true, latest })}
17
+ {@html text('Version.upgrade', { $html: true, latest })}
18
18
  {:else}
19
- {text('component.Version.latest')}
19
+ {text('Version.latest')}
20
20
  {/if}
21
21
  </span>
22
22
  {:catch}
23
- <span class="latest error">{text('component.Version.error')}</span>
23
+ <span class="latest error">{text('Version.error')}</span>
24
24
  {/await}
25
25
  {/if}
26
26
 
@@ -189,7 +189,7 @@
189
189
  </div>
190
190
  {:else}
191
191
  <!-- No idea how to render this -->
192
- <i class="error">{text('component.ZodInput.invalid_type', { type: JSON.stringify((schema as ZodPref)?.def?.type) })}</i>
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
- "component": {
3
- "AccessControlDialog": {
4
- "named_title": "Permissions for <strong>{name}</strong>",
5
- "owner": "Owner",
6
- "title": "Permissions",
7
- "remove": "Remove",
8
- "toast_removed": "Removed access"
9
- },
10
- "AppMenu": {
11
- "failed": "Couldn't load apps.",
12
- "none": "No apps available."
13
- },
14
- "Login": {
15
- "email": "Email",
16
- "register": "Register instead"
17
- },
18
- "Logout": {
19
- "back": "Take me back",
20
- "question": "Are you sure you want to log out?"
21
- },
22
- "Register": {
23
- "email": "Email",
24
- "login": "Login instead",
25
- "name": "Display Name"
26
- },
27
- "SessionList": {
28
- "created": "Created {date}",
29
- "current": "Current",
30
- "elevated": "Elevated",
31
- "expires": "Expires {date}",
32
- "logout_all_question": "Are you sure you want to log out all sessions?",
33
- "logout_all_submit": "Logout All Sessions",
34
- "logout_all_trigger": "Logout All",
35
- "logout_single": "Are you sure you want to log out this session?"
36
- },
37
- "Upload": {
38
- "upload": "Upload"
39
- },
40
- "UserCard": {
41
- "you": "(You)"
42
- },
43
- "UserDiscovery": {
44
- "no_results": "No results",
45
- "placeholder": "Add users and roles"
46
- },
47
- "UserMenu": {
48
- "account": "Your Account",
49
- "admin": "Administration"
50
- },
51
- "Version": {
52
- "error": "Latest unknown",
53
- "latest": "Latest",
54
- "upgrade": "<span class=\"version\">{latest}</span> available"
55
- },
56
- "ZodInput": {
57
- "invalid_type": "Invalid input type: {type}"
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.19.8",
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.20.3",
48
+ "@axium/core": ">=0.23.0",
49
49
  "svelte": "^5.36.0",
50
50
  "utilium": "^2.8.0",
51
51
  "zod": "^4.0.5"