feat(post-card): absorb PostModal into PostCard with click-to-expand

PostCard and PostModal competed for the same data and rendered redundant
chrome (header twice, image grid twice, attachment list twice). The wider
PostCard layout we shipped 2026-05-27 has enough real estate to be the
canonical post surface, so collapse the two into one.

Compact (default) state is unchanged: hero + 3-cell rail + truncated
title + 3/5-line description + attachment count badge. Whole-card click
expands in place. Expanded state shows: full title, mosaic of ALL post
images via PostImageGrid (uncapped, lazy-loaded via getPostFull), full
sanitized-HTML description with paragraph wrapping, attachments as
downloadable pill links. Click the chevron in the header to collapse;
mosaic image clicks open ImageViewer scoped to the post (modalStore's
postImageIds path is preserved — only the comment changed).

Per-card local state — no global modal store. Each PostCard owns its
own expanded ref and lazy-loaded detail; collapsing a card discards
neither (so re-expand is instant after the first fetch).

Deleted: PostModal.vue, postModal.js store. Removed the App.vue mount.
This commit is contained in:
2026-05-27 14:30:04 -04:00
parent b8ad17c68d
commit 4d2c464045
5 changed files with 212 additions and 300 deletions
+3 -3
View File
@@ -11,9 +11,9 @@ export const useModalStore = defineStore('modal', () => {
const error = ref(null)
// Post-scoped cycle. When set, prev/next cycles within this array
// (used by PostModal's PostImageGrid clicks). When null, prev/next
// falls back to current.value.neighbors (the gallery-store-driven
// /api/gallery/image/<id> neighbors).
// (used by PostCard's expanded-mosaic PostImageGrid clicks). When
// null, prev/next falls back to current.value.neighbors (the
// gallery-store-driven /api/gallery/image/<id> neighbors).
const postImageIds = ref(null)
const postImageIndex = ref(0)
-39
View File
@@ -1,39 +0,0 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { usePostsStore } from './posts.js'
export const usePostModalStore = defineStore('postModal', () => {
const postsStore = usePostsStore()
// Feed-shape on open; upgraded to detail shape (full description +
// uncapped thumbnails) once getPostFull resolves.
const currentPost = ref(null)
const detailLoaded = ref(false)
const error = ref(null)
const isOpen = computed(() => currentPost.value !== null)
async function open (post) {
currentPost.value = post
detailLoaded.value = false
error.value = null
try {
const detail = await postsStore.getPostFull(post.id)
if (currentPost.value?.id === post.id) {
currentPost.value = detail
detailLoaded.value = true
}
} catch (e) {
// Keep feed-shape data; PostModal can still render title + truncated
// description + the up-to-6 feed thumbnails. Just surface the error.
error.value = e.message
}
}
function close () {
currentPost.value = null
detailLoaded.value = false
error.value = null
}
return { currentPost, detailLoaded, error, isOpen, open, close }
})