11<template >
2- <div class =" flex-col text-base" >
2+ <div class =" flex-col text-base flex-1 " ref = " commentBoxRef " >
33 <div class =" mb-1 ml-0.5 flex items-center justify-between" >
44 <div class =" text-gray-600 flex items-center gap-2" >
55 <Avatar
1717 </span >
1818 </p >
1919 </div >
20- <div class =" flex items-center" >
20+ <div class =" flex items-center gap-1 " >
2121 <Tooltip :text =" dateFormat(creation, dateTooltipFormat)" >
2222 <span class =" pl-0.5 text-sm text-gray-600" >
2323 {{ timeAgo(creation) }}
2424 </span >
2525 </Tooltip >
26- <div v-if =" authStore.userId === commentedBy" >
26+ <div v-if =" authStore.userId === commentedBy && !editable " >
2727 <Dropdown
28- :options =" [{ label: 'Delete', onClick: () => (showDialog = true) }]"
28+ :options =" [
29+ {
30+ label: 'Edit',
31+ onClick: () => handleEditMode(),
32+ icon: 'edit-2',
33+ },
34+ {
35+ label: 'Delete',
36+ onClick: () => (showDialog = true),
37+ icon: 'trash-2',
38+ },
39+ ]"
2940 >
3041 <Button
3142 icon =" more-horizontal"
3647 </div >
3748 </div >
3849 </div >
39- <div
40- class =" prose-f grow cursor-pointer rounded bg-gray-50 px-4 py-3 text-base leading-6 transition-all duration-300 ease-in-out"
41- v-html =" content"
42- />
50+ <div class =" rounded bg-gray-50 px-4 py-3" >
51+ <TextEditor
52+ ref =" editorRef"
53+ :editor-class =" 'prose-f shrink text-p-sm transition-all duration-300 ease-in-out block w-full content'"
54+ :content =" _content"
55+ :editable =" editable"
56+ :bubble-menu =" textEditorMenuButtons"
57+ @change =" (event:string) => {_content = event}"
58+ >
59+ <template #bottom v-if =" editable " >
60+ <div class =" flex flex-row-reverse gap-2" >
61+ <Button label =" Save" @click =" handleSaveComment" variant =" solid" />
62+ <Button label =" Discard" @click =" handleDiscard" />
63+ </div >
64+ </template >
65+ </TextEditor >
66+ <div class =" flex flex-wrap gap-2" v-if =" !editable" >
67+ <AttachmentItem
68+ v-for =" a in attachments"
69+ :key =" a.file_url"
70+ :label =" a.file_name"
71+ :url =" a.file_url"
72+ />
73+ </div >
74+ </div >
4375 </div >
4476 <Dialog
4577 v-model =" showDialog"
5991</template >
6092
6193<script setup lang="ts">
62- import { ref } from " vue" ;
63- import { Dropdown , createResource , Dialog , Avatar } from " frappe-ui" ;
64- import { dateFormat , timeAgo , dateTooltipFormat , createToast } from " @/utils" ;
94+ import { ref , PropType , onMounted } from " vue" ;
95+ import {
96+ Dropdown ,
97+ createResource ,
98+ Dialog ,
99+ Avatar ,
100+ TextEditor ,
101+ } from " frappe-ui" ;
102+ import {
103+ dateFormat ,
104+ timeAgo ,
105+ dateTooltipFormat ,
106+ createToast ,
107+ textEditorMenuButtons ,
108+ isContentEmpty ,
109+ } from " @/utils" ;
110+ import { AttachmentItem } from " @/components" ;
65111import { useAuthStore } from " @/stores/auth" ;
66112import { useUserStore } from " @/stores/user" ;
67-
113+ import { CommentActivity } from " @/types" ;
114+ import { updateRes as updateComment } from " @/stores/knowledgeBase" ;
68115const authStore = useAuthStore ();
69116const props = defineProps ({
70117 activity: {
71- type: Object ,
118+ type: Object as PropType < CommentActivity > ,
72119 required: true ,
73120 },
74121});
75122const { getUser } = useUserStore ();
76123
77- const { name, creation, content, commenter, commentedBy } = props .activity ;
124+ const { name, creation, content, commenter, commentedBy, attachments } =
125+ props .activity ;
78126
79127const emit = defineEmits ([" update" ]);
80128const showDialog = ref (false );
129+ const editable = ref (false );
130+ const _content = ref (content );
131+
132+ // HTML refs
133+ const commentBoxRef = ref (null );
134+ const editorRef = ref (null );
135+
136+ function handleEditMode() {
137+ editable .value = true ;
138+ editorRef .value .editor .chain ().focus (" start" );
139+ }
140+
141+ function handleDiscard() {
142+ _content .value = content ;
143+ editable .value = false ;
144+ }
81145
82146const deleteComment = createResource ({
83147 url: " frappe.client.delete" ,
@@ -94,4 +158,43 @@ const deleteComment = createResource({
94158 });
95159 },
96160});
161+
162+ function handleSaveComment() {
163+ if (content === _content .value ) {
164+ editable .value = false ;
165+ return ;
166+ }
167+ if (isContentEmpty (_content .value )) {
168+ createToast ({
169+ title: " Comment cannot be empty" ,
170+ icon: " x" ,
171+ iconClasses: " text-red-600" ,
172+ });
173+ return ;
174+ }
175+
176+ updateComment .submit (
177+ {
178+ doctype: " HD Ticket Comment" ,
179+ name: name ,
180+ fieldname: " content" ,
181+ value: _content .value ,
182+ },
183+ {
184+ onSuccess : () => {
185+ editable .value = false ;
186+ emit (" update" );
187+ createToast ({
188+ title: " Comment updated" ,
189+ icon: " check" ,
190+ iconClasses: " text-green-500" ,
191+ });
192+ },
193+ }
194+ );
195+ }
196+
197+ onMounted (() => {
198+ commentBoxRef .value .style .width = " 0px" ;
199+ });
97200 </script >
0 commit comments