feat: implement attachment management with upload, delete, and permission checks; update serializers and models

This commit is contained in:
Sean Morley
2025-01-19 22:22:03 -05:00
parent e0fa62c1ea
commit 94c3e3d363
15 changed files with 444 additions and 10 deletions

View File

@@ -76,5 +76,21 @@ export const actions: Actions = {
});
let data = await res.json();
return data;
},
attachment: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/attachments/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
return data;
}
};

View File

@@ -12,6 +12,12 @@
return marked(markdown);
};
function deleteAttachment(event: CustomEvent<string>) {
adventure.attachments = adventure.attachments.filter(
(attachment) => attachment.id !== event.detail
);
}
export let data: PageData;
console.log(data);
@@ -30,6 +36,7 @@
import ClipboardList from '~icons/mdi/clipboard-list';
import AdventureModal from '$lib/components/AdventureModal.svelte';
import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte';
import AttachmentCard from '$lib/components/AttachmentCard.svelte';
onMount(() => {
if (data.props.adventure) {
@@ -380,6 +387,52 @@
</MapLibre>
{/if}
</div>
{#if adventure.attachments && adventure.attachments.length > 0}
<div>
<!-- attachments -->
<h2 class="text-2xl font-bold mt-4">{$t('adventures.attachments')}</h2>
<div class="grid gap-4 mt-4">
{#if adventure.attachments && adventure.attachments.length > 0}
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{#each adventure.attachments as attachment}
<AttachmentCard {attachment} on:delete={deleteAttachment} />
{/each}
</div>
{/if}
</div>
</div>
{/if}
{#if adventure.images && adventure.images.length > 0}
<div>
<h2 class="text-2xl font-bold mt-4">{$t('adventures.images')}</h2>
<div class="grid gap-4 mt-4">
{#if adventure.images && adventure.images.length > 0}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#each adventure.images as image}
<div class="relative">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-missing-attribute -->
<!-- svelte-ignore a11y-missing-content -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="w-full h-48 bg-cover bg-center rounded-lg"
style="background-image: url({image.image})"
on:click={() => (image_url = image.image)}
></div>
{#if image.is_primary}
<div
class="absolute top-0 right-0 bg-primary text-white px-2 py-1 rounded-bl-lg"
>
{$t('adventures.primary')}
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>
</div>
{/if}
</div>
</div>
</div>

View File

@@ -21,7 +21,7 @@
x: -50, // Smaller movement for quicker animation
duration: 0.6, // Quicker animation duration
stagger: 0.1, // Faster staggering
ease: 'power2.out'
ease: 'power2.out' // Slightly sharper easing for quicker feel
});
// Stat values with faster reveal and snappier effect
@@ -30,8 +30,8 @@
scale: 0.8, // Slightly less scaling for a snappier effect
duration: 1, // Shorter duration
stagger: 0.2, // Faster staggering
ease: 'power2.out', // Snappier easing
delay: 0.3 // Faster delay for quicker sequencing
ease: 'elastic.out(0.75, 0.5)', // Slightly snappier bounce
delay: 0 // Faster delay for quicker sequencing
});
// Adventure card animations with quicker reveal
@@ -41,7 +41,7 @@
duration: 0.8, // Quicker duration
stagger: 0.1, // Faster staggering
ease: 'power2.out',
delay: 0.6 // Shorter delay for quicker appearance
delay: 0 // Shorter delay for quicker appearance
});
// Inspiration section with faster bounce effect
@@ -50,7 +50,7 @@
scale: 0.7, // Less scale for snappier effect
duration: 1, // Slightly quicker duration
ease: 'elastic.out(0.75, 0.5)', // Snappier bounce
delay: 1 // Reduced delay for quicker animation
delay: 0 // Reduced delay for quicker animation
});
});