s
This commit is contained in:
@@ -14,7 +14,6 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: black;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Bell } from 'lucide-svelte';
|
import { Bell } from 'lucide-svelte';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { type Notification } from '$lib/types';
|
import { isErrorResponse, type Notification } from '$lib/types';
|
||||||
import * as Popover from '$lib/components/ui/popover';
|
import * as Popover from '$lib/components/ui/popover';
|
||||||
|
|
||||||
import { cn } from '$lib/utils';
|
import { cn } from '$lib/utils';
|
||||||
import { ScrollArea } from './ui/scroll-area';
|
import { ScrollArea } from './ui/scroll-area';
|
||||||
import { Separator } from './ui/separator';
|
import { Separator } from './ui/separator';
|
||||||
|
import { getAllNotifications } from '$lib/api/notification';
|
||||||
|
import { appWebsocket } from '$lib/stores/websocket';
|
||||||
|
|
||||||
export let notifications: Notification[] = [];
|
|
||||||
export let onSeenNotification: (notification: Notification) => void = () => {};
|
export let onSeenNotification: (notification: Notification) => void = () => {};
|
||||||
|
|
||||||
let className = '';
|
let className = '';
|
||||||
@@ -32,6 +33,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: if (open) onNotificationClick();
|
$: if (open) onNotificationClick();
|
||||||
|
|
||||||
|
let notifications: Notification[] = [];
|
||||||
|
|
||||||
|
getAllNotifications().then((data) => {
|
||||||
|
if (!isErrorResponse(data)) {
|
||||||
|
notifications = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appWebsocket.on_autoDispose('createNotification', (data) => {
|
||||||
|
const typedNotification = data as Notification;
|
||||||
|
|
||||||
|
notifications = [typedNotification, ...notifications];
|
||||||
|
});
|
||||||
|
|
||||||
|
appWebsocket.on_autoDispose('seenNotification', (data) => {
|
||||||
|
const typedId = data as { id: number };
|
||||||
|
|
||||||
|
notifications = notifications.map((notification) => {
|
||||||
|
if (notification.id === typedId.id) {
|
||||||
|
notification.seen = true;
|
||||||
|
}
|
||||||
|
return notification;
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Popover.Root bind:open>
|
<Popover.Root bind:open>
|
||||||
|
|||||||
44
src/lib/components/secret-context.svelte
Normal file
44
src/lib/components/secret-context.svelte
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as ContextMenu from '$lib/components/ui/context-menu';
|
||||||
|
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||||
|
import { type Secret } from '$lib/types';
|
||||||
|
import { Trash2 } from 'lucide-svelte';
|
||||||
|
import { deleteSecret } from '$lib/api/secret';
|
||||||
|
|
||||||
|
export let secret: Secret;
|
||||||
|
|
||||||
|
let deleteSecretDialogOpen = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialog.Root bind:open={deleteSecretDialogOpen}>
|
||||||
|
<AlertDialog.Trigger></AlertDialog.Trigger>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Header>
|
||||||
|
<AlertDialog.Title>Are you absolutely sure?</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>
|
||||||
|
This action <strong>cannot</strong> be undone.
|
||||||
|
</AlertDialog.Description>
|
||||||
|
</AlertDialog.Header>
|
||||||
|
<AlertDialog.Footer>
|
||||||
|
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
||||||
|
<AlertDialog.Action on:click={() => deleteSecret(secret.id)}>
|
||||||
|
Continue
|
||||||
|
</AlertDialog.Action>
|
||||||
|
</AlertDialog.Footer>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog.Root>
|
||||||
|
|
||||||
|
<ContextMenu.Root closeOnEscape={true}>
|
||||||
|
<ContextMenu.Trigger>
|
||||||
|
<slot />
|
||||||
|
</ContextMenu.Trigger>
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<ContextMenu.Label>
|
||||||
|
<span class="font-bold">{secret.name}</span>
|
||||||
|
</ContextMenu.Label>
|
||||||
|
<ContextMenu.Item on:click={() => (deleteSecretDialogOpen = true)}>
|
||||||
|
<Trash2 />
|
||||||
|
<span class="ml-2">Delete</span>
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.Content>
|
||||||
|
</ContextMenu.Root>
|
||||||
@@ -11,9 +11,11 @@ function initialTheme() {
|
|||||||
|
|
||||||
export const theme: Writable<'light' | 'dark'> = persisted('theme', initialTheme());
|
export const theme: Writable<'light' | 'dark'> = persisted('theme', initialTheme());
|
||||||
|
|
||||||
theme.subscribe((theme) => {
|
export function updateTheme(theme: 'light' | 'dark') {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
|
|
||||||
if (theme === 'light') document.documentElement.classList.remove('dark');
|
if (theme === 'light') document.documentElement.classList.remove('dark');
|
||||||
else document.documentElement.classList.add('dark');
|
else document.documentElement.classList.add('dark');
|
||||||
});
|
}
|
||||||
|
|
||||||
|
theme.subscribe((value) => updateTheme(value));
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<ScrollArea class="h-full w-full">
|
<ScrollArea class="h-full w-full">
|
||||||
<div
|
<div
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
class="h-full w-full break-words break-all contain-layout"
|
class="h-full w-full break-words break-all contain-layout min-h-14 outline-none"
|
||||||
bind:innerText={content}
|
bind:innerText={content}
|
||||||
on:paste={onPaste}
|
on:paste={onPaste}
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@@ -11,31 +11,6 @@
|
|||||||
import { getAllNotifications, seenNotification } from '$lib/api/notification';
|
import { getAllNotifications, seenNotification } from '$lib/api/notification';
|
||||||
|
|
||||||
export let createChannelForm: SuperValidated<CreateChannelFormSchema>;
|
export let createChannelForm: SuperValidated<CreateChannelFormSchema>;
|
||||||
|
|
||||||
let notifications: Notification[] = [];
|
|
||||||
|
|
||||||
getAllNotifications().then((data) => {
|
|
||||||
if (!isErrorResponse(data)) {
|
|
||||||
notifications = data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
appWebsocket.on_autoDispose('createNotification', (data) => {
|
|
||||||
const typedNotification = data as Notification;
|
|
||||||
|
|
||||||
notifications = [typedNotification, ...notifications];
|
|
||||||
});
|
|
||||||
|
|
||||||
appWebsocket.on_autoDispose('seenNotification', (data) => {
|
|
||||||
const typedId = data as { id: number };
|
|
||||||
|
|
||||||
notifications = notifications.map((notification) => {
|
|
||||||
if (notification.id === typedId.id) {
|
|
||||||
notification.seen = true;
|
|
||||||
}
|
|
||||||
return notification;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-4 my-3 flex">
|
<div class="mx-4 my-3 flex">
|
||||||
@@ -49,7 +24,6 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Notifications
|
<Notifications
|
||||||
{notifications}
|
|
||||||
onSeenNotification={(notification) => seenNotification(notification.id)}
|
onSeenNotification={(notification) => seenNotification(notification.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
export const addUserToChannelFormSchema = z.object({
|
export const addUserToChannelFormSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, { message: 'Name must be at least 6 characters' })
|
.min(6, 'Name must be at least 6 characters')
|
||||||
.regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, {
|
.regex(
|
||||||
message: 'Name can only contain letters, numbers, and underscores'
|
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
|
||||||
})
|
'Name can only contain letters, numbers, and underscores'
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AddUserToChannelFormSchema = z.infer<typeof addUserToChannelFormSchema>;
|
export type AddUserToChannelFormSchema = z.infer<typeof addUserToChannelFormSchema>;
|
||||||
@@ -36,6 +37,4 @@
|
|||||||
const { form: formData, enhance } = form;
|
const { form: formData, enhance } = form;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
export const createChannelFormSchema = z.object({
|
export const createChannelFormSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, { message: 'Name must be at least 6 characters' })
|
.min(6, 'Name must be at least 6 characters')
|
||||||
.regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, {
|
.regex(
|
||||||
message: 'Name can only contain letters, numbers, and underscores'
|
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
|
||||||
})
|
'Name can only contain letters, numbers, and underscores'
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CreateChannelFormSchema = z.infer<typeof createChannelFormSchema>;
|
export type CreateChannelFormSchema = z.infer<typeof createChannelFormSchema>;
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import type { Secret } from '$lib/types';
|
import type { Secret } from '$lib/types';
|
||||||
|
import SecretContext from '$lib/components/secret-context.svelte';
|
||||||
|
|
||||||
export let secret: Secret;
|
export let secret: Secret;
|
||||||
export let selected: boolean = false;
|
export let selected: boolean = false;
|
||||||
export let onClick: (secret: Secret) => void = () => {};
|
export let onClick: (secret: Secret) => void = () => {};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<SecretContext {secret}>
|
||||||
<Button
|
<div>
|
||||||
on:click={() => onClick(secret)}
|
<Button
|
||||||
variant={selected ? 'default' : 'outline'}
|
on:click={() => onClick(secret)}
|
||||||
class="w-full"
|
variant={selected ? 'default' : 'outline'}
|
||||||
>
|
class="w-full"
|
||||||
{secret.name}
|
>
|
||||||
</Button>
|
{secret.name}
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SecretContext>
|
||||||
|
|||||||
@@ -16,23 +16,27 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex w-full flex-col">
|
<div class="mt-4 h-full w-full">
|
||||||
<ScrollArea class="flex h-full w-full flex-col ">
|
<ScrollArea class="flex h-full w-full flex-col ">
|
||||||
<div class="my-2">
|
<Button
|
||||||
<Button
|
href="/secrets/new"
|
||||||
href="/secrets/new"
|
variant="outline"
|
||||||
variant="outline"
|
class="w-full"
|
||||||
class="w-full"
|
on:click={() => (selectedId = -1)}
|
||||||
on:click={() => (selectedId = -1)}
|
>
|
||||||
>
|
<Plus class="mr-2 h-4 w-4" />
|
||||||
<Plus class="mr-2 h-4 w-4" />
|
New secret
|
||||||
New secret
|
</Button>
|
||||||
</Button>
|
<div class="mt-4 flex w-full flex-col gap-2">
|
||||||
|
{#each secrets as secret}
|
||||||
|
<div class="flex w-full flex-col">
|
||||||
|
<SecretsListItem
|
||||||
|
{secret}
|
||||||
|
selected={selectedId === secret.id}
|
||||||
|
onClick={select}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#each secrets as secret}
|
|
||||||
<div class="my-2">
|
|
||||||
<SecretsListItem {secret} selected={selectedId === secret.id} onClick={select} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
content: z.string().min(1, 'Message is required'),
|
content: z.string().min(1, 'Message is required'),
|
||||||
timeoutSeconds: z.number().int().nonnegative(),
|
timeoutSeconds: z.number().int().nonnegative(),
|
||||||
recipients: z.array(z.number().int().nonnegative())
|
recipients: z.array(z.number().int().nonnegative())
|
||||||
});
|
})
|
||||||
|
.refine((data) => data.recipients.length > 0, 'At least one recipient is required');
|
||||||
|
|
||||||
export type SecretFormSchema = z.infer<typeof secretFormSchema>;
|
export type SecretFormSchema = z.infer<typeof secretFormSchema>;
|
||||||
</script>
|
</script>
|
||||||
@@ -22,12 +23,16 @@
|
|||||||
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
|
import { afterUpdate, beforeUpdate, onMount } from 'svelte';
|
||||||
import { Textarea } from '$lib/components/ui/textarea';
|
import { Textarea } from '$lib/components/ui/textarea';
|
||||||
import { getFollowedUsers } from '$lib/api/user';
|
import { getFollowedUsers } from '$lib/api/user';
|
||||||
|
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||||
|
import * as Avatar from '$lib/components/ui/avatar';
|
||||||
|
import { cn } from '$lib/utils';
|
||||||
|
|
||||||
export let data: SuperValidated<SecretFormSchema>;
|
export let data: SuperValidated<SecretFormSchema>;
|
||||||
export let defaultSecret: Secret | undefined = undefined;
|
export let defaultSecret: Secret | undefined = undefined;
|
||||||
export let followedUsers: User[] = [];
|
export let followedUsers: User[] = [];
|
||||||
export let recipients: User[] = [];
|
export let recipients: User[] = [];
|
||||||
export let reset: boolean = false;
|
export let reset: boolean = false;
|
||||||
|
export let name: string = 'Create Secret';
|
||||||
|
|
||||||
const form = superForm(data, {
|
const form = superForm(data, {
|
||||||
validators: zodClient(secretFormSchema),
|
validators: zodClient(secretFormSchema),
|
||||||
@@ -61,54 +66,105 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form method="post" use:enhance>
|
<div class="flex h-full w-full items-center justify-center">
|
||||||
<Form.Field name="name" {form}>
|
<div
|
||||||
<Form.Control let:attrs>
|
class="max-h-[80%] w-[60%] rounded-lg bg-secondary shadow-md shadow-secondary-foreground contain-content dark:shadow-none"
|
||||||
<Form.Label>Name</Form.Label>
|
>
|
||||||
<Input {...attrs} type="text" placeholder="Name" bind:value={$formData.name} />
|
<ScrollArea class="h-full">
|
||||||
<Form.FieldErrors />
|
<div class="p-4">
|
||||||
</Form.Control>
|
<div>
|
||||||
</Form.Field>
|
{#if name}
|
||||||
<Form.Field name="content" {form}>
|
<h1 class="text-center text-2xl font-bold">{name}</h1>
|
||||||
<Form.Control let:attrs>
|
{/if}
|
||||||
<Form.Label>Message</Form.Label>
|
</div>
|
||||||
<Textarea {...attrs} placeholder="Message" bind:value={$formData.content} />
|
<form method="post" use:enhance class="flex flex-col">
|
||||||
<Form.FieldErrors />
|
<Form.Field name="name" {form}>
|
||||||
</Form.Control>
|
<Form.Control let:attrs>
|
||||||
</Form.Field>
|
<Form.Label>Name</Form.Label>
|
||||||
<Form.Field name="timeoutSeconds" {form}>
|
<Input
|
||||||
<Form.Control let:attrs>
|
{...attrs}
|
||||||
<Form.Label>Timeout</Form.Label>
|
type="text"
|
||||||
<Input {...attrs} type="number" placeholder="Timeout" bind:value={seconds} />
|
placeholder="Name"
|
||||||
<Form.FieldErrors />
|
bind:value={$formData.name}
|
||||||
</Form.Control>
|
/>
|
||||||
</Form.Field>
|
<Form.FieldErrors />
|
||||||
<Form.Fieldset {form} name="recipients">
|
</Form.Control>
|
||||||
<div class="flex flex-col gap-2">
|
</Form.Field>
|
||||||
{#each followedUsers as user}
|
<Form.Field name="content" {form}>
|
||||||
{@const checked = $formData.recipients.includes(user.id)}
|
<Form.Control let:attrs>
|
||||||
<Form.Control let:attrs>
|
<Form.Label>Message</Form.Label>
|
||||||
{@const { name, ...rest } = attrs}
|
<Textarea
|
||||||
<div>
|
{...attrs}
|
||||||
<Checkbox
|
placeholder="Message"
|
||||||
{...rest}
|
bind:value={$formData.content}
|
||||||
{checked}
|
/>
|
||||||
on:click={() => {
|
<Form.FieldErrors />
|
||||||
if (checked) {
|
</Form.Control>
|
||||||
$formData.recipients = $formData.recipients.filter(
|
</Form.Field>
|
||||||
(id) => id !== user.id
|
<Form.Field name="timeoutSeconds" {form}>
|
||||||
);
|
<Form.Control let:attrs>
|
||||||
} else {
|
<Form.Label>Timeout</Form.Label>
|
||||||
$formData.recipients = [...$formData.recipients, user.id];
|
<Input
|
||||||
}
|
{...attrs}
|
||||||
}}
|
type="number"
|
||||||
></Checkbox>
|
placeholder="Timeout"
|
||||||
<input type="checkbox" {name} hidden value={user.id} {checked} />
|
bind:value={seconds}
|
||||||
<span>{user.username}</span>
|
/>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Field>
|
||||||
|
<span class="pb-2 text-sm">Recipients</span>
|
||||||
|
<Form.Fieldset {form} name="recipients">
|
||||||
|
<div class="mb-2 flex flex-row flex-wrap gap-2">
|
||||||
|
{#each followedUsers as user}
|
||||||
|
{@const checked = $formData.recipients.includes(user.id)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={cn(
|
||||||
|
'rounded-lg px-2 py-1 hover:bg-background',
|
||||||
|
checked && 'bg-accent hover:bg-accent'
|
||||||
|
)}
|
||||||
|
on:click={() => {
|
||||||
|
if (checked) {
|
||||||
|
$formData.recipients = $formData.recipients.filter(
|
||||||
|
(id) => id !== user.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$formData.recipients = [
|
||||||
|
...$formData.recipients,
|
||||||
|
user.id
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Control let:attrs>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
{...attrs}
|
||||||
|
hidden
|
||||||
|
value={user.id}
|
||||||
|
{checked}
|
||||||
|
/>
|
||||||
|
<Avatar.Root class="h-6 w-6">
|
||||||
|
<Avatar.Image src={user.avatar} />
|
||||||
|
<Avatar.Fallback>
|
||||||
|
{user.username[0]}
|
||||||
|
</Avatar.Fallback>
|
||||||
|
</Avatar.Root>
|
||||||
|
<span>{user.username}</span>
|
||||||
|
</div>
|
||||||
|
</Form.Control>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</div>
|
||||||
|
</Form.Fieldset>
|
||||||
|
<div class="self-center">
|
||||||
|
<Form.Button type="submit">Save</Form.Button>
|
||||||
</div>
|
</div>
|
||||||
</Form.Control>
|
</form>
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
</ScrollArea>
|
||||||
</Form.Fieldset>
|
</div>
|
||||||
<Form.Button>Save</Form.Button>
|
</div>
|
||||||
</form>
|
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { appWebsocket } from '$lib/stores/websocket';
|
import { appWebsocket } from '$lib/stores/websocket';
|
||||||
import type { Secret } from '$lib/types';
|
import { isErrorResponse, type Secret } from '$lib/types';
|
||||||
|
import { MoveLeft } from 'lucide-svelte';
|
||||||
import type { LayoutData } from './$types';
|
import type { LayoutData } from './$types';
|
||||||
import SecretsList from './(components)/secrets-list.svelte';
|
import SecretsList from './(components)/secrets-list.svelte';
|
||||||
|
import ThemeSwitch from '$lib/components/theme-switch.svelte';
|
||||||
|
import Notifications from '$lib/components/notifications.svelte';
|
||||||
|
import { getAllNotifications } from '$lib/api/notification';
|
||||||
|
import { type Notification } from '$lib/types';
|
||||||
|
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
|
|
||||||
@@ -28,10 +33,18 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full w-full flex-row">
|
<div class="flex h-screen flex-row">
|
||||||
<div class="h-full w-[20%] min-w-[200px] max-w-[400px] space-y-4 rounded-lg bg-secondary p-4">
|
<div class="hidden max-w-[370px] flex-col bg-secondary p-4 sm:w-[50%] md:flex">
|
||||||
<Button variant="outline" href="/channels">Back to channels</Button>
|
<div class="flex h-12 items-center justify-between">
|
||||||
<SecretsList {secrets} />
|
<Button variant="outline" href="/channels"><MoveLeft /></Button>
|
||||||
|
<div>
|
||||||
|
<Notifications />
|
||||||
|
<ThemeSwitch />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<SecretsList {secrets} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { followingUserCache, usersCache } from '$lib/stores/cache';
|
import { usersCache } from '$lib/stores/cache';
|
||||||
import { appWebsocket } from '$lib/stores/websocket';
|
import { appWebsocket } from '$lib/stores/websocket';
|
||||||
import type { Secret } from '$lib/types';
|
import type { Secret } from '$lib/types';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
@@ -61,5 +61,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key secret}
|
{#key secret}
|
||||||
<SecretForm defaultSecret={secret} data={data.form} {followedUsers} {recipients} />
|
<SecretForm
|
||||||
|
name={secret.name}
|
||||||
|
defaultSecret={secret}
|
||||||
|
data={data.form}
|
||||||
|
{followedUsers}
|
||||||
|
{recipients}
|
||||||
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
|
||||||
import { theme } from '$lib/stores/theme';
|
import { theme, updateTheme } from '$lib/stores/theme';
|
||||||
import { Toaster } from '$lib/components/ui/sonner';
|
import { Toaster } from '$lib/components/ui/sonner';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
updateTheme($theme);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Toaster theme={$theme} />
|
<Toaster theme={$theme} />
|
||||||
<div class="h-full w-full">
|
<div class="h-screen w-screen">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user