Skip to content

Commit 9ecf7c4

Browse files
Merge pull request #2151 from RitvikSardana/comment-box-refactor
refactor: comment box
2 parents 9901118 + 030cd8b commit 9ecf7c4

File tree

17 files changed

+243
-84
lines changed

17 files changed

+243
-84
lines changed

desk/src/components/CommentBox.vue

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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
@@ -17,15 +17,26 @@
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"
@@ -36,10 +47,31 @@
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"
@@ -59,25 +91,57 @@
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";
65111
import { useAuthStore } from "@/stores/auth";
66112
import { useUserStore } from "@/stores/user";
67-
113+
import { CommentActivity } from "@/types";
114+
import { updateRes as updateComment } from "@/stores/knowledgeBase";
68115
const authStore = useAuthStore();
69116
const props = defineProps({
70117
activity: {
71-
type: Object,
118+
type: Object as PropType<CommentActivity>,
72119
required: true,
73120
},
74121
});
75122
const { getUser } = useUserStore();
76123
77-
const { name, creation, content, commenter, commentedBy } = props.activity;
124+
const { name, creation, content, commenter, commentedBy, attachments } =
125+
props.activity;
78126
79127
const emit = defineEmits(["update"]);
80128
const 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
82146
const 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>

desk/src/components/CommentTextEditor.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,16 @@ async function submitComment() {
154154
method: "new_comment",
155155
args: {
156156
content: newComment.value,
157+
attachments: attachments.value,
157158
},
158159
}),
159160
onSuccess: () => {
160161
emit("submit");
161162
loading.value = false;
162163
},
164+
onError: () => {
165+
loading.value = false;
166+
},
163167
});
164168
165169
comment.submit();

desk/src/components/EmailArea.vue

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,7 @@
7171
</span>
7272
<span v-if="bcc">{{ bcc }}</span>
7373
</div>
74-
<!-- <div
75-
class="email-content prose-f max-h-[500px] overflow-y-auto"
76-
v-html="content"
77-
/> -->
78-
<EmailContent :content="content" :emailBox="emailBox" />
74+
<EmailContent :content="content" />
7975
<div class="flex flex-wrap gap-2">
8076
<AttachmentItem
8177
v-for="a in attachments"
@@ -88,11 +84,10 @@
8884
</template>
8985

