@axium/client 0.14.4 → 0.15.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 +23 -14
- package/dist/requests.js +1 -1
- package/lib/FormDialog.svelte +6 -3
- package/lib/Popover.svelte +14 -8
- package/lib/UserDiscovery.svelte +21 -15
- package/lib/UserMenu.svelte +70 -47
- package/lib/index.ts +1 -0
- package/package.json +1 -1
package/assets/styles.css
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
* {
|
|
4
4
|
box-sizing: border-box;
|
|
5
|
-
color: hsl(0 0 var(--fg-light));
|
|
6
|
-
accent-color: hsl(0 0 var(--fg-light));
|
|
7
|
-
font-family: sans-serif;
|
|
8
5
|
}
|
|
9
6
|
|
|
10
7
|
body {
|
|
@@ -16,6 +13,11 @@ body {
|
|
|
16
13
|
overflow-y: scroll;
|
|
17
14
|
}
|
|
18
15
|
|
|
16
|
+
a {
|
|
17
|
+
text-decoration: none;
|
|
18
|
+
color: inherit;
|
|
19
|
+
}
|
|
20
|
+
|
|
19
21
|
.main {
|
|
20
22
|
padding: 2em;
|
|
21
23
|
border-radius: 1em;
|
|
@@ -57,6 +59,13 @@ select {
|
|
|
57
59
|
padding: 0.5em 1em;
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
button.reset {
|
|
63
|
+
border-radius: unset;
|
|
64
|
+
border: none;
|
|
65
|
+
background-color: unset;
|
|
66
|
+
padding: unset;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
textarea {
|
|
61
70
|
background: var(--bg-normal);
|
|
62
71
|
border: 1px solid var(--border-accent);
|
|
@@ -219,19 +228,19 @@ input.error {
|
|
|
219
228
|
display: flex;
|
|
220
229
|
flex-direction: column;
|
|
221
230
|
gap: 0.1em;
|
|
222
|
-
}
|
|
223
231
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
.menu-item {
|
|
233
|
+
display: inline-flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
padding: 0.5em 0.75em;
|
|
236
|
+
gap: 1em;
|
|
237
|
+
border-radius: 0.5em;
|
|
238
|
+
user-select: none;
|
|
231
239
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
240
|
+
&:hover {
|
|
241
|
+
background-color: var(--bg-strong);
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
}
|
|
235
244
|
}
|
|
236
245
|
}
|
|
237
246
|
|
package/dist/requests.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function fetchAPI(method, endpoint, data, ...params) {
|
|
|
28
28
|
options.body = JSON.stringify(data);
|
|
29
29
|
const search = method != 'GET' || typeof data != 'object' || data == null || !Object.keys(data).length
|
|
30
30
|
? ''
|
|
31
|
-
: '?' + new URLSearchParams(data).toString();
|
|
31
|
+
: '?' + new URLSearchParams(JSON.parse(JSON.stringify(data))).toString();
|
|
32
32
|
if (token)
|
|
33
33
|
options.headers.Authorization = 'Bearer ' + token;
|
|
34
34
|
const parts = [];
|
package/lib/FormDialog.svelte
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
let {
|
|
5
5
|
children,
|
|
6
6
|
dialog = $bindable(),
|
|
7
|
-
submitText = 'Submit',
|
|
8
7
|
cancel = () => {},
|
|
8
|
+
clearOnCancel = false,
|
|
9
|
+
submitText = 'Submit',
|
|
9
10
|
submit = (data: object): Promise<any> => Promise.resolve(),
|
|
10
11
|
pageMode = false,
|
|
11
12
|
submitDanger = false,
|
|
@@ -15,10 +16,11 @@
|
|
|
15
16
|
}: {
|
|
16
17
|
children(): any;
|
|
17
18
|
dialog?: HTMLDialogElement;
|
|
19
|
+
clearOnCancel?: boolean;
|
|
20
|
+
/** A callback for when the dialog is canceled */
|
|
21
|
+
cancel?(): unknown;
|
|
18
22
|
/** Change the text displayed for the submit button */
|
|
19
23
|
submitText?: string;
|
|
20
|
-
/** Basically a callback for when the dialog is canceled */
|
|
21
|
-
cancel?(): unknown;
|
|
22
24
|
/** Called on submission, this should do the actual submission */
|
|
23
25
|
submit?(data: Record<string, FormDataEntryValue>): Promise<any>;
|
|
24
26
|
/** Whether to display the dialog as a full-page form */
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
|
|
37
39
|
function onclose(e: Event) {
|
|
38
40
|
e.preventDefault();
|
|
41
|
+
if (clearOnCancel) dialog!.querySelector('form')!.reset();
|
|
39
42
|
cancel();
|
|
40
43
|
}
|
|
41
44
|
|
package/lib/Popover.svelte
CHANGED
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
}
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
|
-
<div {onclick}>
|
|
14
|
+
<div class="Popover" {onclick} style:display="contents">
|
|
15
15
|
{#if toggle}
|
|
16
16
|
{@render toggle()}
|
|
17
17
|
{:else}
|
|
18
|
-
<span class={[
|
|
18
|
+
<span class={[showToggle == 'hover' && 'toggle-hover']}>
|
|
19
19
|
<Icon i="ellipsis" />
|
|
20
20
|
</span>
|
|
21
21
|
{/if}
|
|
@@ -26,12 +26,17 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<style>
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
div.Popover {
|
|
30
|
+
anchor-scope: --popover;
|
|
31
|
+
|
|
32
|
+
> :global(:first-child:hover) {
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
> :global(:first-child) {
|
|
37
|
+
user-select: none;
|
|
38
|
+
anchor-name: --popover;
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
@media (width > 700px) {
|
|
@@ -44,7 +49,8 @@
|
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
:popover-open {
|
|
53
|
+
position-anchor: --popover;
|
|
48
54
|
position-try-fallbacks:
|
|
49
55
|
flip-inline,
|
|
50
56
|
flip-block,
|
package/lib/UserDiscovery.svelte
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fetchAPI } from '@axium/client/requests';
|
|
3
|
-
import { getUserImage, type
|
|
3
|
+
import { getUserImage, type UserPublic } from '@axium/core';
|
|
4
4
|
import { colorHash } from '@axium/core/color';
|
|
5
|
-
import Icon from './Icon.svelte';
|
|
6
5
|
import { errorText } from '@axium/core/io';
|
|
6
|
+
import Icon from './Icon.svelte';
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
onSelect,
|
|
10
10
|
enableTags = false,
|
|
11
11
|
excludeTargets = [],
|
|
12
|
+
noRoles = false,
|
|
13
|
+
allowExact,
|
|
12
14
|
}: {
|
|
13
|
-
onSelect(target:
|
|
15
|
+
onSelect(target: string): unknown;
|
|
14
16
|
enableTags?: boolean;
|
|
15
17
|
excludeTargets?: string[];
|
|
18
|
+
noRoles?: boolean;
|
|
19
|
+
allowExact?: boolean;
|
|
16
20
|
} = $props();
|
|
17
21
|
|
|
18
|
-
type Result = { type: 'user'; value: UserPublic; target: string } | { type: 'role' | 'tag'; value: string; target: string };
|
|
19
|
-
|
|
22
|
+
type Result = { type: 'user'; value: UserPublic; target: string } | { type: 'role' | 'tag' | 'exact'; value: string; target: string };
|
|
20
23
|
let results = $state<Result[]>([]);
|
|
21
24
|
let value = $state<string>();
|
|
22
25
|
let gotError = $state<boolean>(false);
|
|
@@ -30,8 +33,9 @@
|
|
|
30
33
|
try {
|
|
31
34
|
const users = await fetchAPI('POST', 'users/discover', value);
|
|
32
35
|
results = [
|
|
36
|
+
allowExact && ({ type: 'exact', value, target: value } as const),
|
|
33
37
|
...users.map(value => ({ type: 'user', value, target: value.id }) as const),
|
|
34
|
-
{ type: 'role', value, target: '@' + value } as const,
|
|
38
|
+
!noRoles && ({ type: 'role', value, target: '@' + value } as const),
|
|
35
39
|
enableTags && ({ type: 'tag', value, target: '#' + value } as const),
|
|
36
40
|
].filter<Result>(r => !!r);
|
|
37
41
|
} catch (e) {
|
|
@@ -52,7 +56,7 @@
|
|
|
52
56
|
</script>
|
|
53
57
|
|
|
54
58
|
<input bind:value type="text" placeholder="Add users and roles" {onchange} onkeyup={onchange} />
|
|
55
|
-
{#if !gotError}
|
|
59
|
+
{#if !gotError && value}
|
|
56
60
|
<!-- Don't show results when we can't use the discovery API -->
|
|
57
61
|
<div class="results">
|
|
58
62
|
{#each results as result}
|
|
@@ -72,6 +76,8 @@
|
|
|
72
76
|
><Icon i="hashtag" />{result.value}</span
|
|
73
77
|
>
|
|
74
78
|
</span>
|
|
79
|
+
{:else if result.type == 'exact'}
|
|
80
|
+
<span class="non-user">{result.value}</span>
|
|
75
81
|
{/if}
|
|
76
82
|
</div>
|
|
77
83
|
{/if}
|
|
@@ -119,6 +125,14 @@
|
|
|
119
125
|
.result {
|
|
120
126
|
padding: 0.5em;
|
|
121
127
|
border-radius: 0.5em;
|
|
128
|
+
|
|
129
|
+
.non-user {
|
|
130
|
+
border-radius: 1em;
|
|
131
|
+
padding: 0.25em 0.75em;
|
|
132
|
+
display: inline-flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 0.25em;
|
|
135
|
+
}
|
|
122
136
|
}
|
|
123
137
|
|
|
124
138
|
.result:hover {
|
|
@@ -126,14 +140,6 @@
|
|
|
126
140
|
background-color: var(--bg-strong);
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
.tag-or-role {
|
|
130
|
-
border-radius: 1em;
|
|
131
|
-
padding: 0.25em 0.75em;
|
|
132
|
-
display: inline-flex;
|
|
133
|
-
align-items: center;
|
|
134
|
-
gap: 0.25em;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
143
|
img {
|
|
138
144
|
width: 2em;
|
|
139
145
|
height: 2em;
|
package/lib/UserMenu.svelte
CHANGED
|
@@ -1,66 +1,78 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { User } from '@axium/core/user';
|
|
3
|
-
import { getUserImage } from '@axium/core';
|
|
4
2
|
import { fetchAPI } from '@axium/client/requests';
|
|
3
|
+
import { getUserImage } from '@axium/core';
|
|
4
|
+
import type { UserPublic } from '@axium/core/user';
|
|
5
5
|
import Icon from './Icon.svelte';
|
|
6
|
-
import Popover from './Popover.svelte';
|
|
7
6
|
import Logout from './Logout.svelte';
|
|
7
|
+
import Popover from './Popover.svelte';
|
|
8
8
|
|
|
9
|
-
const { user }: { user
|
|
9
|
+
const { user }: { user?: UserPublic } = $props();
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<a class="menu-item" href="/account">
|
|
21
|
-
<Icon i="user" --size="1.5em" />
|
|
22
|
-
<span>Your Account</span>
|
|
23
|
-
</a>
|
|
12
|
+
{#if user}
|
|
13
|
+
<Popover>
|
|
14
|
+
{#snippet toggle()}
|
|
15
|
+
<div class="UserMenu toggle">
|
|
16
|
+
<img src={getUserImage(user)} alt={user.name} />
|
|
17
|
+
{user.name}
|
|
18
|
+
</div>
|
|
19
|
+
{/snippet}
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
<span>Administration</span>
|
|
21
|
+
<a class="menu-item" href="/account">
|
|
22
|
+
<Icon i="user" --size="1.5em" />
|
|
23
|
+
<span>Your Account</span>
|
|
29
24
|
</a>
|
|
30
|
-
{/if}
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<a class="menu-item" href="/{app.id}">
|
|
37
|
-
{#if app.image}
|
|
38
|
-
<img src={app.image} alt={app.name} width="1em" height="1em" />
|
|
39
|
-
{:else if app.icon}
|
|
40
|
-
<Icon i={app.icon} --size="1.5em" />
|
|
41
|
-
{:else}
|
|
42
|
-
<Icon i="image-circle-xmark" --size="1.5em" />
|
|
43
|
-
{/if}
|
|
44
|
-
<span>{app.name}</span>
|
|
26
|
+
{#if user.isAdmin}
|
|
27
|
+
<a class="menu-item" href="/admin">
|
|
28
|
+
<Icon i="gear-complex" --size="1.5em" />
|
|
29
|
+
<span>Administration</span>
|
|
45
30
|
</a>
|
|
46
|
-
{
|
|
47
|
-
<i>No apps available.</i>
|
|
48
|
-
{/each}
|
|
49
|
-
{:catch}
|
|
50
|
-
<i>Couldn't load apps.</i>
|
|
51
|
-
{/await}
|
|
31
|
+
{/if}
|
|
52
32
|
|
|
53
|
-
|
|
54
|
-
|
|
33
|
+
{#await fetchAPI('GET', 'apps')}
|
|
34
|
+
<i>Loading...</i>
|
|
35
|
+
{:then apps}
|
|
36
|
+
{#each apps as app}
|
|
37
|
+
<a class="menu-item" href="/{app.id}">
|
|
38
|
+
{#if app.image}
|
|
39
|
+
<img src={app.image} alt={app.name} width="1em" height="1em" />
|
|
40
|
+
{:else if app.icon}
|
|
41
|
+
<Icon i={app.icon} --size="1.5em" />
|
|
42
|
+
{:else}
|
|
43
|
+
<Icon i="image-circle-xmark" --size="1.5em" />
|
|
44
|
+
{/if}
|
|
45
|
+
<span>{app.name}</span>
|
|
46
|
+
</a>
|
|
47
|
+
{:else}
|
|
48
|
+
<i>No apps available.</i>
|
|
49
|
+
{/each}
|
|
50
|
+
{:catch}
|
|
51
|
+
<i>Couldn't load apps.</i>
|
|
52
|
+
{/await}
|
|
53
|
+
|
|
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
56
|
<span>Logout</span>
|
|
57
|
-
</
|
|
58
|
-
</
|
|
59
|
-
</Popover>
|
|
57
|
+
</button>
|
|
58
|
+
</Popover>
|
|
60
59
|
|
|
61
|
-
<Logout />
|
|
60
|
+
<Logout />
|
|
61
|
+
{:else}
|
|
62
|
+
<div class="UserMenu login">
|
|
63
|
+
<a href="/login?after={location.pathname}">Login</a>
|
|
64
|
+
</div>
|
|
65
|
+
{/if}
|
|
62
66
|
|
|
63
67
|
<style>
|
|
68
|
+
.UserMenu {
|
|
69
|
+
border-radius: 0.5em;
|
|
70
|
+
padding: 0.5em;
|
|
71
|
+
border: 1px solid var(--border-accent);
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
background-color: var(--bg-alt);
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
img {
|
|
65
77
|
width: 2em;
|
|
66
78
|
height: 2em;
|
|
@@ -69,7 +81,18 @@
|
|
|
69
81
|
margin-right: 0.5em;
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
|
|
84
|
+
.logout {
|
|
73
85
|
color: hsl(0 33 var(--fg-light));
|
|
86
|
+
font-size: 16px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
:global(.UserMenu + div:popover-open) {
|
|
90
|
+
position: fixed;
|
|
91
|
+
left: unset;
|
|
92
|
+
right: anchor(right);
|
|
93
|
+
top: calc(anchor(bottom) + 0.5em);
|
|
94
|
+
width: fit-content;
|
|
95
|
+
height: fit-content;
|
|
96
|
+
cursor: default;
|
|
74
97
|
}
|
|
75
98
|
</style>
|
package/lib/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { default as Toast } from './Toast.svelte';
|
|
|
14
14
|
export { default as Upload } from './Upload.svelte';
|
|
15
15
|
export { default as URLText } from './URLText.svelte';
|
|
16
16
|
export { default as UserCard } from './UserCard.svelte';
|
|
17
|
+
export { default as UserDiscovery } from './UserDiscovery.svelte';
|
|
17
18
|
export { default as UserMenu } from './UserMenu.svelte';
|
|
18
19
|
export { default as Version } from './Version.svelte';
|
|
19
20
|
export { default as ZodForm } from './ZodForm.svelte';
|