158 lines
4.0 KiB
Svelte
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>
|