Files
beavergram/frontend/src/lib/components/MessageVersions.svelte
T

158 lines
4.0 KiB
Svelte

<script lang="ts">
import { Dialog } from "bits-ui";
import { getMediaVersions, listMessageVersions } from "#lib/api/endpoints.js";
import type { MediaVersion, MessageVersion } from "#lib/api/types.js";
import MediaVersionThumb from "#lib/components/MediaVersionThumb.svelte";
import Icon from "#lib/components/ui/Icon.svelte";
import Spinner from "#lib/components/ui/Spinner.svelte";
import { formatFull } from "#lib/format/datetime.js";
import { toasts } from "#lib/stores/toasts.svelte.js";
interface Props {
chatId: number;
messageId: number | null;
open: boolean;
}
let { open = $bindable(), chatId, messageId }: Props = $props();
let versions = $state<MessageVersion[]>([]);
let mediaVersions = $state<MediaVersion[]>([]);
let loading = $state(false);
$effect(() => {
if (open && messageId !== null) {
loading = true;
mediaVersions = [];
const id = messageId;
Promise.all([
listMessageVersions(chatId, id),
getMediaVersions(chatId, id),
])
.then(([text, media]) => {
versions = text;
mediaVersions = media;
})
.catch(() => toasts.error("Failed to load edit history"))
.finally(() => {
loading = false;
});
}
});
</script>
<Dialog.Root bind:open>
<Dialog.Portal>
<Dialog.Overlay class="dialog-overlay" />
<Dialog.Content class="dialog-content">
<header class="dialog-head">
<Dialog.Title class="dialog-title">Edit history</Dialog.Title>
<Dialog.Close aria-label="Close" class="dialog-close">
<Icon name="close" size="1.25rem" />
</Dialog.Close>
</header>
<div class="versions">
{#if loading}
<div class="centered"><Spinner /></div>
{:else if versions.length === 0 && mediaVersions.length === 0}
<p class="empty">No versions recorded.</p>
{:else}
{#if mediaVersions.length > 0}
<div class="media-versions">
<span class="media-label">Media versions</span>
<div class="media-strip">
{#each mediaVersions as media (media.id)}
<MediaVersionThumb version={media} />
{/each}
</div>
</div>
{/if}
{#each versions as version, index (version.observed_at)}
<div class="version">
<div class="version-meta">
<span class="version-label">Version {index + 1}</span>
<span class="version-date"
>{formatFull(version.observed_at)}</span
>
</div>
<div class="version-text">{version.text ?? "(no text)"}</div>
</div>
{/each}
{/if}
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
<style lang="scss">
.versions {
overflow-y: auto;
padding: 0.75rem 1.25rem 1.25rem;
}
.media-versions {
padding-bottom: 0.75rem;
margin-bottom: 0.25rem;
border-bottom: 1px solid var(--color-borders);
}
.media-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.8125rem;
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
}
.media-strip {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.version {
padding: 0.75rem 0;
border-bottom: 1px solid var(--color-borders);
&:last-child {
border-bottom: 0;
}
}
.version-meta {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 0.25rem;
}
.version-label {
font-size: 0.8125rem;
font-weight: var(--font-weight-medium);
color: var(--color-primary);
}
.version-date {
font-size: 0.75rem;
color: var(--color-text-secondary);
}
.version-text {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.centered {
display: flex;
justify-content: center;
padding: 2rem 0;
}
.empty {
padding: 1rem 0;
color: var(--color-text-secondary);
text-align: center;
}
</style>