import { defineStore } from 'pinia' import { ref } from 'vue' import { type Comment, type CommentEventType, type CreateCommentInput, itemCreate, itemDelete, itemsList, itemUpdate, type UpdateCommentInput, } from '@/api/comment.api.ts' const PAGE_SIZE = 20 /** * Comments of the currently opened issue, oldest first. Loaded chat-style: * the latest page first, older pages are prepended on demand. */ export const useCommentStore = defineStore('comment', () => { const issueId = ref(null) const comments = ref([]) /** id of the oldest loaded comment while older ones exist on the server */ const olderCursor = ref(null) const loading = ref(false) const loadingOlder = ref(false) const error = ref(null) /** Inserts or replaces a comment by id. New comments are always the newest — append. */ function upsert(comment: Comment) { const index = comments.value.findIndex((c) => c.id === comment.id) if (index !== -1) comments.value[index] = comment else comments.value.push(comment) } async function load(id: number) { issueId.value = id comments.value = [] olderCursor.value = null loadingOlder.value = false loading.value = true try { const res = await itemsList(id, { limit: PAGE_SIZE, direction: 'backward' }) // The issue may have changed while the request was in flight if (issueId.value !== id) return comments.value = res.list olderCursor.value = res.nextCursor } catch (e) { error.value = e instanceof Error ? e.message : 'error get list of comments' } finally { if (issueId.value === id) loading.value = false } } async function loadOlder() { const id = issueId.value if (id === null || !olderCursor.value || loading.value || loadingOlder.value) return loadingOlder.value = true try { const res = await itemsList(id, { limit: PAGE_SIZE, cursor: olderCursor.value, direction: 'backward' }) if (issueId.value !== id) return const known = new Set(comments.value.map((c) => c.id)) comments.value.unshift(...res.list.filter((c) => !known.has(c.id))) olderCursor.value = res.nextCursor } catch (e) { error.value = e instanceof Error ? e.message : 'error get list of comments' } finally { if (issueId.value === id) loadingOlder.value = false } } /** Fetches comments newer than the last loaded one — e.g. after an SSE reconnect. */ async function loadNewer() { const id = issueId.value if (id === null || loading.value) return let cursor = comments.value.at(-1)?.id if (!cursor) return load(id) try { do { const res = await itemsList(id, { limit: PAGE_SIZE, cursor, direction: 'forward' }) if (issueId.value !== id) return res.list.forEach(upsert) cursor = res.nextCursor ?? undefined } while (cursor) } catch (e) { error.value = e instanceof Error ? e.message : 'error get list of comments' } } async function create(input: CreateCommentInput) { if (issueId.value === null) return try { const comment = await itemCreate(issueId.value, input) // The SSE event for the same comment may come before or after — upsert dedupes if (comment.issueId === issueId.value) upsert(comment) return comment } catch (e) { error.value = e instanceof Error ? e.message : 'error creating comment' } } async function update(id: string, input: UpdateCommentInput) { try { const comment = await itemUpdate(id, input) if (comment.issueId === issueId.value) upsert(comment) return comment } catch (e) { error.value = e instanceof Error ? e.message : 'error updating comment' } } async function remove(id: string) { try { return await itemDelete(id) } catch (e) { error.value = e instanceof Error ? e.message : 'error deleting comment' return false } } /** Applies a server-sent event. Deleted comments stay in the list as placeholders. */ function applyEvent(type: CommentEventType, comment: Comment) { if (comment.issueId !== issueId.value) return // Updates of comments older than the loaded window are irrelevant if (type !== 'comment.created' && !comments.value.some((c) => c.id === comment.id)) return upsert(comment) } function reset() { issueId.value = null comments.value = [] olderCursor.value = null loading.value = false loadingOlder.value = false } return { issueId, comments, olderCursor, loading, loadingOlder, error, load, loadOlder, loadNewer, create, update, remove, applyEvent, reset, } })