diff --git a/frontend/src/stores/modal.js b/frontend/src/stores/modal.js index 1998c97..0f8665f 100644 --- a/frontend/src/stores/modal.js +++ b/frontend/src/stores/modal.js @@ -6,14 +6,31 @@ export const useModalStore = defineStore('modal', () => { const api = useApi() const currentImageId = ref(null) - const current = ref(null) // full image detail from API + const current = ref(null) const loading = ref(false) const error = ref(null) - async function open(id) { + // 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/ neighbors). + const postImageIds = ref(null) + const postImageIndex = ref(0) + + async function open (id, opts = {}) { currentImageId.value = id loading.value = true error.value = null + // Update post-scoped state if caller passed it; otherwise clear so + // the next open() from gallery context uses neighbors mode. + if (opts.postImageIds != null) { + postImageIds.value = opts.postImageIds + postImageIndex.value = opts.postImageIds.indexOf(id) + if (postImageIndex.value < 0) postImageIndex.value = 0 + } else if (opts.clearPostScope !== false && postImageIds.value != null) { + postImageIds.value = null + postImageIndex.value = 0 + } try { current.value = await api.get(`/api/gallery/image/${id}`) } catch (e) { @@ -24,41 +41,58 @@ export const useModalStore = defineStore('modal', () => { } } - async function close() { + async function close () { currentImageId.value = null current.value = null error.value = null + postImageIds.value = null + postImageIndex.value = 0 } - async function goPrev() { - if (current.value && current.value.neighbors.prev_id) { + async function goPrev () { + if (postImageIds.value != null) { + if (postImageIndex.value > 0) { + const newIdx = postImageIndex.value - 1 + const newId = postImageIds.value[newIdx] + postImageIndex.value = newIdx + await open(newId, { postImageIds: postImageIds.value }) + } + return + } + if (current.value && current.value.neighbors?.prev_id) { await open(current.value.neighbors.prev_id) } } - async function goNext() { - if (current.value && current.value.neighbors.next_id) { + + async function goNext () { + if (postImageIds.value != null) { + if (postImageIndex.value < postImageIds.value.length - 1) { + const newIdx = postImageIndex.value + 1 + const newId = postImageIds.value[newIdx] + postImageIndex.value = newIdx + await open(newId, { postImageIds: postImageIds.value }) + } + return + } + if (current.value && current.value.neighbors?.next_id) { await open(current.value.neighbors.next_id) } } - async function reloadTags() { + async function reloadTags () { if (!currentImageId.value) return const tags = await api.get(`/api/images/${currentImageId.value}/tags`) if (current.value) current.value.tags = tags } - async function removeTag(tagId) { + async function removeTag (tagId) { if (!currentImageId.value) return - // Optimistic UI: remove locally first, restore on error. const prev = current.value.tags current.value.tags = current.value.tags.filter(t => t.id !== tagId) try { - // FC-2b: removal also records a per-image rejection (suggestions/dismiss - // is the rejection-recording endpoint) so the allowlist maintenance - // task won't re-apply this tag to this image. await api.delete(`/api/images/${currentImageId.value}/tags/${tagId}`) await api.post(`/api/images/${currentImageId.value}/suggestions/dismiss`, { - body: { tag_id: tagId } + body: { tag_id: tagId }, }) } catch (e) { current.value.tags = prev @@ -67,25 +101,36 @@ export const useModalStore = defineStore('modal', () => { } } - async function addExistingTag(tagId) { + async function addExistingTag (tagId) { if (!currentImageId.value) return await api.post(`/api/images/${currentImageId.value}/tags`, { - body: { tag_id: tagId, source: 'manual' } + body: { tag_id: tagId, source: 'manual' }, }) await reloadTags() } - async function createAndAdd({ name, kind, fandom_id = null }) { + async function createAndAdd ({ name, kind, fandom_id = null }) { const tag = await api.post('/api/tags', { body: { name, kind, fandom_id } }) await addExistingTag(tag.id) } const isOpen = computed(() => currentImageId.value !== null) - const canPrev = computed(() => current.value?.neighbors?.prev_id != null) - const canNext = computed(() => current.value?.neighbors?.next_id != null) + const canPrev = computed(() => { + if (postImageIds.value != null) return postImageIndex.value > 0 + return current.value?.neighbors?.prev_id != null + }) + const canNext = computed(() => { + if (postImageIds.value != null) { + return postImageIndex.value < (postImageIds.value.length - 1) + } + return current.value?.neighbors?.next_id != null + }) return { - currentImageId, current, loading, error, isOpen, canPrev, canNext, - open, close, goPrev, goNext, reloadTags, removeTag, addExistingTag, createAndAdd + currentImageId, current, loading, error, + postImageIds, postImageIndex, + isOpen, canPrev, canNext, + open, close, goPrev, goNext, + reloadTags, removeTag, addExistingTag, createAndAdd, } })