Inline composer

This commit is contained in:
Justin Edmund 2025-06-02 04:56:09 -07:00
parent 4407a85dec
commit e7a7e7cd1e
4 changed files with 46 additions and 34 deletions

View file

@ -51,6 +51,7 @@
box-sizing: border-box; box-sizing: border-box;
min-height: 110px; min-height: 110px;
padding: $unit-4x; padding: $unit-4x;
display: flex;
@include breakpoint('phone') { @include breakpoint('phone') {
padding: $unit-3x; padding: $unit-3x;

View file

@ -86,7 +86,7 @@
function switchToEssay() { function switchToEssay() {
const contentParam = content ? encodeURIComponent(JSON.stringify(content)) : '' const contentParam = content ? encodeURIComponent(JSON.stringify(content)) : ''
goto(`/admin/universe/compose?type=essay${contentParam ? `&content=${contentParam}` : ''}`) goto(`/admin/posts/new?type=essay${contentParam ? `&content=${contentParam}` : ''}`)
} }
function generateSlug(title: string): string { function generateSlug(title: string): string {
@ -151,8 +151,8 @@
function handleMediaSelect(media: Media | Media[]) { function handleMediaSelect(media: Media | Media[]) {
const mediaArray = Array.isArray(media) ? media : [media] const mediaArray = Array.isArray(media) ? media : [media]
const currentIds = attachedPhotos.map(p => p.id) const currentIds = attachedPhotos.map((p) => p.id)
const newMedia = mediaArray.filter(m => !currentIds.includes(m.id)) const newMedia = mediaArray.filter((m) => !currentIds.includes(m.id))
attachedPhotos = [...attachedPhotos, ...newMedia] attachedPhotos = [...attachedPhotos, ...newMedia]
} }
@ -161,7 +161,7 @@
} }
function removePhoto(photoId: number) { function removePhoto(photoId: number) {
attachedPhotos = attachedPhotos.filter(p => p.id !== photoId) attachedPhotos = attachedPhotos.filter((p) => p.id !== photoId)
} }
function handlePhotoClick(photo: Media) { function handlePhotoClick(photo: Media) {
@ -176,7 +176,7 @@
function handleMediaUpdate(updatedMedia: Media) { function handleMediaUpdate(updatedMedia: Media) {
// Update the photo in the attachedPhotos array // Update the photo in the attachedPhotos array
attachedPhotos = attachedPhotos.map(photo => attachedPhotos = attachedPhotos.map((photo) =>
photo.id === updatedMedia.id ? updatedMedia : photo photo.id === updatedMedia.id ? updatedMedia : photo
) )
} }
@ -206,7 +206,7 @@
let postData: any = { let postData: any = {
content, content,
status: 'published', status: 'published',
attachedPhotos: attachedPhotos.map(photo => photo.id) attachedPhotos: attachedPhotos.map((photo) => photo.id)
} }
if (postType === 'essay') { if (postType === 'essay') {
@ -234,9 +234,15 @@
} }
try { try {
const auth = localStorage.getItem('admin_auth')
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (auth) {
headers.Authorization = `Basic ${auth}`
}
const response = await fetch('/api/posts', { const response = await fetch('/api/posts', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers,
body: JSON.stringify(postData) body: JSON.stringify(postData)
}) })
@ -330,7 +336,6 @@
</div> </div>
{/if} {/if}
{#if attachedPhotos.length > 0} {#if attachedPhotos.length > 0}
<div class="attached-photos"> <div class="attached-photos">
{#each attachedPhotos as photo} {#each attachedPhotos as photo}
@ -340,11 +345,7 @@
onclick={() => handlePhotoClick(photo)} onclick={() => handlePhotoClick(photo)}
title="View media details" title="View media details"
> >
<img <img src={photo.url} alt={photo.altText || ''} class="photo-preview" />
src={photo.url}
alt={photo.altText || ''}
class="photo-preview"
/>
</button> </button>
<button <button
class="remove-photo" class="remove-photo"
@ -580,7 +581,6 @@
</div> </div>
{/if} {/if}
{#if attachedPhotos.length > 0} {#if attachedPhotos.length > 0}
<div class="attached-photos"> <div class="attached-photos">
{#each attachedPhotos as photo} {#each attachedPhotos as photo}
@ -590,11 +590,7 @@
onclick={() => handlePhotoClick(photo)} onclick={() => handlePhotoClick(photo)}
title="View media details" title="View media details"
> >
<img <img src={photo.url} alt={photo.altText || ''} class="photo-preview" />
src={photo.url}
alt={photo.altText || ''}
class="photo-preview"
/>
</button> </button>
<button <button
class="remove-photo" class="remove-photo"
@ -903,11 +899,8 @@
background: white; background: white;
border-radius: $unit-2x; border-radius: $unit-2x;
border: 1px solid $grey-80; border: 1px solid $grey-80;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
max-width: 800px;
margin: 0 auto;
.composer-body { .composer-body {
display: flex; display: flex;
@ -997,7 +990,7 @@
.photo-item { .photo-item {
position: relative; position: relative;
.photo-button { .photo-button {
border: none; border: none;
background: none; background: none;
@ -1010,7 +1003,7 @@
transform: scale(1.05); transform: scale(1.05);
} }
} }
:global(.photo-preview) { :global(.photo-preview) {
width: 64px; width: 64px;
height: 64px; height: 64px;
@ -1018,7 +1011,7 @@
border-radius: 12px; border-radius: 12px;
display: block; display: block;
} }
.remove-photo { .remove-photo {
position: absolute; position: absolute;
top: -6px; top: -6px;

View file

@ -335,7 +335,7 @@
{viewMode === 'grid' ? '📋' : '🖼️'} {viewMode === 'grid' ? '📋' : '🖼️'}
{viewMode === 'grid' ? 'List' : 'Grid'} {viewMode === 'grid' ? 'List' : 'Grid'}
</Button> </Button>
<Button variant="primary" size="large" onclick={openUploadModal}>Upload Media</Button> <Button variant="primary" size="large" onclick={openUploadModal}>Upload...</Button>
{/snippet} {/snippet}
</AdminHeader> </AdminHeader>
@ -650,7 +650,7 @@
<!-- Media Upload Modal --> <!-- Media Upload Modal -->
<MediaUploadModal <MediaUploadModal
bind:isOpen={isUploadModalOpen} bind:isOpen={isUploadModalOpen}
onClose={() => isUploadModalOpen = false} onClose={() => (isUploadModalOpen = false)}
onUploadComplete={handleUploadComplete} onUploadComplete={handleUploadComplete}
/> />

View file

@ -5,9 +5,9 @@
import AdminHeader from '$lib/components/admin/AdminHeader.svelte' import AdminHeader from '$lib/components/admin/AdminHeader.svelte'
import AdminFilters from '$lib/components/admin/AdminFilters.svelte' import AdminFilters from '$lib/components/admin/AdminFilters.svelte'
import PostListItem from '$lib/components/admin/PostListItem.svelte' import PostListItem from '$lib/components/admin/PostListItem.svelte'
import PostDropdown from '$lib/components/admin/PostDropdown.svelte'
import LoadingSpinner from '$lib/components/admin/LoadingSpinner.svelte' import LoadingSpinner from '$lib/components/admin/LoadingSpinner.svelte'
import Select from '$lib/components/admin/Select.svelte' import Select from '$lib/components/admin/Select.svelte'
import UniverseComposer from '$lib/components/admin/UniverseComposer.svelte'
interface Post { interface Post {
id: number id: number
@ -36,6 +36,9 @@
// Filter state // Filter state
let selectedFilter = $state<string>('all') let selectedFilter = $state<string>('all')
// Composer state
let showInlineComposer = $state(true)
// Create filter options // Create filter options
const filterOptions = $derived([ const filterOptions = $derived([
{ value: 'all', label: 'All posts' }, { value: 'all', label: 'All posts' },
@ -140,18 +143,30 @@
applyFilter() applyFilter()
} }
function handleComposerSaved() {
// Reload posts when a new post is created
loadPosts()
}
</script> </script>
<AdminPage> <AdminPage>
<AdminHeader title="Universe" slot="header"> <AdminHeader title="Universe" slot="header" />
{#snippet actions()}
<PostDropdown />
{/snippet}
</AdminHeader>
{#if error} {#if error}
<div class="error-message">{error}</div> <div class="error-message">{error}</div>
{:else} {:else}
<!-- Inline Composer -->
{#if showInlineComposer}
<div class="composer-section">
<UniverseComposer
isOpen={true}
initialMode="page"
initialPostType="post"
on:saved={handleComposerSaved}
/>
</div>
{/if}
<!-- Filters --> <!-- Filters -->
<AdminFilters> <AdminFilters>
{#snippet left()} {#snippet left()}
@ -241,5 +256,8 @@
gap: $unit-2x; gap: $unit-2x;
} }
.composer-section {
margin-bottom: $unit-4x;
padding: 0 $unit;
}
</style> </style>