9086
<script setup lang="ts">
91-
import { UserAvatar, AttachmentItem } from "@/components";
87+
import { AttachmentItem } from "@/components";
9288
import { dateFormat, timeAgo, dateTooltipFormat } from "@/utils";
9389
import { ReplyIcon, ReplyAllIcon } from "./icons";
9490
import { useScreenSize } from "@/composables/screen";
95-
import { inject } from "vue";
9691
9792
const props = defineProps({
9893
activity: {
@@ -106,7 +101,6 @@ const { sender, to, cc, bcc, creation, subject, attachments, content } =
106101
107102
const emit = defineEmits(["reply"]);
108103
109-
let emailBox = inject("communicationArea");
110104
const { isMobileView } = useScreenSize();
111105
112106
// TODO: Implement reply functionality using this way instead of emit drillup

desk/src/components/EmailEditor.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ import {
165165
} from "@/components";
166166
import { AttachmentIcon, EmailIcon } from "@/components/icons";
167167
import { PreserveVideoControls } from "@/tiptap-extensions";
168-
import { useError } from "@/composables/error";
169168
170169
const editorRef = ref(null);
171170
const showCannedResponseSelectorModal = ref(false);

desk/src/components/HistoryBox.vue

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,56 @@
11
<template>
2-
<div class="mt-1.5 flex justify-between text-base">
3-
<div class="text-gray-600">
4-
<span class="font-medium text-gray-800">
5-
{{ user }}
6-
</span>
7-
<span> {{ content }}</span>
8-
</div>
9-
<Tooltip :text="dateFormat(creation, dateTooltipFormat)">
10-
<div class="text-gray-600">
11-
{{ timeAgo(creation) }}
12-
</div>
13-
</Tooltip>
14-
</div>
15-
<div v-if="show_others && content !== 'created this ticket'">
16-
<div
17-
v-for="relatedActivity in relatedActivities"
18-
:key="relatedActivity.creation"
19-
class="mt-2 flex justify-between text-base"
20-
>
2+
<div class="flex-1">
3+
<div class="mt-1.5 flex justify-between text-base items-center">
214
<div class="text-gray-600">
225
<span class="font-medium text-gray-800">
23-
{{ relatedActivity.user }}
6+
{{ user }}
247
</span>
25-
<span> {{ relatedActivity.content }}</span>
8+
<span> {{ content }}</span>
269
</div>
27-
<Tooltip :text="dateFormat(relatedActivity.creation, dateTooltipFormat)">
28-
<div class="text-gray-600">
29-
{{ timeAgo(relatedActivity.creation) }}
10+
<Tooltip :text="dateFormat(creation, dateTooltipFormat)">
11+
<div class="text-gray-600 text-sm">
12+
{{ timeAgo(creation) }}
3013
</div>
3114
</Tooltip>
3215
</div>
16+
<div v-if="show_others && content !== 'created this ticket'">
17+
<div
18+
v-for="relatedActivity in relatedActivities"
19+
:key="relatedActivity.creation"
20+
class="mt-2 flex justify-between text-base"
21+
>
22+
<div class="text-gray-600">
23+
<span class="font-medium text-gray-800">
24+
{{ relatedActivity.user }}
25+
</span>
26+
<span> {{ relatedActivity.content }}</span>
27+
</div>
28+
<Tooltip
29+
:text="dateFormat(relatedActivity.creation, dateTooltipFormat)"
30+
>
31+
<div class="text-gray-600 text-sm">
32+
{{ timeAgo(relatedActivity.creation) }}
33+
</div>
34+
</Tooltip>
35+
</div>
36+
</div>
37+
<Button
38+
v-if="relatedActivities.length && content !== 'created this ticket'"
39+
:label="
40+
show_others ? 'Hide' : `${relatedActivities.length} other activities`
41+
"
42+
variant="outline"
43+
class="mt-2"
44+
@click="show_others = !show_others"
45+
>
46+
<template #suffix>
47+
<FeatherIcon
48+
:name="show_others ? 'chevron-up' : 'chevron-down'"
49+
class="h-4 text-gray-600"
50+
/>
51+
</template>
52+
</Button>
3353
</div>
34-
<Button
35-
v-if="relatedActivities.length && content !== 'created this ticket'"
36-
:label="
37-
show_others ? 'Hide' : `${relatedActivities.length} other activities`
38-
"
39-
variant="outline"
40-
class="mt-2"
41-
@click="show_others = !show_others"
42-
>
43-
<template #suffix>
44-
<FeatherIcon
45-
:name="show_others ? 'chevron-up' : 'chevron-down'"
46-
class="h-4 text-gray-600"
47-
/>
48-
</template>
49-
</Button>
5054
</template>
5155

5256
<script setup lang="ts">

desk/src/components/layouts/MobileSidebar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<!-- user dropwdown -->
1717
<div><UserMenu class="p-2 mb-2" :options="profileSettings" /></div>
1818
<!-- notifications -->
19-
<div class="overflow-y-auto px-2">
19+
<div class="overflow-y-auto px-2" v-if="!isCustomerPortal">
2020
<div class="mb-3 flex flex-col">
2121
<SidebarLink
2222
class="relative"

desk/src/components/layouts/Sidebar.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
<template #right>
4040
<Badge
4141
v-if="isExpanded && notificationStore.unread"
42-
:label="notificationStore.unread"
42+
:label="
43+
notificationStore.unread > 9 ? '9+' : notificationStore.unread
44+
"
4345
theme="gray"
4446
variant="subtle"
4547
/>

desk/src/components/ticket/TicketAgentActivities.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<DotIcon v-else class="text-gray-600" />
3737
</div>
3838
</div>
39-
<div class="mb-4 w-full">
39+
<div class="mb-4 flex flex-1">
4040
<EmailArea
4141
v-if="activity.type === 'email'"
4242
:activity="activity"
@@ -74,7 +74,7 @@
7474
</template>
7575

7676
<script setup lang="ts">
77-
import { Ref, inject, h, computed, onMounted, watch } from "vue";
77+
import { Ref, inject, h, computed, onMounted, watch, PropType } from "vue";
7878
import { useElementVisibility } from "@vueuse/core";
7979
import {
8080
DotIcon,
@@ -86,10 +86,10 @@ import {
8686
import { EmailArea, CommentBox, HistoryBox } from "@/components";
8787
import { useUserStore } from "@/stores/user";
8888
import { Avatar } from "frappe-ui";
89-
89+
import { TicketActivity } from "@/types";
9090
const props = defineProps({
9191
activities: {
92-
type: Array,
92+
type: Array as PropType<TicketActivity[]>,
9393
required: true,
9494
},
9595
title: {

desk/src/components/ticket/TicketAgentSidebar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div class="flex w-[382px] flex-col justify-between border-l">
3-
<div class="h-10.5 flex items-center justify-between border-b px-5 py-2.5">
3+
<div class="h-[2.83rem] flex items-center justify-between border-b px-5">
44
<span
55
class="cursor-copy text-lg font-semibold"
66
@click="copyToClipboard(ticket.name, ticket.name)"

0 commit comments

Comments
 (0)