@axium/client 0.16.6 → 0.17.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 +6 -6
- package/assets/theme.css +2 -1
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/index.js +5 -2
- package/dist/config.d.ts +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/locales.d.ts +249 -0
- package/dist/locales.js +70 -0
- package/dist/requests.d.ts +4 -0
- package/dist/requests.js +9 -0
- package/dist/user.js +1 -3
- package/lib/AccessControlDialog.svelte +8 -9
- package/lib/AppMenu.svelte +5 -4
- package/lib/ColorPicker.svelte +1 -1
- package/lib/FormDialog.svelte +2 -1
- package/lib/Login.svelte +4 -4
- package/lib/Logout.svelte +4 -4
- package/lib/Register.svelte +5 -5
- package/lib/SessionList.svelte +13 -12
- package/lib/SidebarLayout.svelte +1 -2
- package/lib/Upload.svelte +2 -1
- package/lib/UserCard.svelte +3 -2
- package/lib/UserDiscovery.svelte +4 -3
- package/lib/UserMenu.svelte +9 -9
- package/lib/Version.svelte +22 -9
- package/lib/ZodInput.svelte +21 -24
- package/lib/tsconfig.json +1 -1
- package/locales/en.json +213 -0
- package/package.json +7 -5
- package/styles/account.css +1 -1
package/assets/styles.css
CHANGED
|
@@ -54,7 +54,7 @@ input,
|
|
|
54
54
|
button,
|
|
55
55
|
select,
|
|
56
56
|
textarea {
|
|
57
|
-
border:
|
|
57
|
+
border: var(--border-accent);
|
|
58
58
|
background-color: var(--bg-normal);
|
|
59
59
|
font-family: inherit;
|
|
60
60
|
color: hsl(0 0 var(--fg-light));
|
|
@@ -84,7 +84,7 @@ label.checkbox {
|
|
|
84
84
|
cursor: pointer;
|
|
85
85
|
width: 1.5em;
|
|
86
86
|
height: 1.5em;
|
|
87
|
-
border:
|
|
87
|
+
border: var(--border-accent);
|
|
88
88
|
border-radius: 0.5em;
|
|
89
89
|
display: inline-flex;
|
|
90
90
|
justify-content: center;
|
|
@@ -101,7 +101,7 @@ select,
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
::picker(select) {
|
|
104
|
-
border:
|
|
104
|
+
border: var(--border-accent);
|
|
105
105
|
padding: 1em;
|
|
106
106
|
border-radius: 0.5em;
|
|
107
107
|
background-color: var(--bg-menu);
|
|
@@ -158,7 +158,7 @@ progress {
|
|
|
158
158
|
border-radius: 0.5em;
|
|
159
159
|
overflow: hidden;
|
|
160
160
|
background-color: var(--bg-normal);
|
|
161
|
-
border:
|
|
161
|
+
border: var(--border-accent);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
progress::-webkit-progress-bar {
|
|
@@ -212,7 +212,7 @@ input.error {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
.danger {
|
|
215
|
-
border:
|
|
215
|
+
border: var(--border-error);
|
|
216
216
|
background-color: hsl(0 20 calc(var(--bg-light) + var(--light-step)));
|
|
217
217
|
color: hsl(0 33 var(--fg-light));
|
|
218
218
|
accent-color: hsl(0 33 var(--fg-light));
|
|
@@ -321,7 +321,7 @@ h6 {
|
|
|
321
321
|
|
|
322
322
|
.mobile-button {
|
|
323
323
|
border-radius: 0.5em;
|
|
324
|
-
border:
|
|
324
|
+
border: var(--border-accent);
|
|
325
325
|
background-color: var(--bg-normal);
|
|
326
326
|
padding: 0.5em 1em;
|
|
327
327
|
cursor: pointer;
|
package/assets/theme.css
CHANGED
|
@@ -39,5 +39,6 @@ html {
|
|
|
39
39
|
--bg-success: hsl(120 40 var(--bg-light-status));
|
|
40
40
|
--fg-accent: hsl(var(--hue) 15 var(--fg-light));
|
|
41
41
|
--fg-strong: hsl(var(--hue) 25 calc(var(--fg-light) - var(--light-step)));
|
|
42
|
-
--border-accent: hsl(var(--hue) 10 calc(var(--bg-light) + (var(--light-step) * 3)));
|
|
42
|
+
--border-accent: 1px solid hsl(var(--hue) 10 calc(var(--bg-light) + (var(--light-step) * 3)));
|
|
43
|
+
--border-error: 1px solid hsl(0 50 var(--fg-light));
|
|
43
44
|
}
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare function session(): {
|
|
|
21
21
|
emailVerified?: Date | null | undefined;
|
|
22
22
|
image?: string | null | undefined;
|
|
23
23
|
};
|
|
24
|
+
name?: string | null | undefined;
|
|
24
25
|
};
|
|
25
26
|
export declare function loadConfig(safe: boolean): Promise<void>;
|
|
26
27
|
export declare function saveConfig(): void;
|
package/dist/cli/index.js
CHANGED
|
@@ -60,13 +60,16 @@ import { styleText } from 'node:util';
|
|
|
60
60
|
import * as z from 'zod';
|
|
61
61
|
import $pkg from '../../package.json' with { type: 'json' };
|
|
62
62
|
import { config, resolveServerURL } from '../config.js';
|
|
63
|
-
import { prefix, setPrefix, setToken } from '../requests.js';
|
|
63
|
+
import { prefix, setPrefix, setToken, useUserAgent } from '../requests.js';
|
|
64
64
|
import { getCurrentSession, logout } from '../user.js';
|
|
65
65
|
import { loadConfig, saveConfig, updateCache } from './config.js';
|
|
66
|
+
import { capitalize } from 'utilium';
|
|
66
67
|
const safe = z.stringbool().default(false).parse(process.env.SAFE?.toLowerCase()) || process.argv.includes('--safe');
|
|
67
68
|
const debug = z.stringbool().default(false).parse(process.env.DEBUG?.toLowerCase()) || process.argv.includes('--debug');
|
|
68
69
|
if (debug)
|
|
69
70
|
io._setDebugOutput(true);
|
|
71
|
+
const clientUA = `Axium Client/${$pkg.version} (${capitalize(process.platform)}; ${process.arch})`;
|
|
72
|
+
useUserAgent(clientUA);
|
|
70
73
|
await loadConfig(safe);
|
|
71
74
|
process.on('SIGHUP', () => {
|
|
72
75
|
io.info('Reloading configuration due to SIGHUP.');
|
|
@@ -150,7 +153,7 @@ try {
|
|
|
150
153
|
});
|
|
151
154
|
server.on('error', e => io.exit('Failed to start local callback server: ' + e.message, 5));
|
|
152
155
|
const port = await serverReady.promise;
|
|
153
|
-
const authURL = new URL(
|
|
156
|
+
const authURL = new URL(`/login/client?port=${port}&client=${encodeURIComponent(clientUA)}`, url).href;
|
|
154
157
|
console.log('Authenticate by visiting this URL in your browser: ' + styleText('underline', authURL));
|
|
155
158
|
const { token } = await sessionReady.promise.catch(e => io.exit('Failed to obtain session: ' + e, 6));
|
|
156
159
|
setToken(token);
|
package/dist/config.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare const ClientConfig: z.ZodObject<{
|
|
|
7
7
|
session: z.ZodObject<{
|
|
8
8
|
id: z.ZodUUID;
|
|
9
9
|
userId: z.ZodUUID;
|
|
10
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
10
11
|
expires: z.ZodCoercedDate<unknown>;
|
|
11
12
|
created: z.ZodCoercedDate<unknown>;
|
|
12
13
|
elevated: z.ZodBoolean;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { FlattenKeys, GetByString, Split, UnionToIntersection } from 'utilium';
|
|
2
|
+
/**
|
|
3
|
+
* Add translations to a locale.
|
|
4
|
+
* Note that translations are rendered as HTML, only replacements are escaped.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extendLocale(locale: string, data: object): void;
|
|
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
|
+
};
|
|
14
|
+
readonly AppMenu: {
|
|
15
|
+
readonly failed: "Couldn't load apps.";
|
|
16
|
+
readonly none: "No apps available.";
|
|
17
|
+
};
|
|
18
|
+
readonly Login: {
|
|
19
|
+
readonly email: "Email";
|
|
20
|
+
readonly register: "Register instead";
|
|
21
|
+
};
|
|
22
|
+
readonly Logout: {
|
|
23
|
+
readonly back: "Take me back";
|
|
24
|
+
readonly question: "Are you sure you want to log out?";
|
|
25
|
+
};
|
|
26
|
+
readonly Register: {
|
|
27
|
+
readonly email: "Email";
|
|
28
|
+
readonly login: "Login instead";
|
|
29
|
+
readonly name: "Display Name";
|
|
30
|
+
};
|
|
31
|
+
readonly SessionList: {
|
|
32
|
+
readonly created: "Created {date}";
|
|
33
|
+
readonly current: "Current";
|
|
34
|
+
readonly elevated: "Elevated";
|
|
35
|
+
readonly expires: "Expires {date}";
|
|
36
|
+
readonly logout_all_question: "Are you sure you want to log out all sessions?";
|
|
37
|
+
readonly logout_all_submit: "Logout All Sessions";
|
|
38
|
+
readonly logout_all_trigger: "Logout All";
|
|
39
|
+
readonly logout_single: "Are you sure you want to log out this session?";
|
|
40
|
+
};
|
|
41
|
+
readonly Upload: {
|
|
42
|
+
readonly upload: "Upload";
|
|
43
|
+
};
|
|
44
|
+
readonly UserCard: {
|
|
45
|
+
readonly you: "(You)";
|
|
46
|
+
};
|
|
47
|
+
readonly UserDiscovery: {
|
|
48
|
+
readonly no_results: "No results";
|
|
49
|
+
readonly placeholder: "Add users and roles";
|
|
50
|
+
};
|
|
51
|
+
readonly UserMenu: {
|
|
52
|
+
readonly account: "Your Account";
|
|
53
|
+
readonly admin: "Administration";
|
|
54
|
+
};
|
|
55
|
+
readonly Version: {
|
|
56
|
+
readonly error: "Latest unknown";
|
|
57
|
+
readonly latest: "Latest";
|
|
58
|
+
readonly upgrade: "<span class=\"version\">{latest}</span> available";
|
|
59
|
+
};
|
|
60
|
+
readonly ZodInput: {
|
|
61
|
+
readonly invalid_type: "Invalid input type: {type}";
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
readonly generic: {
|
|
65
|
+
readonly action_irreversible: "This action can't be undone.";
|
|
66
|
+
readonly cancel: "Cancel";
|
|
67
|
+
readonly change: "Change";
|
|
68
|
+
readonly create: "Create";
|
|
69
|
+
readonly delete: "Delete";
|
|
70
|
+
readonly done: "Done";
|
|
71
|
+
readonly email: "Email";
|
|
72
|
+
readonly loading: "Loading...";
|
|
73
|
+
readonly login: "Login";
|
|
74
|
+
readonly logout: "Logout";
|
|
75
|
+
readonly no: "No";
|
|
76
|
+
readonly none: "None";
|
|
77
|
+
readonly ok: "Okay";
|
|
78
|
+
readonly preferences: "Preferences";
|
|
79
|
+
readonly register: "Register";
|
|
80
|
+
readonly sessions: "Sessions";
|
|
81
|
+
readonly unknown: "Unknown";
|
|
82
|
+
readonly unnamed: "Unnamed";
|
|
83
|
+
readonly username: "Name";
|
|
84
|
+
readonly yes: "Yes";
|
|
85
|
+
};
|
|
86
|
+
readonly page: {
|
|
87
|
+
readonly account: {
|
|
88
|
+
readonly delete_account: "Delete Account";
|
|
89
|
+
readonly delete_account_confirm: "Are you sure you want to delete your account?";
|
|
90
|
+
readonly edit_email: "Email Address";
|
|
91
|
+
readonly edit_name: "What do you want to be called?";
|
|
92
|
+
readonly email_verified_on: "Email verified on {date}";
|
|
93
|
+
readonly greeting: "Welcome, {name}";
|
|
94
|
+
readonly passkeys: {
|
|
95
|
+
readonly backed_up: "This passkey is backed up";
|
|
96
|
+
readonly create: "Create";
|
|
97
|
+
readonly created: "Created {date}";
|
|
98
|
+
readonly delete: "Delete";
|
|
99
|
+
readonly delete_confirm: "Are you sure you want to delete this passkey?";
|
|
100
|
+
readonly edit_name: "Passkey Name";
|
|
101
|
+
readonly min_one: "You must have at least one passkey";
|
|
102
|
+
readonly multi_device: "Multiple devices";
|
|
103
|
+
readonly name_type_error: "Passkey name must be a string";
|
|
104
|
+
readonly not_backed_up: "This passkey is not backed up";
|
|
105
|
+
readonly rename: "Rename";
|
|
106
|
+
readonly single_device: "Single device";
|
|
107
|
+
readonly title: "Passkeys";
|
|
108
|
+
};
|
|
109
|
+
readonly personal_info: "Personal Information";
|
|
110
|
+
readonly preferences: "Preferences";
|
|
111
|
+
readonly profile_alt: "User profile";
|
|
112
|
+
readonly sessions: "Sessions";
|
|
113
|
+
readonly title: "Your Account";
|
|
114
|
+
readonly user_id: "User ID";
|
|
115
|
+
readonly user_id_hint: "This is your UUID. It can't be changed.";
|
|
116
|
+
readonly verification_sent: "Verification email sent";
|
|
117
|
+
readonly verify: "Verify";
|
|
118
|
+
};
|
|
119
|
+
readonly admin: {
|
|
120
|
+
readonly heading: "Administration";
|
|
121
|
+
readonly audit: {
|
|
122
|
+
readonly any: "Any";
|
|
123
|
+
readonly apply: "Apply";
|
|
124
|
+
readonly error_stack: "Error Stack";
|
|
125
|
+
readonly event_heading: "Audit Event";
|
|
126
|
+
readonly event_title: "Admin — Audit Log Event #{id}";
|
|
127
|
+
readonly extra_data: "Extra Data";
|
|
128
|
+
readonly filter: {
|
|
129
|
+
readonly event: "Event Name:";
|
|
130
|
+
readonly severity: "Minimum Severity:";
|
|
131
|
+
readonly since: "Since:";
|
|
132
|
+
readonly source: "Source:";
|
|
133
|
+
readonly tags: "Tags:";
|
|
134
|
+
readonly until: "Until:";
|
|
135
|
+
readonly user: "User UUID:";
|
|
136
|
+
};
|
|
137
|
+
readonly filters: "Filters";
|
|
138
|
+
readonly heading: "Audit Log";
|
|
139
|
+
readonly invalid_filter: "Invalid Filter:";
|
|
140
|
+
readonly name: "Name";
|
|
141
|
+
readonly no_events: "No audit log events found";
|
|
142
|
+
readonly reset: "Reset";
|
|
143
|
+
readonly severity: "Severity";
|
|
144
|
+
readonly source: "Source";
|
|
145
|
+
readonly tags: "Tags";
|
|
146
|
+
readonly timestamp: "Timestamp";
|
|
147
|
+
readonly title: "Admin — Audit Log";
|
|
148
|
+
readonly user: "User";
|
|
149
|
+
readonly uuid: "UUID";
|
|
150
|
+
};
|
|
151
|
+
readonly config: {
|
|
152
|
+
readonly active: "Active Configuration";
|
|
153
|
+
readonly loaded_files: "Loaded Files";
|
|
154
|
+
readonly title: "Admin — Configuration";
|
|
155
|
+
};
|
|
156
|
+
readonly dashboard: {
|
|
157
|
+
readonly audit_link: "Audit Log";
|
|
158
|
+
readonly config_files: "{count} files loaded.";
|
|
159
|
+
readonly config_link: "Configuration";
|
|
160
|
+
readonly plugins_link: "Plugins";
|
|
161
|
+
readonly plugins_loaded: "{count} plugins loaded.";
|
|
162
|
+
readonly stats: "{users} users, {sessions} sessions, {passkeys} passkeys.";
|
|
163
|
+
readonly title: "Admin — Dashboard";
|
|
164
|
+
readonly users_link: "Users";
|
|
165
|
+
};
|
|
166
|
+
readonly plugins: {
|
|
167
|
+
readonly author: "Author:";
|
|
168
|
+
readonly configuration: "Configuration";
|
|
169
|
+
readonly heading: "Plugins";
|
|
170
|
+
readonly loaded_from: "Loaded from";
|
|
171
|
+
readonly none: "No plugins loaded.";
|
|
172
|
+
readonly provided_apps: "Provided apps:";
|
|
173
|
+
readonly title: "Admin — Plugins";
|
|
174
|
+
};
|
|
175
|
+
readonly users: {
|
|
176
|
+
readonly admin_tag: "Admin";
|
|
177
|
+
readonly administrator: "Administrator";
|
|
178
|
+
readonly attributes: "Attributes";
|
|
179
|
+
readonly audit: "Audit";
|
|
180
|
+
readonly back: "Back to all users";
|
|
181
|
+
readonly create: "Create User";
|
|
182
|
+
readonly created_title: "New User Created";
|
|
183
|
+
readonly created_url: "They can log in using this URL:";
|
|
184
|
+
readonly default_image: "Default";
|
|
185
|
+
readonly delete_confirm: "Are you sure you want to delete this user?";
|
|
186
|
+
readonly delete_user: "Delete User";
|
|
187
|
+
readonly display_name: "Display Name";
|
|
188
|
+
readonly email_not_verified: "not verified";
|
|
189
|
+
readonly email_verified: "verified {date}";
|
|
190
|
+
readonly heading: "Users";
|
|
191
|
+
readonly manage: "Manage";
|
|
192
|
+
readonly manage_heading: "User Management";
|
|
193
|
+
readonly manage_title: "Admin — User Management";
|
|
194
|
+
readonly none: "No users!";
|
|
195
|
+
readonly profile_image: "Profile Image";
|
|
196
|
+
readonly registered: "Registered";
|
|
197
|
+
readonly roles: "Roles";
|
|
198
|
+
readonly suspend: "Suspend";
|
|
199
|
+
readonly suspended: "Suspended";
|
|
200
|
+
readonly tags: "Tags";
|
|
201
|
+
readonly title: "Admin — Users";
|
|
202
|
+
readonly unsuspend: "Unsuspend";
|
|
203
|
+
readonly uuid: "UUID";
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
readonly login: {
|
|
207
|
+
readonly client: {
|
|
208
|
+
readonly authorize: "Authorize";
|
|
209
|
+
readonly confirm: "Are you sure you want to log in to this local client?";
|
|
210
|
+
readonly success: "Login successful! You can close this tab.";
|
|
211
|
+
readonly title: "Local Client Login";
|
|
212
|
+
};
|
|
213
|
+
readonly failed: "Login Failed";
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
readonly preference: {
|
|
217
|
+
readonly debug: "Debug mode";
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Current locale
|
|
222
|
+
*/
|
|
223
|
+
export declare let currentLocale: string;
|
|
224
|
+
type _locale = typeof currentLoaded;
|
|
225
|
+
export interface Locale extends _locale {
|
|
226
|
+
}
|
|
227
|
+
export interface ReplacementOptions {
|
|
228
|
+
$default?: string;
|
|
229
|
+
/** */
|
|
230
|
+
$html?: boolean;
|
|
231
|
+
}
|
|
232
|
+
type _ArgsValue<V extends string[]> = UnionToIntersection<{
|
|
233
|
+
[I in keyof V]: Split<V[I], '}'> extends [infer Name extends string, string] ? {
|
|
234
|
+
[N in Name]: string;
|
|
235
|
+
} : {};
|
|
236
|
+
}[keyof V & number]>;
|
|
237
|
+
type Replacements<K extends string> = ReplacementOptions & (GetByString<Locale, K> extends string ? _ArgsValue<Split<GetByString<Locale, K> & string, '{'>> : Record<string, any>);
|
|
238
|
+
type ReplacementsArgs<K extends string> = {} extends Replacements<K> ? [replacements?: Replacements<K>] : [replacements: Replacements<K>];
|
|
239
|
+
export declare function useLocale(newLocale: string): void;
|
|
240
|
+
export declare function escape(text: string): string;
|
|
241
|
+
/**
|
|
242
|
+
* Get localized text for a given translation key
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* text(`example.translation.key.${dynamicPart}`, { a: 1, b: 2 });
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export declare function text<const K extends string = FlattenKeys<Locale>>(key: K, ...args: ReplacementsArgs<K>): string;
|
|
249
|
+
export {};
|
package/dist/locales.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { debug, error, info, warn } from '@axium/core/io';
|
|
2
|
+
import { deepAssign, getByString } from 'utilium';
|
|
3
|
+
import en from '../locales/en.json' with { type: 'json' };
|
|
4
|
+
const loadedLocales = Object.assign(Object.create(null), { en });
|
|
5
|
+
/**
|
|
6
|
+
* Add translations to a locale.
|
|
7
|
+
* Note that translations are rendered as HTML, only replacements are escaped.
|
|
8
|
+
*/
|
|
9
|
+
export function extendLocale(locale, data) {
|
|
10
|
+
if (!loadedLocales[locale]) {
|
|
11
|
+
info('Adding new locale (no built-in): ' + locale);
|
|
12
|
+
loadedLocales[locale] = {};
|
|
13
|
+
}
|
|
14
|
+
else
|
|
15
|
+
debug('Extending locale: ' + locale);
|
|
16
|
+
deepAssign(loadedLocales[locale], data);
|
|
17
|
+
}
|
|
18
|
+
let currentLoaded = en;
|
|
19
|
+
/**
|
|
20
|
+
* Current locale
|
|
21
|
+
*/
|
|
22
|
+
export let currentLocale = 'en';
|
|
23
|
+
export function useLocale(newLocale) {
|
|
24
|
+
if (!loadedLocales[newLocale])
|
|
25
|
+
throw new Error('Locale is not available: ' + newLocale);
|
|
26
|
+
currentLocale = newLocale;
|
|
27
|
+
currentLoaded = loadedLocales[newLocale];
|
|
28
|
+
}
|
|
29
|
+
const localeReplacement = /\{(\w+)\}/g;
|
|
30
|
+
const escapePattern = /[&<>"']/g;
|
|
31
|
+
const escapes = {
|
|
32
|
+
'&': '&',
|
|
33
|
+
'<': '<',
|
|
34
|
+
'>': '>',
|
|
35
|
+
'"': '"',
|
|
36
|
+
"'": ''',
|
|
37
|
+
};
|
|
38
|
+
export function escape(text) {
|
|
39
|
+
return text.replaceAll(escapePattern, ch => escapes[ch]);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get localized text for a given translation key
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* text(`example.translation.key.${dynamicPart}`, { a: 1, b: 2 });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function text(key, ...args) {
|
|
49
|
+
const values = Object.assign(Object.create(null), args[0]);
|
|
50
|
+
let text = getByString(currentLoaded, key) || values.$default;
|
|
51
|
+
if (!text && currentLocale != 'en') {
|
|
52
|
+
warn(`Missing translation in ${currentLocale}: ` + key);
|
|
53
|
+
text = getByString(en, key);
|
|
54
|
+
}
|
|
55
|
+
if (!text) {
|
|
56
|
+
error('Missing translation for key: ' + key);
|
|
57
|
+
text = `?${key}?`;
|
|
58
|
+
}
|
|
59
|
+
if (typeof text == 'object') {
|
|
60
|
+
error('Invalid translation key: ' + key);
|
|
61
|
+
text = values.$default || `!${key}!`;
|
|
62
|
+
}
|
|
63
|
+
return text.replaceAll(localeReplacement, (_, name) => {
|
|
64
|
+
if (!Object.hasOwn(values, name)) {
|
|
65
|
+
console.error(new Error(`Missing replacement value for ${key}: ${name}`));
|
|
66
|
+
values[name] = `<missing: ${name}>`;
|
|
67
|
+
}
|
|
68
|
+
return values.$html ? escape(String(values[name])) : String(values[name]);
|
|
69
|
+
});
|
|
70
|
+
}
|
package/dist/requests.d.ts
CHANGED
|
@@ -5,4 +5,8 @@ export declare let token: string | null;
|
|
|
5
5
|
export declare function setToken(value: string | null): void;
|
|
6
6
|
export declare let prefix: string;
|
|
7
7
|
export declare function setPrefix(value: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Only for use on non-browser clients, e.g. Node.js
|
|
10
|
+
*/
|
|
11
|
+
export declare function useUserAgent(ua: string | null): void;
|
|
8
12
|
export declare function fetchAPI<const E extends Endpoint, const M extends keyof $API[E] & RequestMethod>(method: M, endpoint: E, data?: RequestBody<M, E>, ...params: APIParameters<E>): Promise<M extends keyof APIValues[E] ? (APIValues[E][M] extends readonly [unknown, infer R] ? R : APIValues[E][M]) : unknown>;
|
package/dist/requests.js
CHANGED
|
@@ -8,6 +8,13 @@ export let prefix = '/api/';
|
|
|
8
8
|
export function setPrefix(value) {
|
|
9
9
|
prefix = value;
|
|
10
10
|
}
|
|
11
|
+
let userAgent = null;
|
|
12
|
+
/**
|
|
13
|
+
* Only for use on non-browser clients, e.g. Node.js
|
|
14
|
+
*/
|
|
15
|
+
export function useUserAgent(ua) {
|
|
16
|
+
userAgent = ua;
|
|
17
|
+
}
|
|
11
18
|
export async function fetchAPI(method, endpoint, data, ...params) {
|
|
12
19
|
const options = {
|
|
13
20
|
method,
|
|
@@ -31,6 +38,8 @@ export async function fetchAPI(method, endpoint, data, ...params) {
|
|
|
31
38
|
: '?' + new URLSearchParams(JSON.parse(JSON.stringify(data))).toString();
|
|
32
39
|
if (token)
|
|
33
40
|
options.headers.Authorization = 'Bearer ' + token;
|
|
41
|
+
if (userAgent)
|
|
42
|
+
options.headers['User-Agent'] = userAgent;
|
|
34
43
|
const parts = [];
|
|
35
44
|
for (const part of endpoint.split('/')) {
|
|
36
45
|
if (!part.startsWith(':')) {
|
package/dist/user.js
CHANGED
|
@@ -112,9 +112,7 @@ export async function createPasskey(userId) {
|
|
|
112
112
|
_checkId(userId);
|
|
113
113
|
const options = await fetchAPI('OPTIONS', 'users/:id/passkeys', {}, userId);
|
|
114
114
|
const response = await startRegistration({ optionsJSON: options });
|
|
115
|
-
|
|
116
|
-
passkey.createdAt = new Date(passkey.createdAt);
|
|
117
|
-
return passkey;
|
|
115
|
+
return await fetchAPI('PUT', 'users/:id/passkeys', response, userId);
|
|
118
116
|
}
|
|
119
117
|
export async function updatePasskey(passkeyId, data) {
|
|
120
118
|
return await fetchAPI('PATCH', 'passkeys/:id', data, passkeyId);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { addToACL, getACL, updateACL } from '@axium/client
|
|
3
|
-
import { userInfo } from '@axium/client/user';
|
|
2
|
+
import { addToACL, getACL, text, updateACL, userInfo } from '@axium/client';
|
|
4
3
|
import type { AccessControllable, AccessTarget, User } from '@axium/core';
|
|
5
|
-
import { getTarget, pickPermissions } from '@axium/core
|
|
4
|
+
import { getTarget, pickPermissions } from '@axium/core';
|
|
6
5
|
import { errorText } from '@axium/core/io';
|
|
7
6
|
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
8
7
|
import Icon from './Icon.svelte';
|
|
@@ -30,9 +29,9 @@
|
|
|
30
29
|
|
|
31
30
|
<dialog bind:this={dialog} {...rest}>
|
|
32
31
|
{#if item.name}
|
|
33
|
-
<h3>
|
|
32
|
+
<h3>{@html text('component.AccessControlDialog.named_title', { $html: true, name: item.name })}</h3>
|
|
34
33
|
{:else}
|
|
35
|
-
<h3>
|
|
34
|
+
<h3>{text('component.AccessControlDialog.title')}</h3>
|
|
36
35
|
{/if}
|
|
37
36
|
|
|
38
37
|
{#if error}
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
{:else if item}
|
|
46
45
|
{#await userInfo(item.userId) then user}<UserCard {user} />{/await}
|
|
47
46
|
{/if}
|
|
48
|
-
<span>
|
|
47
|
+
<span>{text('component.AccessControlDialog.owner')}</span>
|
|
49
48
|
</div>
|
|
50
49
|
|
|
51
50
|
{#each acl as control}
|
|
@@ -66,7 +65,7 @@
|
|
|
66
65
|
<span>{control.role}</span>
|
|
67
66
|
</span>
|
|
68
67
|
{:else}
|
|
69
|
-
<i>
|
|
68
|
+
<i>{text('generic.unknown')}</i>
|
|
70
69
|
{/if}
|
|
71
70
|
<div class="permissions">
|
|
72
71
|
{#each Object.entries(pickPermissions(control) as Record<string, boolean>) as [key, value]}
|
|
@@ -90,7 +89,7 @@
|
|
|
90
89
|
<UserDiscovery {onSelect} excludeTargets={acl.map(getTarget)} />
|
|
91
90
|
|
|
92
91
|
<div>
|
|
93
|
-
<button class="done" onclick={() => dialog!.close()}>
|
|
92
|
+
<button class="done" onclick={() => dialog!.close()}>{text('generic.done')}</button>
|
|
94
93
|
</div>
|
|
95
94
|
</dialog>
|
|
96
95
|
|
|
@@ -119,7 +118,7 @@
|
|
|
119
118
|
padding: 1em 2em;
|
|
120
119
|
|
|
121
120
|
&:not(.public) {
|
|
122
|
-
border-bottom:
|
|
121
|
+
border-bottom: var(--border-accent);
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
</style>
|
package/lib/AppMenu.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client/locales';
|
|
2
3
|
import { fetchAPI } from '@axium/client/requests';
|
|
3
4
|
import Icon from './Icon.svelte';
|
|
4
5
|
import Popover from './Popover.svelte';
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
{/snippet}
|
|
13
14
|
|
|
14
15
|
{#await fetchAPI('GET', 'apps')}
|
|
15
|
-
<i>
|
|
16
|
+
<i>{text('generic.loading')}</i>
|
|
16
17
|
{:then apps}
|
|
17
18
|
{#each apps as app}
|
|
18
19
|
<a class="menu-item" href="/{app.id}">
|
|
@@ -23,12 +24,12 @@
|
|
|
23
24
|
{:else}
|
|
24
25
|
<Icon i="image-circle-xmark" --size="1.5em" />
|
|
25
26
|
{/if}
|
|
26
|
-
<span>{app.name}</span>
|
|
27
|
+
<span>{text('app_name.' + app.id, { $default: app.name })}</span>
|
|
27
28
|
</a>
|
|
28
29
|
{:else}
|
|
29
|
-
<i>
|
|
30
|
+
<i>{text('component.AppMenu.none')}</i>
|
|
30
31
|
{/each}
|
|
31
32
|
{:catch}
|
|
32
|
-
<i>
|
|
33
|
+
<i>{text('component.AppMenu.failed')}</i>
|
|
33
34
|
{/await}
|
|
34
35
|
</Popover>
|
package/lib/ColorPicker.svelte
CHANGED
package/lib/FormDialog.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
2
3
|
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
3
4
|
|
|
4
5
|
let {
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
{@render submitButton()}
|
|
73
74
|
{:else}
|
|
74
75
|
<div class="actions">
|
|
75
|
-
<button type="button" onclick={() => dialog!.close()}>
|
|
76
|
+
<button type="button" onclick={() => dialog!.close()}>{text('generic.cancel')}</button>
|
|
76
77
|
{@render submitButton()}
|
|
77
78
|
</div>
|
|
78
79
|
{/if}
|
package/lib/Login.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { loginByEmail } from '@axium/client
|
|
2
|
+
import { loginByEmail, text } from '@axium/client';
|
|
3
3
|
import FormDialog from './FormDialog.svelte';
|
|
4
4
|
import authRedirect from './auth_redirect.js';
|
|
5
5
|
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
}
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
|
-
<FormDialog bind:dialog submitText=
|
|
19
|
+
<FormDialog bind:dialog submitText={text('generic.login')} {submit} pageMode={fullPage}>
|
|
20
20
|
<div>
|
|
21
|
-
<label for="email">
|
|
21
|
+
<label for="email">{text('component.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">
|
|
27
|
+
<a href="/register">{text('component.Login.register')}</a>
|
|
28
28
|
</div>
|
|
29
29
|
{/if}
|
|
30
30
|
{/snippet}
|
package/lib/Logout.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { logoutCurrentSession } from '@axium/client
|
|
2
|
+
import { logoutCurrentSession, text } from '@axium/client';
|
|
3
3
|
import FormDialog from './FormDialog.svelte';
|
|
4
4
|
|
|
5
5
|
let { fullPage = false }: { fullPage?: boolean } = $props();
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
<FormDialog
|
|
9
9
|
pageMode={fullPage}
|
|
10
10
|
id="logout"
|
|
11
|
-
submitText=
|
|
11
|
+
submitText={text('generic.logout')}
|
|
12
12
|
submit={() => logoutCurrentSession().then(() => (window.location.href = '/'))}
|
|
13
13
|
>
|
|
14
|
-
<p>
|
|
14
|
+
<p>{text('component.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
|
-
}}>
|
|
22
|
+
}}>{text('component.Logout.back')}</button
|
|
23
23
|
>
|
|
24
24
|
{/if}
|
|
25
25
|
</FormDialog>
|