1 parent
cdd7147d8e
commit
b80e88b121
9 files changed
+660
-12
No files matched your search
@@ -0,0 +1,153 @@
|
||||
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<number | null>(null)
|
||||
const comments = ref<Comment[]>([])
|
||||
/** id of the oldest loaded comment while older ones exist on the server */
|
||||
const olderCursor = ref<string | null>(null)
|
||||
const loading = ref(false)
|
||||
const loadingOlder = ref(false)
|
||||
const error = ref<string | null>(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,
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user