From b80e88b1219636440d74330fed4f29c92bc04b0b Mon Sep 17 00:00:00 2001 From: Vadim Date: Fri, 2 Oct 2026 21:43:19 +0300 Subject: [PATCH] chat --- src/App.vue | 10 +- src/api/comment.api.ts | 72 +++++++ src/components/CommentsList.vue | 302 +++++++++++++++++++++++++++- src/components/IssueContent.vue | 5 + src/components/IssueDetails.vue | 2 +- src/composables/useChatScroll.ts | 61 ++++++ src/composables/useCommentEvents.ts | 65 ++++++ src/stores/comment.ts | 153 ++++++++++++++ src/views/Issues/IssuesListView.vue | 2 +- 9 files changed, 660 insertions(+), 12 deletions(-) create mode 100644 src/api/comment.api.ts create mode 100644 src/composables/useChatScroll.ts create mode 100644 src/composables/useCommentEvents.ts create mode 100644 src/stores/comment.ts diff --git a/src/App.vue b/src/App.vue index e50620f..10fc45d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,7 +17,7 @@ interface IssueSseEvent { type: string } -const { data } = useEventSource( +const { data, event } = useEventSource( `${window.location.origin}/api/issues/events`, ['issue.created', 'issue.updated', 'issue.deleted'], { @@ -33,12 +33,8 @@ const { data } = useEventSource( }, ) -watch(data, () => { - if ( - data.value?.type === 'issue.created' || - data.value?.type === 'issue.updated' || - data.value?.type === 'issue.deleted' - ) { +watch(event, () => { + if (event.value === 'issue.created' || event.value === 'issue.updated' || event.value === 'issue.deleted') { issueStore.getStat(currentProjectId.value) } }) diff --git a/src/api/comment.api.ts b/src/api/comment.api.ts new file mode 100644 index 0000000..b91a4aa --- /dev/null +++ b/src/api/comment.api.ts @@ -0,0 +1,72 @@ +import { apiClient, toQuery } from '@/lib/api-client.ts' +import type { User } from '@/api/user.api.ts' + +export interface CommentFile { + id: string + fileName: string + mimeType: string + size: number + uploaderId: string | null + createdAt: string +} + +export interface Comment { + id: string + issueId: number + authorId: string | null + /** null — the author deleted their account */ + author: User | null + /** null for a deleted comment */ + content: string | null + attachments: CommentFile[] + editedAt: string | null + deletedAt: string | null + createdAt: string + updatedAt: string +} + +export interface CommentsPage { + list: Comment[] + nextCursor: string | null +} + +export interface CreateCommentInput { + content: string + fileIds?: string[] +} + +export type UpdateCommentInput = CreateCommentInput + +/** forward — newer than the cursor, backward — older than the cursor (no cursor: the latest page) */ +export type CommentListDirection = 'forward' | 'backward' + +export const COMMENT_EVENT_TYPES = ['comment.created', 'comment.updated', 'comment.deleted'] as const + +export type CommentEventType = (typeof COMMENT_EVENT_TYPES)[number] + +// CLIENT REQUEST + +export async function itemsList( + issueId: number, + params: { limit?: number; cursor?: string | null; direction?: CommentListDirection } = {}, +) { + const query = toQuery({ limit: params.limit, cursor: params.cursor, direction: params.direction }) + return apiClient.get(`/api/issues/${issueId}/comments${query}`) +} + +export async function itemCreate(issueId: number, input: CreateCommentInput) { + return apiClient.post(`/api/issues/${issueId}/comments`, input) +} + +export async function itemUpdate(id: string, input: UpdateCommentInput) { + return apiClient.patch(`/api/comments/${id}`, input) +} + +export async function itemDelete(id: string) { + await apiClient.delete(`/api/comments/${id}`) + return true +} + +export function eventsUrl(issueId: number) { + return `${window.location.origin}/api/issues/${issueId}/comments/events` +} diff --git a/src/components/CommentsList.vue b/src/components/CommentsList.vue index 9c1c6d6..aebbc13 100644 --- a/src/components/CommentsList.vue +++ b/src/components/CommentsList.vue @@ -1,5 +1,301 @@ - + + + + + diff --git a/src/components/IssueContent.vue b/src/components/IssueContent.vue index d980d4f..7219ba6 100644 --- a/src/components/IssueContent.vue +++ b/src/components/IssueContent.vue @@ -54,6 +54,11 @@ const cancel = () => {