pukaad-ui-lib 1.201.0 → 1.203.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/comment.d.vue.ts +12 -0
- package/dist/runtime/components/comment.vue +312 -85
- package/dist/runtime/components/comment.vue.d.ts +12 -0
- package/dist/runtime/components/modal/modal-report.d.vue.ts +22 -1
- package/dist/runtime/components/modal/modal-report.vue +86 -26
- package/dist/runtime/components/modal/modal-report.vue.d.ts +22 -1
- package/dist/runtime/components/picker/picker-option-menu/picker-option-menu-user.vue +2 -1
- package/dist/runtime/components/picker/picker-option-menu/picker-option-menu.d.vue.ts +11 -2
- package/dist/runtime/components/picker/picker-option-menu/picker-option-menu.vue +8 -4
- package/dist/runtime/components/picker/picker-option-menu/picker-option-menu.vue.d.ts +11 -2
- package/dist/runtime/components/ui/button/index.d.ts +1 -1
- package/dist/runtime/components/ui/button/index.js +1 -0
- package/dist/runtime/components/ui/carousel/CarouselNext.d.vue.ts +1 -1
- package/dist/runtime/components/ui/carousel/CarouselNext.vue.d.ts +1 -1
- package/dist/runtime/components/ui/carousel/CarouselPrevious.d.vue.ts +1 -1
- package/dist/runtime/components/ui/carousel/CarouselPrevious.vue.d.ts +1 -1
- package/dist/runtime/components/ui/input-group/InputGroupButton.d.vue.ts +1 -1
- package/dist/runtime/components/ui/input-group/InputGroupButton.vue.d.ts +1 -1
- package/dist/runtime/components/ui/input-group/index.d.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationFirst.d.vue.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationFirst.vue.d.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationItem.d.vue.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationItem.vue.d.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationLast.d.vue.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationLast.vue.d.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationNext.d.vue.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationNext.vue.d.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationPrevious.d.vue.ts +1 -1
- package/dist/runtime/components/ui/pagination/PaginationPrevious.vue.d.ts +1 -1
- package/dist/runtime/components/ui/select/Select.d.vue.ts +2 -2
- package/dist/runtime/components/ui/select/Select.vue.d.ts +2 -2
- package/package.json +1 -1
- package/dist/runtime/components/comments/CommentSection.d.vue.ts +0 -49
- package/dist/runtime/components/comments/CommentSection.vue +0 -360
- package/dist/runtime/components/comments/CommentSection.vue.d.ts +0 -49
package/dist/module.json
CHANGED
|
@@ -32,11 +32,23 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
32
32
|
reply: (commentId: string, replyToUserId: string | undefined, content: string) => any;
|
|
33
33
|
"like-comment": (comment: CommentItem) => any;
|
|
34
34
|
"like-reply": (reply: Reply) => any;
|
|
35
|
+
"edit-comment": (comment: CommentItem, content: string) => any;
|
|
36
|
+
"edit-reply": (reply: Reply, content: string) => any;
|
|
37
|
+
"delete-comment": (comment: CommentItem) => any;
|
|
38
|
+
"delete-reply": (reply: Reply) => any;
|
|
39
|
+
"report-comment": (comment: CommentItem) => any;
|
|
40
|
+
"report-reply": (reply: Reply) => any;
|
|
35
41
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
42
|
onSubmit?: ((content: string) => any) | undefined;
|
|
37
43
|
onReply?: ((commentId: string, replyToUserId: string | undefined, content: string) => any) | undefined;
|
|
38
44
|
"onLike-comment"?: ((comment: CommentItem) => any) | undefined;
|
|
39
45
|
"onLike-reply"?: ((reply: Reply) => any) | undefined;
|
|
46
|
+
"onEdit-comment"?: ((comment: CommentItem, content: string) => any) | undefined;
|
|
47
|
+
"onEdit-reply"?: ((reply: Reply, content: string) => any) | undefined;
|
|
48
|
+
"onDelete-comment"?: ((comment: CommentItem) => any) | undefined;
|
|
49
|
+
"onDelete-reply"?: ((reply: Reply) => any) | undefined;
|
|
50
|
+
"onReport-comment"?: ((comment: CommentItem) => any) | undefined;
|
|
51
|
+
"onReport-reply"?: ((reply: Reply) => any) | undefined;
|
|
40
52
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
53
|
declare const _default: typeof __VLS_export;
|
|
42
54
|
export default _default;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</div>
|
|
6
6
|
<div class="flex flex-col gap-[16px]">
|
|
7
7
|
<div class="flex gap-[8px] w-full">
|
|
8
|
-
<Avatar :size="30" />
|
|
8
|
+
<Avatar :size="30" :src="currentUser.avatar" />
|
|
9
9
|
<div
|
|
10
10
|
ref="mainInputRef"
|
|
11
11
|
contenteditable="true"
|
|
@@ -37,40 +37,93 @@
|
|
|
37
37
|
class="flex flex-col gap-[16px] w-full"
|
|
38
38
|
>
|
|
39
39
|
<div class="flex flex-col gap-[4px] w-full">
|
|
40
|
-
<div
|
|
40
|
+
<div
|
|
41
|
+
class="flex gap-[8px]"
|
|
42
|
+
@mouseenter="hoveredId = cmt.id"
|
|
43
|
+
@mouseleave="hoveredId = null"
|
|
44
|
+
>
|
|
41
45
|
<Avatar
|
|
42
46
|
:size="30"
|
|
47
|
+
:src="cmt.user.avatar"
|
|
43
48
|
class="cursor-pointer"
|
|
44
49
|
@click="onViewProfileComment(cmt.user.id)"
|
|
45
50
|
/>
|
|
46
|
-
<
|
|
47
|
-
<div
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
<template v-if="editingId === cmt.id">
|
|
52
|
+
<div class="flex-1 flex flex-col gap-[8px]">
|
|
53
|
+
<div
|
|
54
|
+
:ref="(el) => setEditInputRef(el, cmt.id)"
|
|
55
|
+
contenteditable="true"
|
|
56
|
+
class="w-full min-h-[36px] px-[12px] py-[5px] rounded-md border border-mercury font-body-large focus:outline-none focus:ring-2 focus:ring-primary break-words bg-white"
|
|
57
|
+
@input="onEditInput($event, cmt.id)"
|
|
58
|
+
@keydown.enter.exact.prevent="onSendEdit(cmt, 'comment')"
|
|
59
|
+
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
60
|
+
/>
|
|
61
|
+
<div class="flex justify-end gap-[8px]">
|
|
62
|
+
<Button
|
|
63
|
+
variant="outline"
|
|
64
|
+
class="w-[56px]"
|
|
65
|
+
@click="onCancelEdit"
|
|
66
|
+
>
|
|
67
|
+
ยกเลิก
|
|
68
|
+
</Button>
|
|
69
|
+
<Button
|
|
70
|
+
color="primary"
|
|
71
|
+
class="w-[56px]"
|
|
72
|
+
:disabled="!editTextMap[cmt.id]"
|
|
73
|
+
@click="onSendEdit(cmt, 'comment')"
|
|
74
|
+
>
|
|
75
|
+
ส่ง
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
52
78
|
</div>
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
</template>
|
|
80
|
+
<template v-else>
|
|
81
|
+
<div class="flex flex-col gap-[4px] bg-bright p-[8px] rounded-lg">
|
|
82
|
+
<div
|
|
83
|
+
class="font-body-large-prominent cursor-pointer"
|
|
84
|
+
@click="onViewProfileComment(cmt.user.id)"
|
|
85
|
+
>
|
|
86
|
+
{{ cmt.user.name }}
|
|
87
|
+
</div>
|
|
88
|
+
<div
|
|
89
|
+
:ref="
|
|
55
90
|
(el) => registerContentRef(el, `comment-${cmt.id}`)
|
|
56
91
|
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
92
|
+
class="font-body-large whitespace-pre-wrap"
|
|
93
|
+
>
|
|
94
|
+
<template
|
|
95
|
+
v-if="
|
|
61
96
|
isContentExpanded(`comment-${cmt.id}`) || !isContentOverflowing(`comment-${cmt.id}`)
|
|
62
97
|
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
>{{ normalizeContent(cmt.content) }}</template
|
|
99
|
+
>
|
|
100
|
+
<template v-else
|
|
101
|
+
>{{ getTruncatedText(`comment-${cmt.id}`) }}...<span
|
|
102
|
+
class="text-primary cursor-pointer"
|
|
103
|
+
@click="toggleContentExpand(`comment-${cmt.id}`)"
|
|
104
|
+
>ดูเพิ่มเติม</span
|
|
105
|
+
></template
|
|
106
|
+
>
|
|
107
|
+
</div>
|
|
72
108
|
</div>
|
|
73
|
-
|
|
109
|
+
<div
|
|
110
|
+
class="self-center transition-opacity"
|
|
111
|
+
:class="
|
|
112
|
+
isMenuVisible(cmt.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
113
|
+
"
|
|
114
|
+
>
|
|
115
|
+
<PickerOptionMenu
|
|
116
|
+
horizontal
|
|
117
|
+
variant="ghost"
|
|
118
|
+
circle
|
|
119
|
+
size="icon-sm"
|
|
120
|
+
icon-size="20"
|
|
121
|
+
:items="getCommentMenuItems(cmt)"
|
|
122
|
+
:open="openMenuId === cmt.id"
|
|
123
|
+
@update:open="(v) => openMenuId = v ? cmt.id : null"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
74
127
|
</div>
|
|
75
128
|
<div class="pl-[38px] flex gap-[16px] text-gray">
|
|
76
129
|
<div class="font-body-medium">
|
|
@@ -86,7 +139,7 @@
|
|
|
86
139
|
"
|
|
87
140
|
:class="[cmt.is_liked && 'text-primary']"
|
|
88
141
|
/>
|
|
89
|
-
<div :class="[cmt.is_liked && 'text-primary']">
|
|
142
|
+
<div v-if="cmt.like_count > 0" :class="[cmt.is_liked && 'text-primary']">
|
|
90
143
|
{{ $convert.convertNumber(cmt.like_count) }}
|
|
91
144
|
</div>
|
|
92
145
|
</div>
|
|
@@ -98,17 +151,17 @@
|
|
|
98
151
|
</div>
|
|
99
152
|
</div>
|
|
100
153
|
<div
|
|
101
|
-
v-if="
|
|
154
|
+
v-if="getHiddenCount(cmt) > 0"
|
|
102
155
|
class="pl-[38px] flex flex-col gap-[16px]"
|
|
103
156
|
>
|
|
104
157
|
<Button
|
|
105
158
|
variant="text"
|
|
106
|
-
class="justify-start text-primary
|
|
159
|
+
class="justify-start text-primary h-auto gap-[4px] w-fit"
|
|
107
160
|
@click="onToggleReplyComment(cmt.id)"
|
|
108
161
|
>
|
|
109
162
|
<div class="font-body-large-prominent">
|
|
110
163
|
การตอบกลับ
|
|
111
|
-
{{ $convert.convertNumber(
|
|
164
|
+
{{ $convert.convertNumber(getHiddenCount(cmt)) }}
|
|
112
165
|
รายการ
|
|
113
166
|
</div>
|
|
114
167
|
<Icon
|
|
@@ -121,7 +174,7 @@
|
|
|
121
174
|
</div>
|
|
122
175
|
<div v-if="replyingToId === cmt.id" class="flex flex-col gap-[16px]">
|
|
123
176
|
<div class="flex gap-[8px] w-full">
|
|
124
|
-
<Avatar :size="30" />
|
|
177
|
+
<Avatar :size="30" :src="currentUser.avatar" />
|
|
125
178
|
<div class="flex-1">
|
|
126
179
|
<div
|
|
127
180
|
:ref="(el) => setReplyInputRef(el, cmt.id)"
|
|
@@ -153,63 +206,119 @@
|
|
|
153
206
|
</div>
|
|
154
207
|
</div>
|
|
155
208
|
<div
|
|
156
|
-
v-if="
|
|
157
|
-
|
|
209
|
+
v-if="cmt.reply.length > 0"
|
|
210
|
+
v-show="openedComments.includes(cmt.id) || cmt.reply.some((r) => pinnedReplyIds.has(r.id))"
|
|
211
|
+
class="pl-[38px] flex flex-col gap-[16px]"
|
|
158
212
|
>
|
|
159
213
|
<div
|
|
160
214
|
v-for="reply in cmt.reply"
|
|
161
|
-
v-show="
|
|
162
|
-
openedComments.includes(cmt.id) || reply.user.id === currentUser.id
|
|
163
|
-
"
|
|
215
|
+
v-show="isReplyVisible(cmt, reply)"
|
|
164
216
|
:key="reply.id"
|
|
165
|
-
:data-reply-id="reply.id"
|
|
166
217
|
class="flex flex-col gap-[16px]"
|
|
167
218
|
>
|
|
168
|
-
<div class="flex flex-col gap-[4px]
|
|
169
|
-
<div
|
|
219
|
+
<div class="flex flex-col gap-[4px]">
|
|
220
|
+
<div
|
|
221
|
+
class="flex gap-[8px]"
|
|
222
|
+
@mouseenter="hoveredId = reply.id"
|
|
223
|
+
@mouseleave="hoveredId = null"
|
|
224
|
+
>
|
|
170
225
|
<Avatar
|
|
171
226
|
:size="30"
|
|
227
|
+
:src="reply.user.avatar"
|
|
172
228
|
class="cursor-pointer"
|
|
173
229
|
@click="onViewProfileComment(reply.user.id)"
|
|
174
230
|
/>
|
|
175
|
-
<
|
|
231
|
+
<template v-if="editingId === reply.id">
|
|
232
|
+
<div class="flex-1 flex flex-col gap-[8px]">
|
|
233
|
+
<div
|
|
234
|
+
:ref="(el) => setEditInputRef(el, reply.id)"
|
|
235
|
+
contenteditable="true"
|
|
236
|
+
class="w-full min-h-[36px] px-[12px] py-[5px] rounded-md border border-mercury font-body-large focus:outline-none focus:ring-2 focus:ring-primary break-words bg-white"
|
|
237
|
+
@input="onEditInput($event, reply.id)"
|
|
238
|
+
@keydown.enter.exact.prevent="onSendEdit(reply, 'reply')"
|
|
239
|
+
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
240
|
+
/>
|
|
241
|
+
<div class="flex justify-end gap-[8px]">
|
|
242
|
+
<Button
|
|
243
|
+
variant="outline"
|
|
244
|
+
class="w-[56px]"
|
|
245
|
+
@click="onCancelEdit"
|
|
246
|
+
>
|
|
247
|
+
ยกเลิก
|
|
248
|
+
</Button>
|
|
249
|
+
<Button
|
|
250
|
+
color="primary"
|
|
251
|
+
class="w-[56px]"
|
|
252
|
+
:disabled="!editTextMap[reply.id]"
|
|
253
|
+
@click="onSendEdit(reply, 'reply')"
|
|
254
|
+
>
|
|
255
|
+
ส่ง
|
|
256
|
+
</Button>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</template>
|
|
260
|
+
<template v-else>
|
|
176
261
|
<div
|
|
177
|
-
class="
|
|
178
|
-
@click="onViewProfileComment(reply.user.id)"
|
|
262
|
+
class="flex flex-col bg-bright p-[8px] rounded-lg gap-[4px]"
|
|
179
263
|
>
|
|
180
|
-
|
|
264
|
+
<div
|
|
265
|
+
class="font-body-large-prominent cursor-pointer"
|
|
266
|
+
@click="onViewProfileComment(reply.user.id)"
|
|
267
|
+
>
|
|
268
|
+
{{ reply.user.name }}
|
|
269
|
+
</div>
|
|
270
|
+
<div
|
|
271
|
+
:ref="
|
|
272
|
+
(el) => registerContentRef(
|
|
273
|
+
el,
|
|
274
|
+
`reply-${reply.id}`
|
|
275
|
+
)
|
|
276
|
+
"
|
|
277
|
+
class="font-body-large whitespace-pre-wrap"
|
|
278
|
+
>
|
|
279
|
+
<template
|
|
280
|
+
v-if="
|
|
281
|
+
isContentExpanded(`reply-${reply.id}`) || !isContentOverflowing(`reply-${reply.id}`)
|
|
282
|
+
"
|
|
283
|
+
><span
|
|
284
|
+
v-if="reply.reply_to"
|
|
285
|
+
@click="onViewProfileComment(reply.reply_to.id)"
|
|
286
|
+
class="text-primary cursor-pointer mr-1"
|
|
287
|
+
>{{ reply.reply_to.name }}</span
|
|
288
|
+
>{{ normalizeContent(reply.content) }}</template
|
|
289
|
+
>
|
|
290
|
+
<template v-else
|
|
291
|
+
><span
|
|
292
|
+
v-if="reply.reply_to"
|
|
293
|
+
@click="onViewProfileComment(reply.reply_to.id)"
|
|
294
|
+
class="text-primary cursor-pointer mr-1"
|
|
295
|
+
>{{ reply.reply_to.name }}</span
|
|
296
|
+
>{{ getTruncatedText(`reply-${reply.id}`) }}...<span
|
|
297
|
+
class="text-primary cursor-pointer"
|
|
298
|
+
@click="toggleContentExpand(`reply-${reply.id}`)"
|
|
299
|
+
>ดูเพิ่มเติม</span
|
|
300
|
+
></template
|
|
301
|
+
>
|
|
302
|
+
</div>
|
|
181
303
|
</div>
|
|
182
304
|
<div
|
|
183
|
-
|
|
184
|
-
|
|
305
|
+
class="self-center transition-opacity"
|
|
306
|
+
:class="
|
|
307
|
+
isMenuVisible(reply.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
185
308
|
"
|
|
186
|
-
class="font-body-large whitespace-pre-wrap"
|
|
187
309
|
>
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
>
|
|
199
|
-
<template v-else
|
|
200
|
-
><span
|
|
201
|
-
v-if="reply.reply_to"
|
|
202
|
-
@click="onViewProfileComment(reply.reply_to.id)"
|
|
203
|
-
class="text-primary cursor-pointer mr-1"
|
|
204
|
-
>{{ reply.reply_to.name }}</span
|
|
205
|
-
>{{ getTruncatedText(`reply-${reply.id}`) }}...<span
|
|
206
|
-
class="text-primary cursor-pointer"
|
|
207
|
-
@click="toggleContentExpand(`reply-${reply.id}`)"
|
|
208
|
-
>ดูเพิ่มเติม</span
|
|
209
|
-
></template
|
|
210
|
-
>
|
|
310
|
+
<PickerOptionMenu
|
|
311
|
+
horizontal
|
|
312
|
+
variant="ghost"
|
|
313
|
+
circle
|
|
314
|
+
size="icon-xs"
|
|
315
|
+
icon-size="20"
|
|
316
|
+
:items="getReplyMenuItems(reply)"
|
|
317
|
+
:open="openMenuId === reply.id"
|
|
318
|
+
@update:open="(v) => openMenuId = v ? reply.id : null"
|
|
319
|
+
/>
|
|
211
320
|
</div>
|
|
212
|
-
</
|
|
321
|
+
</template>
|
|
213
322
|
</div>
|
|
214
323
|
<div class="pl-[38px] flex gap-[16px] text-gray">
|
|
215
324
|
<div class="font-body-medium">
|
|
@@ -225,7 +334,7 @@
|
|
|
225
334
|
"
|
|
226
335
|
:class="[reply.is_liked && 'text-primary']"
|
|
227
336
|
/>
|
|
228
|
-
<div :class="[reply.is_liked && 'text-primary']">
|
|
337
|
+
<div v-if="reply.like_count > 0" :class="[reply.is_liked && 'text-primary']">
|
|
229
338
|
{{ $convert.convertNumber(reply.like_count) }}
|
|
230
339
|
</div>
|
|
231
340
|
</div>
|
|
@@ -242,12 +351,12 @@
|
|
|
242
351
|
class="flex flex-col gap-[16px]"
|
|
243
352
|
>
|
|
244
353
|
<div class="flex gap-[8px] w-full">
|
|
245
|
-
<Avatar :size="30" />
|
|
354
|
+
<Avatar :size="30" :src="currentUser.avatar" />
|
|
246
355
|
<div class="flex-1">
|
|
247
356
|
<div
|
|
248
357
|
:ref="(el) => setReplyInputRef(el, reply.id)"
|
|
249
358
|
contenteditable="true"
|
|
250
|
-
class="w-full min-h-[
|
|
359
|
+
class="w-full min-h-[36px] px-[12px] py-[5px] rounded-md border border-mercury font-body-large focus:outline-none focus:ring-2 focus:ring-primary break-words bg-white empty:before:content-[attr(data-placeholder)] empty:before:text-gray"
|
|
251
360
|
data-placeholder="เขียนการตอบกลับ..."
|
|
252
361
|
@input="onReplyInput($event, reply.id)"
|
|
253
362
|
@keydown.enter.exact.prevent="onSendReplyComment(cmt)"
|
|
@@ -280,7 +389,7 @@
|
|
|
280
389
|
</template>
|
|
281
390
|
|
|
282
391
|
<script setup>
|
|
283
|
-
import { ref, nextTick,
|
|
392
|
+
import { ref, nextTick, watch } from "vue";
|
|
284
393
|
import { useRouter } from "vue-router";
|
|
285
394
|
import { useConvert } from "@/runtime/composables/useConvert";
|
|
286
395
|
const props = defineProps({
|
|
@@ -288,10 +397,9 @@ const props = defineProps({
|
|
|
288
397
|
commentCount: { type: Number, required: true },
|
|
289
398
|
currentUser: { type: Object, required: true }
|
|
290
399
|
});
|
|
291
|
-
const emit = defineEmits(["submit", "reply", "like-comment", "like-reply"]);
|
|
400
|
+
const emit = defineEmits(["submit", "reply", "like-comment", "like-reply", "edit-comment", "edit-reply", "delete-comment", "delete-reply", "report-comment", "report-reply"]);
|
|
292
401
|
const router = useRouter();
|
|
293
402
|
const { convertDateTorelativeText } = useConvert();
|
|
294
|
-
const currentUser = computed(() => props.currentUser);
|
|
295
403
|
const openedComments = ref([]);
|
|
296
404
|
const expandedContentIds = ref([]);
|
|
297
405
|
const truncatedTexts = ref({});
|
|
@@ -302,6 +410,48 @@ const replyingToId = ref(null);
|
|
|
302
410
|
const replyToUser = ref(null);
|
|
303
411
|
const replyInputRefs = ref({});
|
|
304
412
|
const replyTextMap = ref({});
|
|
413
|
+
const pinnedReplyIds = ref(/* @__PURE__ */ new Set());
|
|
414
|
+
let knownReplyIds = null;
|
|
415
|
+
watch(
|
|
416
|
+
() => props.comments.flatMap((c) => c.reply.map((r) => r.id)),
|
|
417
|
+
(allIds) => {
|
|
418
|
+
if (knownReplyIds === null) {
|
|
419
|
+
if (allIds.length === 0) return;
|
|
420
|
+
knownReplyIds = new Set(allIds);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const liveIds = new Set(allIds);
|
|
424
|
+
allIds.forEach((id) => {
|
|
425
|
+
if (knownReplyIds.has(id)) return;
|
|
426
|
+
knownReplyIds.add(id);
|
|
427
|
+
pinnedReplyIds.value.add(id);
|
|
428
|
+
});
|
|
429
|
+
knownReplyIds.forEach((id) => {
|
|
430
|
+
if (!liveIds.has(id)) {
|
|
431
|
+
knownReplyIds.delete(id);
|
|
432
|
+
pinnedReplyIds.value.delete(id);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
},
|
|
436
|
+
{ immediate: true }
|
|
437
|
+
);
|
|
438
|
+
const getHiddenCount = (cmt) => cmt.reply.filter((r) => !pinnedReplyIds.value.has(r.id)).length;
|
|
439
|
+
const isReplyVisible = (cmt, reply) => openedComments.value.includes(cmt.id) || pinnedReplyIds.value.has(reply.id);
|
|
440
|
+
const hoveredId = ref(null);
|
|
441
|
+
const openMenuId = ref(null);
|
|
442
|
+
const isMenuVisible = (id) => hoveredId.value === id || openMenuId.value === id;
|
|
443
|
+
const editingId = ref(null);
|
|
444
|
+
const editInputRefs = ref({});
|
|
445
|
+
const editTextMap = ref({});
|
|
446
|
+
const isSelfUser = (user) => !!props.currentUser.id && user.id === props.currentUser.id;
|
|
447
|
+
const buildMentionChip = (name) => {
|
|
448
|
+
const chip = document.createElement("span");
|
|
449
|
+
chip.dataset.type = "mention";
|
|
450
|
+
chip.contentEditable = "false";
|
|
451
|
+
chip.className = "inline-flex items-center text-primary font-body-large-prominent select-none mr-1";
|
|
452
|
+
chip.textContent = name;
|
|
453
|
+
return chip;
|
|
454
|
+
};
|
|
305
455
|
const onToggleReplyComment = (id) => {
|
|
306
456
|
if (openedComments.value.includes(id)) {
|
|
307
457
|
openedComments.value = openedComments.value.filter((i) => i !== id);
|
|
@@ -396,21 +546,17 @@ const setReplyInputRef = (el, id) => {
|
|
|
396
546
|
else delete replyInputRefs.value[id];
|
|
397
547
|
};
|
|
398
548
|
const onReplyComment = (id, user) => {
|
|
549
|
+
const isSelf = isSelfUser(user);
|
|
399
550
|
replyingToId.value = id;
|
|
400
|
-
replyToUser.value =
|
|
551
|
+
replyToUser.value = isSelf ? null : user;
|
|
401
552
|
replyTextMap.value[id] = "";
|
|
402
553
|
nextTick(() => {
|
|
403
554
|
const el = replyInputRefs.value[id];
|
|
404
555
|
if (!el) return;
|
|
405
556
|
el.innerHTML = "";
|
|
406
|
-
if (
|
|
407
|
-
const chip = document.createElement("span");
|
|
408
|
-
chip.dataset.type = "mention";
|
|
409
|
-
chip.contentEditable = "false";
|
|
410
|
-
chip.className = "inline-flex items-center text-primary font-body-large-prominent select-none mr-1";
|
|
411
|
-
chip.textContent = user.name;
|
|
557
|
+
if (!isSelf) {
|
|
412
558
|
const space = document.createTextNode("\xA0");
|
|
413
|
-
el.appendChild(
|
|
559
|
+
el.appendChild(buildMentionChip(user.name));
|
|
414
560
|
el.appendChild(space);
|
|
415
561
|
const range = document.createRange();
|
|
416
562
|
const sel = window.getSelection();
|
|
@@ -498,7 +644,88 @@ const onToggleLikeReplyComment = (reply) => {
|
|
|
498
644
|
const onViewProfileComment = (id) => {
|
|
499
645
|
if (id) router.push(`/@${id}`);
|
|
500
646
|
};
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
647
|
+
const setEditInputRef = (el, id) => {
|
|
648
|
+
if (el) editInputRefs.value[id] = el;
|
|
649
|
+
else delete editInputRefs.value[id];
|
|
650
|
+
};
|
|
651
|
+
const onEditClick = (id, content, replyTo) => {
|
|
652
|
+
editingId.value = id;
|
|
653
|
+
editTextMap.value[id] = content;
|
|
654
|
+
nextTick(() => {
|
|
655
|
+
const el = editInputRefs.value[id];
|
|
656
|
+
if (!el) return;
|
|
657
|
+
el.innerHTML = "";
|
|
658
|
+
if (replyTo) {
|
|
659
|
+
el.appendChild(buildMentionChip(replyTo.name));
|
|
660
|
+
el.appendChild(document.createTextNode("\xA0"));
|
|
661
|
+
}
|
|
662
|
+
el.appendChild(document.createTextNode(content));
|
|
663
|
+
const range = document.createRange();
|
|
664
|
+
range.selectNodeContents(el);
|
|
665
|
+
range.collapse(false);
|
|
666
|
+
const sel = window.getSelection();
|
|
667
|
+
sel?.removeAllRanges();
|
|
668
|
+
sel?.addRange(range);
|
|
669
|
+
el.focus();
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
const onEditInput = (event, id) => {
|
|
673
|
+
const el = event.target;
|
|
674
|
+
editTextMap.value[id] = readTextWithBreaks(el, true);
|
|
675
|
+
};
|
|
676
|
+
const onCancelEdit = () => {
|
|
677
|
+
const id = editingId.value;
|
|
678
|
+
if (id) delete editTextMap.value[id];
|
|
679
|
+
editingId.value = null;
|
|
680
|
+
};
|
|
681
|
+
const onSendEdit = (item, type) => {
|
|
682
|
+
const content = editTextMap.value[item.id]?.trim() ?? "";
|
|
683
|
+
if (!content) return;
|
|
684
|
+
if (type === "comment") emit("edit-comment", item, content);
|
|
685
|
+
else emit("edit-reply", item, content);
|
|
686
|
+
delete editTextMap.value[item.id];
|
|
687
|
+
editingId.value = null;
|
|
688
|
+
};
|
|
689
|
+
const getCommentMenuItems = (comment) => isSelfUser(comment.user) ? [
|
|
690
|
+
{
|
|
691
|
+
label: "\u0E41\u0E01\u0E49\u0E44\u0E02",
|
|
692
|
+
name: "edit",
|
|
693
|
+
icon: "lucide:pencil",
|
|
694
|
+
action: () => onEditClick(comment.id, comment.content)
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
label: "\u0E25\u0E1A",
|
|
698
|
+
name: "delete",
|
|
699
|
+
icon: "lucide:trash-2",
|
|
700
|
+
action: () => emit("delete-comment", comment)
|
|
701
|
+
}
|
|
702
|
+
] : [
|
|
703
|
+
{
|
|
704
|
+
label: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19",
|
|
705
|
+
name: "report",
|
|
706
|
+
icon: "lucide:flag",
|
|
707
|
+
action: () => emit("report-comment", comment)
|
|
708
|
+
}
|
|
709
|
+
];
|
|
710
|
+
const getReplyMenuItems = (reply) => isSelfUser(reply.user) ? [
|
|
711
|
+
{
|
|
712
|
+
label: "\u0E41\u0E01\u0E49\u0E44\u0E02",
|
|
713
|
+
name: "edit",
|
|
714
|
+
icon: "lucide:pencil",
|
|
715
|
+
action: () => onEditClick(reply.id, reply.content, reply.reply_to)
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
label: "\u0E25\u0E1A",
|
|
719
|
+
name: "delete",
|
|
720
|
+
icon: "lucide:trash-2",
|
|
721
|
+
action: () => emit("delete-reply", reply)
|
|
722
|
+
}
|
|
723
|
+
] : [
|
|
724
|
+
{
|
|
725
|
+
label: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19",
|
|
726
|
+
name: "report",
|
|
727
|
+
icon: "lucide:flag",
|
|
728
|
+
action: () => emit("report-reply", reply)
|
|
729
|
+
}
|
|
730
|
+
];
|
|
504
731
|
</script>
|
|
@@ -32,11 +32,23 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
32
32
|
reply: (commentId: string, replyToUserId: string | undefined, content: string) => any;
|
|
33
33
|
"like-comment": (comment: CommentItem) => any;
|
|
34
34
|
"like-reply": (reply: Reply) => any;
|
|
35
|
+
"edit-comment": (comment: CommentItem, content: string) => any;
|
|
36
|
+
"edit-reply": (reply: Reply, content: string) => any;
|
|
37
|
+
"delete-comment": (comment: CommentItem) => any;
|
|
38
|
+
"delete-reply": (reply: Reply) => any;
|
|
39
|
+
"report-comment": (comment: CommentItem) => any;
|
|
40
|
+
"report-reply": (reply: Reply) => any;
|
|
35
41
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
42
|
onSubmit?: ((content: string) => any) | undefined;
|
|
37
43
|
onReply?: ((commentId: string, replyToUserId: string | undefined, content: string) => any) | undefined;
|
|
38
44
|
"onLike-comment"?: ((comment: CommentItem) => any) | undefined;
|
|
39
45
|
"onLike-reply"?: ((reply: Reply) => any) | undefined;
|
|
46
|
+
"onEdit-comment"?: ((comment: CommentItem, content: string) => any) | undefined;
|
|
47
|
+
"onEdit-reply"?: ((reply: Reply, content: string) => any) | undefined;
|
|
48
|
+
"onDelete-comment"?: ((comment: CommentItem) => any) | undefined;
|
|
49
|
+
"onDelete-reply"?: ((reply: Reply) => any) | undefined;
|
|
50
|
+
"onReport-comment"?: ((comment: CommentItem) => any) | undefined;
|
|
51
|
+
"onReport-reply"?: ((reply: Reply) => any) | undefined;
|
|
40
52
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
53
|
declare const _default: typeof __VLS_export;
|
|
42
54
|
export default _default;
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ModalReportProps } from "@/types/components/modal/modal-report";
|
|
2
|
+
type __VLS_Props = ModalReportProps;
|
|
3
|
+
type __VLS_ModelProps = {
|
|
4
|
+
modelValue?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
"update:modelValue": (value: boolean) => any;
|
|
9
|
+
} & {
|
|
10
|
+
submit: (payload: {
|
|
11
|
+
value: string;
|
|
12
|
+
detail: string;
|
|
13
|
+
}) => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
15
|
+
onSubmit?: ((payload: {
|
|
16
|
+
value: string;
|
|
17
|
+
detail: string;
|
|
18
|
+
}) => any) | undefined;
|
|
19
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
20
|
+
}>, {
|
|
21
|
+
loading: boolean;
|
|
22
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
2
23
|
declare const _default: typeof __VLS_export;
|
|
3
24
|
export default _default;
|