@axium/client 0.16.7 → 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/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/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 +12 -11
- 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 +6 -4
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/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/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>
|
package/lib/Register.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { register } from '@axium/client
|
|
2
|
+
import { register, text } from '@axium/client';
|
|
3
3
|
import FormDialog from './FormDialog.svelte';
|
|
4
4
|
import authRedirect from './auth_redirect.js';
|
|
5
5
|
|
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
}
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
|
-
<FormDialog bind:dialog submitText=
|
|
15
|
+
<FormDialog bind:dialog submitText={text('generic.register')} {submit} pageMode={fullPage}>
|
|
16
16
|
<div>
|
|
17
|
-
<label for="name">
|
|
17
|
+
<label for="name">{text('component.Register.name')}</label>
|
|
18
18
|
<input name="name" type="text" required />
|
|
19
19
|
</div>
|
|
20
20
|
<div>
|
|
21
|
-
<label for="email">
|
|
21
|
+
<label for="email">{text('component.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">
|
|
27
|
+
<a href="/login">{text('component.Register.login')}</a>
|
|
28
28
|
</div>
|
|
29
29
|
{/if}
|
|
30
30
|
{/snippet}
|
package/lib/SessionList.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { logout, logoutAll } from '@axium/client
|
|
2
|
+
import { logout, logoutAll, text } from '@axium/client';
|
|
3
3
|
import type { Session, User } from '@axium/core';
|
|
4
4
|
import FormDialog from './FormDialog.svelte';
|
|
5
5
|
import Icon from './Icon.svelte';
|
|
@@ -17,17 +17,17 @@
|
|
|
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">
|
|
20
|
+
<span class="current">{text('component.SessionList.current')}</span>
|
|
21
21
|
{/if}
|
|
22
22
|
{#if session.elevated}
|
|
23
|
-
<span class="elevated">
|
|
23
|
+
<span class="elevated">{text('component.SessionList.elevated')}</span>
|
|
24
24
|
{/if}
|
|
25
25
|
</p>
|
|
26
|
-
<p class="timestamp">
|
|
27
|
-
<p class="timestamp">
|
|
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>
|
|
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
|
-
<span class="mobile-only">
|
|
30
|
+
<span class="mobile-only">{text('generic.logout')}</span>
|
|
31
31
|
</button>
|
|
32
32
|
</div>
|
|
33
33
|
<FormDialog
|
|
@@ -37,19 +37,20 @@
|
|
|
37
37
|
sessions.splice(sessions.indexOf(session), 1);
|
|
38
38
|
if (session.id == currentSession?.id) window.location.href = '/';
|
|
39
39
|
}}
|
|
40
|
-
submitText=
|
|
40
|
+
submitText={text('generic.logout')}
|
|
41
41
|
>
|
|
42
|
-
<p>
|
|
42
|
+
<p>{text('component.SessionList.logout_single')}</p>
|
|
43
43
|
</FormDialog>
|
|
44
44
|
{/each}
|
|
45
|
-
<button command="show-modal" commandfor="logout-all" class="danger inline-button">
|
|
45
|
+
<button command="show-modal" commandfor="logout-all" class="danger inline-button">{text('component.SessionList.logout_all_trigger')}</button
|
|
46
|
+
>
|
|
46
47
|
<FormDialog
|
|
47
48
|
id="logout-all"
|
|
48
49
|
submit={() => logoutAll(user.id).then(() => (redirectAfterLogoutAll ? (window.location.href = '/') : null))}
|
|
49
|
-
submitText=
|
|
50
|
+
submitText={text('component.SessionList.logout_all_submit')}
|
|
50
51
|
submitDanger
|
|
51
52
|
>
|
|
52
|
-
<p>
|
|
53
|
+
<p>{text('component.SessionList.logout_all_question')}</p>
|
|
53
54
|
</FormDialog>
|
|
54
55
|
|
|
55
56
|
<style>
|
package/lib/SidebarLayout.svelte
CHANGED
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
<div class="sidebar-container">
|
|
16
16
|
<div class="sidebar">
|
|
17
17
|
{#each tabs as { href, name, icon: i, active }}
|
|
18
|
-
<a {href} class={['item', 'icon-text', active && 'active']}><Icon {i} /> <span class="sidebar-text">{
|
|
19
|
-
>
|
|
18
|
+
<a {href} class={['item', 'icon-text', active && 'active']}><Icon {i} /> <span class="sidebar-text">{name}</span></a>
|
|
20
19
|
{/each}
|
|
21
20
|
|
|
22
21
|
{#if bottom}
|
package/lib/Upload.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
2
3
|
import { forMime } from '@axium/core/icons';
|
|
3
4
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
5
|
import Icon from './Icon.svelte';
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
<Icon i="cloud-arrow-up" />
|
|
46
47
|
{/if}
|
|
47
48
|
{:else}
|
|
48
|
-
<Icon i="upload" /> Upload
|
|
49
|
+
<Icon i="upload" /> {text('component.Upload.upload')}
|
|
49
50
|
{/each}
|
|
50
51
|
</label>
|
|
51
52
|
|
package/lib/UserCard.svelte
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import { text } from '@axium/client';
|
|
3
3
|
import { getUserImage } from '@axium/core';
|
|
4
|
+
import type { User } from '@axium/core/user';
|
|
4
5
|
|
|
5
6
|
const {
|
|
6
7
|
user,
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
{/if}
|
|
28
29
|
{user.name}
|
|
29
30
|
{#if self && you}
|
|
30
|
-
<span class="subtle">(
|
|
31
|
+
<span class="subtle">{text('component.UserCard.you')}</span>
|
|
31
32
|
{/if}
|
|
32
33
|
</a>
|
|
33
34
|
|
package/lib/UserDiscovery.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
2
3
|
import { fetchAPI } from '@axium/client/requests';
|
|
3
4
|
import { getUserImage, type UserPublic } from '@axium/core';
|
|
4
5
|
import { colorHashRGB } from '@axium/core/color';
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
}
|
|
56
57
|
</script>
|
|
57
58
|
|
|
58
|
-
<input bind:value type="text" placeholder=
|
|
59
|
+
<input bind:value type="text" placeholder={text('component.UserDiscovery.placeholder')} {oninput} />
|
|
59
60
|
{#if !gotError && value}
|
|
60
61
|
<!-- Don't show results when we can't use the discovery API -->
|
|
61
62
|
<div class="results">
|
|
@@ -82,7 +83,7 @@
|
|
|
82
83
|
</div>
|
|
83
84
|
{/if}
|
|
84
85
|
{:else}
|
|
85
|
-
<i>
|
|
86
|
+
<i>{text('component.UserDiscovery.no_results')}</i>
|
|
86
87
|
{/each}
|
|
87
88
|
</div>
|
|
88
89
|
{/if}
|
|
@@ -114,7 +115,7 @@
|
|
|
114
115
|
background-color: var(--bg-accent);
|
|
115
116
|
border-radius: 0.25em 0.25em 0.75em 0.75em;
|
|
116
117
|
padding: 1em;
|
|
117
|
-
border:
|
|
118
|
+
border: var(--border-accent);
|
|
118
119
|
align-items: stretch;
|
|
119
120
|
|
|
120
121
|
i {
|
package/lib/UserMenu.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { fetchAPI } from '@axium/client
|
|
2
|
+
import { fetchAPI, text } from '@axium/client';
|
|
3
3
|
import { getUserImage } from '@axium/core';
|
|
4
4
|
import type { UserPublic } from '@axium/core/user';
|
|
5
5
|
import Icon from './Icon.svelte';
|
|
@@ -20,18 +20,18 @@
|
|
|
20
20
|
|
|
21
21
|
<a class="menu-item" href="/account">
|
|
22
22
|
<Icon i="user" --size="1.5em" />
|
|
23
|
-
<span>
|
|
23
|
+
<span>{text('component.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>
|
|
29
|
+
<span>{text('component.UserMenu.admin')}</span>
|
|
30
30
|
</a>
|
|
31
31
|
{/if}
|
|
32
32
|
|
|
33
33
|
{#await fetchAPI('GET', 'apps')}
|
|
34
|
-
<i>
|
|
34
|
+
<i>{text('generic.loading')}</i>
|
|
35
35
|
{:then apps}
|
|
36
36
|
{#each apps as app}
|
|
37
37
|
<a class="menu-item" href="/{app.id}">
|
|
@@ -42,18 +42,18 @@
|
|
|
42
42
|
{:else}
|
|
43
43
|
<Icon i="image-circle-xmark" --size="1.5em" />
|
|
44
44
|
{/if}
|
|
45
|
-
<span>{app.name}</span>
|
|
45
|
+
<span>{text('app_name.' + app.id, { $default: app.name })}</span>
|
|
46
46
|
</a>
|
|
47
47
|
{:else}
|
|
48
|
-
<i>
|
|
48
|
+
<i>{text('component.AppMenu.none')}</i>
|
|
49
49
|
{/each}
|
|
50
50
|
{:catch}
|
|
51
|
-
<i>
|
|
51
|
+
<i>{text('component.AppMenu.failed')}</i>
|
|
52
52
|
{/await}
|
|
53
53
|
|
|
54
54
|
<button class="menu-item logout reset" command="show-modal" commandfor="logout">
|
|
55
55
|
<Icon i="right-from-bracket" --size="1.5em" --fill="hsl(0 33 var(--fg-light))" />
|
|
56
|
-
<span>
|
|
56
|
+
<span>{text('generic.logout')}</span>
|
|
57
57
|
</button>
|
|
58
58
|
</Popover>
|
|
59
59
|
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
.UserMenu {
|
|
69
69
|
border-radius: 0.5em;
|
|
70
70
|
padding: 0.5em;
|
|
71
|
-
border:
|
|
71
|
+
border: var(--border-accent);
|
|
72
72
|
cursor: pointer;
|
|
73
73
|
background-color: var(--bg-alt);
|
|
74
74
|
}
|
package/lib/Version.svelte
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import { text } from '@axium/client';
|
|
3
|
+
import { error } from '@axium/core/io';
|
|
3
4
|
import { lt as ltVersion } from 'semver';
|
|
5
|
+
|
|
6
|
+
const { v, latest: _latest }: { v: string; latest?: string | null | Promise<string> } = $props();
|
|
7
|
+
|
|
8
|
+
if (_latest && typeof _latest != 'string') _latest.catch(error);
|
|
4
9
|
</script>
|
|
5
10
|
|
|
6
11
|
<span class="version">{v}</span>
|
|
7
12
|
|
|
8
|
-
{#if
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
{#if _latest}
|
|
14
|
+
{#await _latest then latest}
|
|
15
|
+
<span class="latest">
|
|
16
|
+
{#if ltVersion(v, latest)}
|
|
17
|
+
{@html text('component.Version.upgrade', { $html: true, latest })}
|
|
18
|
+
{:else}
|
|
19
|
+
{text('component.Version.latest')}
|
|
20
|
+
{/if}
|
|
21
|
+
</span>
|
|
22
|
+
{:catch}
|
|
23
|
+
<span class="latest error">{text('component.Version.error')}</span>
|
|
24
|
+
{/await}
|
|
16
25
|
{/if}
|
|
17
26
|
|
|
18
27
|
<style>
|
|
@@ -23,6 +32,10 @@
|
|
|
23
32
|
border-radius: 1em;
|
|
24
33
|
}
|
|
25
34
|
|
|
35
|
+
.error {
|
|
36
|
+
border: var(--border-error);
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
:global(h1 h2, h3, h4, h5, h6) {
|
|
27
40
|
.latest {
|
|
28
41
|
margin-left: 1em;
|
package/lib/ZodInput.svelte
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { text } from '@axium/client';
|
|
2
3
|
import type { ZodPref } from '@axium/core';
|
|
4
|
+
import { zKeys } from '@axium/core/locales';
|
|
3
5
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
6
|
import { getByString, pick, setByString } from 'utilium';
|
|
7
|
+
import { prettifyError } from 'zod';
|
|
5
8
|
import Icon from './Icon.svelte';
|
|
6
9
|
import ZodInput from './ZodInput.svelte';
|
|
7
|
-
import { prettifyError } from 'zod';
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
10
12
|
idPrefix?: string;
|
|
@@ -18,17 +20,10 @@
|
|
|
18
20
|
updateValue(value: any): void;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
let {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
schema,
|
|
26
|
-
optional = false,
|
|
27
|
-
defaultValue,
|
|
28
|
-
idPrefix,
|
|
29
|
-
updateValue,
|
|
30
|
-
noLabel = false,
|
|
31
|
-
}: Props = $props();
|
|
23
|
+
let { rootValue = $bindable(), ...props }: Props = $props();
|
|
24
|
+
|
|
25
|
+
let { label, path, schema, optional, defaultValue, idPrefix, updateValue, noLabel } = props;
|
|
26
|
+
|
|
32
27
|
const id = (idPrefix ? idPrefix + ':' : '') + path.replaceAll(' ', '_');
|
|
33
28
|
|
|
34
29
|
let value = $state<any>(getByString(rootValue, path));
|
|
@@ -81,18 +76,20 @@
|
|
|
81
76
|
if (defaultValue == val) {
|
|
82
77
|
const parts = path.split('.');
|
|
83
78
|
const prop = parts.pop()!;
|
|
84
|
-
delete getByString<Record<string, any>>(rootValue, parts.join('.'))[prop];
|
|
79
|
+
delete getByString<Record<string, any>>(rootValue, parts.join('.'))![prop];
|
|
85
80
|
} else setByString(rootValue, path, val);
|
|
86
81
|
|
|
87
82
|
updateValue(rootValue);
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
const oninput = onchange;
|
|
86
|
+
|
|
87
|
+
const labelText = $derived(zKeys.has(props.schema) ? text(zKeys.get(props.schema)!.key) : label || path);
|
|
91
88
|
</script>
|
|
92
89
|
|
|
93
90
|
{#snippet _in(rest: HTMLInputAttributes)}
|
|
94
91
|
<div class="ZodInput">
|
|
95
|
-
{#if !noLabel}<label for={id}>{
|
|
92
|
+
{#if !noLabel}<label for={id}>{labelText}</label>{/if}
|
|
96
93
|
{#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
|
|
97
94
|
<input {id} {...rest} bind:value {onchange} {oninput} required={!optional} {defaultValue} class={[error && 'error']} />
|
|
98
95
|
</div>
|
|
@@ -106,7 +103,7 @@
|
|
|
106
103
|
{@render _in({ type: 'number', min: Number(schema.minValue), max: Number(schema.maxValue), step: 1 })}
|
|
107
104
|
{:else if schema.type == 'boolean'}
|
|
108
105
|
<div class="ZodInput">
|
|
109
|
-
{#if !noLabel}<label for="{id}:checkbox">{
|
|
106
|
+
{#if !noLabel}<label for="{id}:checkbox">{labelText}</label>{/if}
|
|
110
107
|
<input bind:checked={value} id="{id}:checkbox" type="checkbox" {onchange} required={!optional} />
|
|
111
108
|
<label for="{id}:checkbox" {id} class="checkbox">
|
|
112
109
|
{#if value}<Icon i="check" --size="1.3em" />{/if}
|
|
@@ -122,7 +119,7 @@
|
|
|
122
119
|
<!-- todo -->
|
|
123
120
|
{:else if schema.type == 'literal'}
|
|
124
121
|
<div class="ZodInput">
|
|
125
|
-
<label for={id}>{
|
|
122
|
+
<label for={id}>{labelText}</label>
|
|
126
123
|
<select bind:value {id} {oninput} required={!optional}>
|
|
127
124
|
{#each schema.values as value}
|
|
128
125
|
<option {value} selected={value === value}>{value}</option>
|
|
@@ -132,13 +129,13 @@
|
|
|
132
129
|
{:else if schema.type == 'template_literal'}
|
|
133
130
|
<!-- todo -->
|
|
134
131
|
{:else if schema.type == 'default'}
|
|
135
|
-
<ZodInput bind:rootValue {
|
|
132
|
+
<ZodInput bind:rootValue {...props} label={labelText} schema={schema.def.innerType} defaultValue={schema.def.defaultValue} />
|
|
136
133
|
{:else if schema.type == 'nullable' || schema.type == 'optional'}
|
|
137
134
|
<!-- defaults are handled differently -->
|
|
138
|
-
<ZodInput bind:rootValue {
|
|
135
|
+
<ZodInput bind:rootValue {...props} label={labelText} schema={schema.def.innerType} optional={true} />
|
|
139
136
|
{:else if schema.type == 'array'}
|
|
140
137
|
<div class="ZodInput">
|
|
141
|
-
{#if !noLabel}<label for={id}>{
|
|
138
|
+
{#if !noLabel}<label for={id}>{labelText}</label>{/if}
|
|
142
139
|
{#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
|
|
143
140
|
<div class="ZodInput-array">
|
|
144
141
|
{#each value, i}
|
|
@@ -164,25 +161,25 @@
|
|
|
164
161
|
{#each Object.keys(value) as key}
|
|
165
162
|
<div class="ZodInput-record-entry">
|
|
166
163
|
{#if !noLabel}<label for={id}>{key}</label>{/if}
|
|
167
|
-
<ZodInput bind:rootValue {updateValue} {idPrefix}
|
|
164
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} path="{path}.{key}" schema={schema.valueType} />
|
|
168
165
|
</div>
|
|
169
166
|
{/each}
|
|
170
167
|
</div>
|
|
171
168
|
{:else if schema.type == 'object'}
|
|
172
169
|
<!-- <div class="ZodInput-object"> -->
|
|
173
170
|
{#each Object.entries(schema.shape) as [key, value]}
|
|
174
|
-
<ZodInput bind:rootValue {updateValue} {idPrefix}
|
|
171
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} path="{path}.{key}" schema={value} />
|
|
175
172
|
{/each}
|
|
176
173
|
<!-- </div> -->
|
|
177
174
|
{:else if schema.type == 'tuple'}
|
|
178
175
|
<div class="ZodInput-tuple" data-rest={schema.def.rest}>
|
|
179
176
|
{#each schema.def.items as item, i}
|
|
180
|
-
<ZodInput bind:rootValue {updateValue} {idPrefix} {defaultValue} path="{path}.{i}" schema={item} />
|
|
177
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} defaultValue={defaultValue?.[i]} path="{path}.{i}" schema={item} />
|
|
181
178
|
{/each}
|
|
182
179
|
</div>
|
|
183
180
|
{:else if schema.type == 'enum'}
|
|
184
181
|
<div class="ZodInput">
|
|
185
|
-
{#if !noLabel}<label for={id}>{
|
|
182
|
+
{#if !noLabel}<label for={id}>{labelText}</label>{/if}
|
|
186
183
|
{#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
|
|
187
184
|
<select {id} {onchange} bind:value required={!optional}>
|
|
188
185
|
{#each Object.entries(schema.enum) as [key, value]}
|
|
@@ -192,7 +189,7 @@
|
|
|
192
189
|
</div>
|
|
193
190
|
{:else}
|
|
194
191
|
<!-- No idea how to render this -->
|
|
195
|
-
<i class="error-text">
|
|
192
|
+
<i class="error-text">{text('component.ZodInput.invalid_type', { type: JSON.stringify((schema as ZodPref)?.def?.type) })}</i>
|
|
196
193
|
{/if}
|
|
197
194
|
|
|
198
195
|
<style>
|
package/lib/tsconfig.json
CHANGED
package/locales/en.json
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"component": {
|
|
3
|
+
"AccessControlDialog": {
|
|
4
|
+
"named_title": "Permissions for <strong>{name}</strong>",
|
|
5
|
+
"owner": "Owner",
|
|
6
|
+
"title": "Permissions"
|
|
7
|
+
},
|
|
8
|
+
"AppMenu": {
|
|
9
|
+
"failed": "Couldn't load apps.",
|
|
10
|
+
"none": "No apps available."
|
|
11
|
+
},
|
|
12
|
+
"Login": {
|
|
13
|
+
"email": "Email",
|
|
14
|
+
"register": "Register instead"
|
|
15
|
+
},
|
|
16
|
+
"Logout": {
|
|
17
|
+
"back": "Take me back",
|
|
18
|
+
"question": "Are you sure you want to log out?"
|
|
19
|
+
},
|
|
20
|
+
"Register": {
|
|
21
|
+
"email": "Email",
|
|
22
|
+
"login": "Login instead",
|
|
23
|
+
"name": "Display Name"
|
|
24
|
+
},
|
|
25
|
+
"SessionList": {
|
|
26
|
+
"created": "Created {date}",
|
|
27
|
+
"current": "Current",
|
|
28
|
+
"elevated": "Elevated",
|
|
29
|
+
"expires": "Expires {date}",
|
|
30
|
+
"logout_all_question": "Are you sure you want to log out all sessions?",
|
|
31
|
+
"logout_all_submit": "Logout All Sessions",
|
|
32
|
+
"logout_all_trigger": "Logout All",
|
|
33
|
+
"logout_single": "Are you sure you want to log out this session?"
|
|
34
|
+
},
|
|
35
|
+
"Upload": {
|
|
36
|
+
"upload": "Upload"
|
|
37
|
+
},
|
|
38
|
+
"UserCard": {
|
|
39
|
+
"you": "(You)"
|
|
40
|
+
},
|
|
41
|
+
"UserDiscovery": {
|
|
42
|
+
"no_results": "No results",
|
|
43
|
+
"placeholder": "Add users and roles"
|
|
44
|
+
},
|
|
45
|
+
"UserMenu": {
|
|
46
|
+
"account": "Your Account",
|
|
47
|
+
"admin": "Administration"
|
|
48
|
+
},
|
|
49
|
+
"Version": {
|
|
50
|
+
"error": "Latest unknown",
|
|
51
|
+
"latest": "Latest",
|
|
52
|
+
"upgrade": "<span class=\"version\">{latest}</span> available"
|
|
53
|
+
},
|
|
54
|
+
"ZodInput": {
|
|
55
|
+
"invalid_type": "Invalid input type: {type}"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"generic": {
|
|
59
|
+
"action_irreversible": "This action can't be undone.",
|
|
60
|
+
"cancel": "Cancel",
|
|
61
|
+
"change": "Change",
|
|
62
|
+
"create": "Create",
|
|
63
|
+
"delete": "Delete",
|
|
64
|
+
"done": "Done",
|
|
65
|
+
"email": "Email",
|
|
66
|
+
"loading": "Loading...",
|
|
67
|
+
"login": "Login",
|
|
68
|
+
"logout": "Logout",
|
|
69
|
+
"no": "No",
|
|
70
|
+
"none": "None",
|
|
71
|
+
"ok": "Okay",
|
|
72
|
+
"preferences": "Preferences",
|
|
73
|
+
"register": "Register",
|
|
74
|
+
"sessions": "Sessions",
|
|
75
|
+
"unknown": "Unknown",
|
|
76
|
+
"unnamed": "Unnamed",
|
|
77
|
+
"username": "Name",
|
|
78
|
+
"yes": "Yes"
|
|
79
|
+
},
|
|
80
|
+
"page": {
|
|
81
|
+
"account": {
|
|
82
|
+
"delete_account": "Delete Account",
|
|
83
|
+
"delete_account_confirm": "Are you sure you want to delete your account?",
|
|
84
|
+
"edit_email": "Email Address",
|
|
85
|
+
"edit_name": "What do you want to be called?",
|
|
86
|
+
"email_verified_on": "Email verified on {date}",
|
|
87
|
+
"greeting": "Welcome, {name}",
|
|
88
|
+
"passkeys": {
|
|
89
|
+
"backed_up": "This passkey is backed up",
|
|
90
|
+
"create": "Create",
|
|
91
|
+
"created": "Created {date}",
|
|
92
|
+
"delete": "Delete",
|
|
93
|
+
"delete_confirm": "Are you sure you want to delete this passkey?",
|
|
94
|
+
"edit_name": "Passkey Name",
|
|
95
|
+
"min_one": "You must have at least one passkey",
|
|
96
|
+
"multi_device": "Multiple devices",
|
|
97
|
+
"name_type_error": "Passkey name must be a string",
|
|
98
|
+
"not_backed_up": "This passkey is not backed up",
|
|
99
|
+
"rename": "Rename",
|
|
100
|
+
"single_device": "Single device",
|
|
101
|
+
"title": "Passkeys"
|
|
102
|
+
},
|
|
103
|
+
"personal_info": "Personal Information",
|
|
104
|
+
"preferences": "Preferences",
|
|
105
|
+
"profile_alt": "User profile",
|
|
106
|
+
"sessions": "Sessions",
|
|
107
|
+
"title": "Your Account",
|
|
108
|
+
"user_id": "User ID",
|
|
109
|
+
"user_id_hint": "This is your UUID. It can't be changed.",
|
|
110
|
+
"verification_sent": "Verification email sent",
|
|
111
|
+
"verify": "Verify"
|
|
112
|
+
},
|
|
113
|
+
"admin": {
|
|
114
|
+
"heading": "Administration",
|
|
115
|
+
"audit": {
|
|
116
|
+
"any": "Any",
|
|
117
|
+
"apply": "Apply",
|
|
118
|
+
"error_stack": "Error Stack",
|
|
119
|
+
"event_heading": "Audit Event",
|
|
120
|
+
"event_title": "Admin — Audit Log Event #{id}",
|
|
121
|
+
"extra_data": "Extra Data",
|
|
122
|
+
"filter": {
|
|
123
|
+
"event": "Event Name:",
|
|
124
|
+
"severity": "Minimum Severity:",
|
|
125
|
+
"since": "Since:",
|
|
126
|
+
"source": "Source:",
|
|
127
|
+
"tags": "Tags:",
|
|
128
|
+
"until": "Until:",
|
|
129
|
+
"user": "User UUID:"
|
|
130
|
+
},
|
|
131
|
+
"filters": "Filters",
|
|
132
|
+
"heading": "Audit Log",
|
|
133
|
+
"invalid_filter": "Invalid Filter:",
|
|
134
|
+
"name": "Name",
|
|
135
|
+
"no_events": "No audit log events found",
|
|
136
|
+
"reset": "Reset",
|
|
137
|
+
"severity": "Severity",
|
|
138
|
+
"source": "Source",
|
|
139
|
+
"tags": "Tags",
|
|
140
|
+
"timestamp": "Timestamp",
|
|
141
|
+
"title": "Admin — Audit Log",
|
|
142
|
+
"user": "User",
|
|
143
|
+
"uuid": "UUID"
|
|
144
|
+
},
|
|
145
|
+
"config": {
|
|
146
|
+
"active": "Active Configuration",
|
|
147
|
+
"loaded_files": "Loaded Files",
|
|
148
|
+
"title": "Admin — Configuration"
|
|
149
|
+
},
|
|
150
|
+
"dashboard": {
|
|
151
|
+
"audit_link": "Audit Log",
|
|
152
|
+
"config_files": "{count} files loaded.",
|
|
153
|
+
"config_link": "Configuration",
|
|
154
|
+
"plugins_link": "Plugins",
|
|
155
|
+
"plugins_loaded": "{count} plugins loaded.",
|
|
156
|
+
"stats": "{users} users, {sessions} sessions, {passkeys} passkeys.",
|
|
157
|
+
"title": "Admin — Dashboard",
|
|
158
|
+
"users_link": "Users"
|
|
159
|
+
},
|
|
160
|
+
"plugins": {
|
|
161
|
+
"author": "Author:",
|
|
162
|
+
"configuration": "Configuration",
|
|
163
|
+
"heading": "Plugins",
|
|
164
|
+
"loaded_from": "Loaded from",
|
|
165
|
+
"none": "No plugins loaded.",
|
|
166
|
+
"provided_apps": "Provided apps:",
|
|
167
|
+
"title": "Admin — Plugins"
|
|
168
|
+
},
|
|
169
|
+
"users": {
|
|
170
|
+
"admin_tag": "Admin",
|
|
171
|
+
"administrator": "Administrator",
|
|
172
|
+
"attributes": "Attributes",
|
|
173
|
+
"audit": "Audit",
|
|
174
|
+
"back": "Back to all users",
|
|
175
|
+
"create": "Create User",
|
|
176
|
+
"created_title": "New User Created",
|
|
177
|
+
"created_url": "They can log in using this URL:",
|
|
178
|
+
"default_image": "Default",
|
|
179
|
+
"delete_confirm": "Are you sure you want to delete this user?",
|
|
180
|
+
"delete_user": "Delete User",
|
|
181
|
+
"display_name": "Display Name",
|
|
182
|
+
"email_not_verified": "not verified",
|
|
183
|
+
"email_verified": "verified {date}",
|
|
184
|
+
"heading": "Users",
|
|
185
|
+
"manage": "Manage",
|
|
186
|
+
"manage_heading": "User Management",
|
|
187
|
+
"manage_title": "Admin — User Management",
|
|
188
|
+
"none": "No users!",
|
|
189
|
+
"profile_image": "Profile Image",
|
|
190
|
+
"registered": "Registered",
|
|
191
|
+
"roles": "Roles",
|
|
192
|
+
"suspend": "Suspend",
|
|
193
|
+
"suspended": "Suspended",
|
|
194
|
+
"tags": "Tags",
|
|
195
|
+
"title": "Admin — Users",
|
|
196
|
+
"unsuspend": "Unsuspend",
|
|
197
|
+
"uuid": "UUID"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"login": {
|
|
201
|
+
"client": {
|
|
202
|
+
"authorize": "Authorize",
|
|
203
|
+
"confirm": "Are you sure you want to log in to this local client?",
|
|
204
|
+
"success": "Login successful! You can close this tab.",
|
|
205
|
+
"title": "Local Client Login"
|
|
206
|
+
},
|
|
207
|
+
"failed": "Login Failed"
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"preference": {
|
|
211
|
+
"debug": "Debug mode"
|
|
212
|
+
}
|
|
213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"author": "James Prevett <jp@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
"assets",
|
|
27
27
|
"dist",
|
|
28
28
|
"lib",
|
|
29
|
+
"locales",
|
|
29
30
|
"styles",
|
|
30
31
|
"axium-client.service"
|
|
31
32
|
],
|
|
32
33
|
"exports": {
|
|
34
|
+
"./package.json": "./package.json",
|
|
33
35
|
".": "./dist/index.js",
|
|
34
36
|
"./*": "./dist/*.js",
|
|
35
37
|
"./components": "./lib/index.js",
|
|
@@ -42,9 +44,9 @@
|
|
|
42
44
|
},
|
|
43
45
|
"peerDependencies": {
|
|
44
46
|
"@axium/core": ">=0.20.3",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
47
|
+
"svelte": "^5.36.0",
|
|
48
|
+
"utilium": "^2.8.0",
|
|
49
|
+
"zod": "^4.0.5"
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
52
|
"@simplewebauthn/browser": "^13.1.0",
|