import { ElMessage, ElMessageBox } from 'element-plus'; import { listDocumentComments, createDocumentComment, deleteDocumentComment, updateDocumentComment } from '@/services/api'; import { resolveFileUrl } from '@/utils/fileUrl'; export function useInlineCommentCrud({ t, getDocId, getInlineEnabled, getUserInfo, commentList, userDisplayName, maybeRemoveAnchor, onCommentMutated }) { const buildLocalId = () => `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const createTempComment = ({ parent, anchorId }) => { const userInfo = getUserInfo?.(); return { local_id: buildLocalId(), external_id: '', parent_external_id: parent?.external_id || '', content: '', created_at: '', creator_name: userDisplayName.value, creator_user_external_id: userInfo?.external_id || '', creator_avatar: resolveFileUrl(userInfo?.avatar || ''), anchor_id: `${anchorId || parent?.anchor_id || ''}`.trim(), editing: true, draft: '', saving: false }; }; const canModify = (item) => { if (!item) return false; if (!item.external_id) return true; const userInfo = getUserInfo?.(); const currentExternalId = userInfo?.external_id; if (!currentExternalId) return false; if (item.creator_user_external_id === currentExternalId) { return true; } return userInfo?.username === 'admin'; }; const getActions = (item) => { const editable = canModify(item); return [ { key: 'copy', label: t('common.copy') }, { key: 'edit', label: t('common.edit'), disabled: !editable }, { key: 'delete', label: t('document.commentDelete'), danger: true, disabled: !editable } ]; }; const insertAfter = (list, predicate, item) => { const index = list.findIndex(predicate); if (index === -1) { list.unshift(item); return; } list.splice(index + 1, 0, item); }; const handleReply = (item) => { const temp = createTempComment({ parent: item }); insertAfter(commentList.value, (entry) => entry.external_id === item.external_id, temp); }; const startEdit = (item) => { if (!item || item.editing) return; item.draft = item.content || ''; item.editing = true; }; const cancelEdit = (item) => { if (!item) return; if (!item.external_id) { const nextList = commentList.value.filter((entry) => entry.local_id !== item.local_id); commentList.value = nextList; maybeRemoveAnchor(item.anchor_id, nextList); return; } item.editing = false; item.draft = ''; }; const saveEdit = async (item) => { if (!item) return; const content = (item.draft || '').trim(); if (!content) { ElMessage.error(t('document.inlineCommentContentRequired')); return; } if (!item.anchor_id) { ElMessage.error(t('document.inlineCommentAnchorMissing')); return; } if (item.saving) return; const docId = getDocId?.(); if (!docId) { return; } item.saving = true; try { if (item.external_id) { const resp = await updateDocumentComment({ external_id: item.external_id, content }); const saved = resp.comment; item.content = saved?.content || content; item.created_at = saved?.created_at || item.created_at; item.creator_name = saved?.creator_name || item.creator_name; item.creator_avatar = saved?.creator_avatar || item.creator_avatar; item.creator_user_external_id = saved?.creator_user_external_id || item.creator_user_external_id; item.editing = false; item.draft = ''; if (typeof onCommentMutated === 'function') { onCommentMutated({ type: 'update', comment_id: item.external_id, anchor_id: item.anchor_id }); } } else { const resp = await createDocumentComment({ document_external_id: docId, content, parent_external_id: item.parent_external_id || undefined, anchor_id: item.anchor_id, mentioned_user_external_ids: item.mentionedUserExternalIds || [] }); const saved = resp.comment; item.content = saved?.content || content; item.external_id = saved?.external_id || item.external_id; item.local_id = item.external_id || item.local_id; item.created_at = saved?.created_at || new Date().toISOString(); item.creator_name = saved?.creator_name || item.creator_name; item.creator_avatar = saved?.creator_avatar || item.creator_avatar; item.creator_user_external_id = saved?.creator_user_external_id || item.creator_user_external_id; item.anchor_id = saved?.anchor_id || item.anchor_id; item.editing = false; item.draft = ''; item.mentionedUserExternalIds = []; if (typeof onCommentMutated === 'function') { onCommentMutated({ type: 'create', comment_id: item.external_id, anchor_id: item.anchor_id }); } } } catch (error) { ElMessage.error(t('common.requestFailed')); } finally { item.saving = false; } }; const deleteCommentItem = async (item) => { if (!item) return; const deletedAnchorId = item.anchor_id; if (item.external_id) { try { await ElMessageBox.confirm( t('document.commentDeleteConfirm'), t('document.commentDelete'), { confirmButtonText: t('button.confirm'), cancelButtonText: t('button.cancel'), type: 'warning' } ); await deleteDocumentComment(item.external_id); if (typeof onCommentMutated === 'function') { onCommentMutated({ type: 'delete', comment_id: item.external_id, anchor_id: deletedAnchorId }); } } catch (error) { return; } } const nextList = commentList.value.filter((entry) => entry.local_id !== item.local_id); commentList.value = nextList; maybeRemoveAnchor(item.anchor_id, nextList); }; const copyComment = async (item) => { const text = item?.content || ''; if (!text) return; try { await navigator.clipboard.writeText(text); ElMessage.success(t('common.copySuccess')); } catch (error) { ElMessage.error(t('common.copyFailed')); } }; const handleAction = (item, action) => { if (action === 'copy') { copyComment(item); return; } if (action === 'edit') { if (!canModify(item)) return; startEdit(item); return; } if (action === 'delete') { if (!canModify(item)) return; deleteCommentItem(item); } }; const formatCommentTime = (value) => { if (!value) return ''; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString(); }; const loadComments = async (loadVersionRef) => { const requestVersion = ++loadVersionRef.value; const requestDocId = getDocId?.(); const inlineEnabled = !!getInlineEnabled?.(); const pendingEditingDrafts = commentList.value .filter((item) => item && !item.external_id && item.editing) .map((item) => ({ ...item })); if (!requestDocId || !inlineEnabled) { commentList.value = []; if (requestVersion === loadVersionRef.value) { return false; } return true; } try { const resp = await listDocumentComments({ document_external_id: requestDocId, page: 1, page_size: 100 }); if (requestVersion !== loadVersionRef.value || requestDocId !== getDocId?.()) { return false; } const remoteComments = (resp.comments || []) .filter((item) => item.anchor_id) .map((item) => ({ local_id: item.external_id || item.anchor_id, anchor_id: item.anchor_id, external_id: item.external_id, parent_external_id: item.parent_external_id || '', content: item.content, created_at: item.created_at, creator_name: item.creator_name, creator_user_external_id: item.creator_user_external_id, creator_avatar: item.creator_avatar, editing: false, draft: '', saving: false })); commentList.value = [...pendingEditingDrafts, ...remoteComments]; return true; } catch (error) { if (requestVersion !== loadVersionRef.value || requestDocId !== getDocId?.()) { return false; } commentList.value = pendingEditingDrafts; return true; } }; return { createTempComment, getActions, handleReply, handleAction, saveEdit, cancelEdit, formatCommentTime, loadComments, deleteRemoteComment: deleteDocumentComment }; }