pukaad-ui-lib 1.202.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 +307 -82
- package/dist/runtime/components/comment.vue.d.ts +12 -0
- package/dist/runtime/components/image/image-cropper.d.vue.ts +2 -2
- package/dist/runtime/components/image/image-cropper.vue.d.ts +2 -2
- package/dist/runtime/components/input/input-file.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-file.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-password.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-password.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-slider.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-slider.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-textarea.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-textarea.vue.d.ts +1 -1
- package/dist/runtime/components/modal/modal-password-confirmed.d.vue.ts +1 -1
- package/dist/runtime/components/modal/modal-password-confirmed.vue.d.ts +1 -1
- package/dist/runtime/components/modal/modal-password-verify.d.vue.ts +1 -1
- package/dist/runtime/components/modal/modal-password-verify.vue.d.ts +1 -1
- 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/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;
|
|
@@ -37,41 +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"
|
|
43
47
|
:src="cmt.user.avatar"
|
|
44
48
|
class="cursor-pointer"
|
|
45
49
|
@click="onViewProfileComment(cmt.user.id)"
|
|
46
50
|
/>
|
|
47
|
-
<
|
|
48
|
-
<div
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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>
|
|
53
78
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
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="
|
|
56
90
|
(el) => registerContentRef(el, `comment-${cmt.id}`)
|
|
57
91
|
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
92
|
+
class="font-body-large whitespace-pre-wrap"
|
|
93
|
+
>
|
|
94
|
+
<template
|
|
95
|
+
v-if="
|
|
62
96
|
isContentExpanded(`comment-${cmt.id}`) || !isContentOverflowing(`comment-${cmt.id}`)
|
|
63
97
|
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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>
|
|
73
108
|
</div>
|
|
74
|
-
|
|
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>
|
|
75
127
|
</div>
|
|
76
128
|
<div class="pl-[38px] flex gap-[16px] text-gray">
|
|
77
129
|
<div class="font-body-medium">
|
|
@@ -87,7 +139,7 @@
|
|
|
87
139
|
"
|
|
88
140
|
:class="[cmt.is_liked && 'text-primary']"
|
|
89
141
|
/>
|
|
90
|
-
<div :class="[cmt.is_liked && 'text-primary']">
|
|
142
|
+
<div v-if="cmt.like_count > 0" :class="[cmt.is_liked && 'text-primary']">
|
|
91
143
|
{{ $convert.convertNumber(cmt.like_count) }}
|
|
92
144
|
</div>
|
|
93
145
|
</div>
|
|
@@ -99,17 +151,17 @@
|
|
|
99
151
|
</div>
|
|
100
152
|
</div>
|
|
101
153
|
<div
|
|
102
|
-
v-if="
|
|
154
|
+
v-if="getHiddenCount(cmt) > 0"
|
|
103
155
|
class="pl-[38px] flex flex-col gap-[16px]"
|
|
104
156
|
>
|
|
105
157
|
<Button
|
|
106
158
|
variant="text"
|
|
107
|
-
class="justify-start text-primary
|
|
159
|
+
class="justify-start text-primary h-auto gap-[4px] w-fit"
|
|
108
160
|
@click="onToggleReplyComment(cmt.id)"
|
|
109
161
|
>
|
|
110
162
|
<div class="font-body-large-prominent">
|
|
111
163
|
การตอบกลับ
|
|
112
|
-
{{ $convert.convertNumber(
|
|
164
|
+
{{ $convert.convertNumber(getHiddenCount(cmt)) }}
|
|
113
165
|
รายการ
|
|
114
166
|
</div>
|
|
115
167
|
<Icon
|
|
@@ -154,64 +206,119 @@
|
|
|
154
206
|
</div>
|
|
155
207
|
</div>
|
|
156
208
|
<div
|
|
157
|
-
v-if="
|
|
158
|
-
|
|
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]"
|
|
159
212
|
>
|
|
160
213
|
<div
|
|
161
214
|
v-for="reply in cmt.reply"
|
|
162
|
-
v-show="
|
|
163
|
-
openedComments.includes(cmt.id) || reply.user.id === currentUser.id
|
|
164
|
-
"
|
|
215
|
+
v-show="isReplyVisible(cmt, reply)"
|
|
165
216
|
:key="reply.id"
|
|
166
|
-
:data-reply-id="reply.id"
|
|
167
217
|
class="flex flex-col gap-[16px]"
|
|
168
218
|
>
|
|
169
|
-
<div class="flex flex-col gap-[4px]
|
|
170
|
-
<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
|
+
>
|
|
171
225
|
<Avatar
|
|
172
226
|
:size="30"
|
|
173
227
|
:src="reply.user.avatar"
|
|
174
228
|
class="cursor-pointer"
|
|
175
229
|
@click="onViewProfileComment(reply.user.id)"
|
|
176
230
|
/>
|
|
177
|
-
<
|
|
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>
|
|
178
261
|
<div
|
|
179
|
-
class="
|
|
180
|
-
@click="onViewProfileComment(reply.user.id)"
|
|
262
|
+
class="flex flex-col bg-bright p-[8px] rounded-lg gap-[4px]"
|
|
181
263
|
>
|
|
182
|
-
|
|
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>
|
|
183
303
|
</div>
|
|
184
304
|
<div
|
|
185
|
-
|
|
186
|
-
|
|
305
|
+
class="self-center transition-opacity"
|
|
306
|
+
:class="
|
|
307
|
+
isMenuVisible(reply.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
187
308
|
"
|
|
188
|
-
class="font-body-large whitespace-pre-wrap"
|
|
189
309
|
>
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
>
|
|
201
|
-
<template v-else
|
|
202
|
-
><span
|
|
203
|
-
v-if="reply.reply_to"
|
|
204
|
-
@click="onViewProfileComment(reply.reply_to.id)"
|
|
205
|
-
class="text-primary cursor-pointer mr-1"
|
|
206
|
-
>{{ reply.reply_to.name }}</span
|
|
207
|
-
>{{ getTruncatedText(`reply-${reply.id}`) }}...<span
|
|
208
|
-
class="text-primary cursor-pointer"
|
|
209
|
-
@click="toggleContentExpand(`reply-${reply.id}`)"
|
|
210
|
-
>ดูเพิ่มเติม</span
|
|
211
|
-
></template
|
|
212
|
-
>
|
|
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
|
+
/>
|
|
213
320
|
</div>
|
|
214
|
-
</
|
|
321
|
+
</template>
|
|
215
322
|
</div>
|
|
216
323
|
<div class="pl-[38px] flex gap-[16px] text-gray">
|
|
217
324
|
<div class="font-body-medium">
|
|
@@ -227,7 +334,7 @@
|
|
|
227
334
|
"
|
|
228
335
|
:class="[reply.is_liked && 'text-primary']"
|
|
229
336
|
/>
|
|
230
|
-
<div :class="[reply.is_liked && 'text-primary']">
|
|
337
|
+
<div v-if="reply.like_count > 0" :class="[reply.is_liked && 'text-primary']">
|
|
231
338
|
{{ $convert.convertNumber(reply.like_count) }}
|
|
232
339
|
</div>
|
|
233
340
|
</div>
|
|
@@ -249,7 +356,7 @@
|
|
|
249
356
|
<div
|
|
250
357
|
:ref="(el) => setReplyInputRef(el, reply.id)"
|
|
251
358
|
contenteditable="true"
|
|
252
|
-
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"
|
|
253
360
|
data-placeholder="เขียนการตอบกลับ..."
|
|
254
361
|
@input="onReplyInput($event, reply.id)"
|
|
255
362
|
@keydown.enter.exact.prevent="onSendReplyComment(cmt)"
|
|
@@ -282,7 +389,7 @@
|
|
|
282
389
|
</template>
|
|
283
390
|
|
|
284
391
|
<script setup>
|
|
285
|
-
import { ref, nextTick,
|
|
392
|
+
import { ref, nextTick, watch } from "vue";
|
|
286
393
|
import { useRouter } from "vue-router";
|
|
287
394
|
import { useConvert } from "@/runtime/composables/useConvert";
|
|
288
395
|
const props = defineProps({
|
|
@@ -290,10 +397,9 @@ const props = defineProps({
|
|
|
290
397
|
commentCount: { type: Number, required: true },
|
|
291
398
|
currentUser: { type: Object, required: true }
|
|
292
399
|
});
|
|
293
|
-
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"]);
|
|
294
401
|
const router = useRouter();
|
|
295
402
|
const { convertDateTorelativeText } = useConvert();
|
|
296
|
-
const currentUser = computed(() => props.currentUser);
|
|
297
403
|
const openedComments = ref([]);
|
|
298
404
|
const expandedContentIds = ref([]);
|
|
299
405
|
const truncatedTexts = ref({});
|
|
@@ -304,6 +410,48 @@ const replyingToId = ref(null);
|
|
|
304
410
|
const replyToUser = ref(null);
|
|
305
411
|
const replyInputRefs = ref({});
|
|
306
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
|
+
};
|
|
307
455
|
const onToggleReplyComment = (id) => {
|
|
308
456
|
if (openedComments.value.includes(id)) {
|
|
309
457
|
openedComments.value = openedComments.value.filter((i) => i !== id);
|
|
@@ -398,21 +546,17 @@ const setReplyInputRef = (el, id) => {
|
|
|
398
546
|
else delete replyInputRefs.value[id];
|
|
399
547
|
};
|
|
400
548
|
const onReplyComment = (id, user) => {
|
|
549
|
+
const isSelf = isSelfUser(user);
|
|
401
550
|
replyingToId.value = id;
|
|
402
|
-
replyToUser.value =
|
|
551
|
+
replyToUser.value = isSelf ? null : user;
|
|
403
552
|
replyTextMap.value[id] = "";
|
|
404
553
|
nextTick(() => {
|
|
405
554
|
const el = replyInputRefs.value[id];
|
|
406
555
|
if (!el) return;
|
|
407
556
|
el.innerHTML = "";
|
|
408
|
-
if (
|
|
409
|
-
const chip = document.createElement("span");
|
|
410
|
-
chip.dataset.type = "mention";
|
|
411
|
-
chip.contentEditable = "false";
|
|
412
|
-
chip.className = "inline-flex items-center text-primary font-body-large-prominent select-none mr-1";
|
|
413
|
-
chip.textContent = user.name;
|
|
557
|
+
if (!isSelf) {
|
|
414
558
|
const space = document.createTextNode("\xA0");
|
|
415
|
-
el.appendChild(
|
|
559
|
+
el.appendChild(buildMentionChip(user.name));
|
|
416
560
|
el.appendChild(space);
|
|
417
561
|
const range = document.createRange();
|
|
418
562
|
const sel = window.getSelection();
|
|
@@ -500,7 +644,88 @@ const onToggleLikeReplyComment = (reply) => {
|
|
|
500
644
|
const onViewProfileComment = (id) => {
|
|
501
645
|
if (id) router.push(`/@${id}`);
|
|
502
646
|
};
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
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
|
+
];
|
|
506
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;
|
|
@@ -64,15 +64,15 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
|
|
|
64
64
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageCropperProps> & Readonly<{}>, {
|
|
65
65
|
src: string;
|
|
66
66
|
center: boolean;
|
|
67
|
+
background: boolean;
|
|
68
|
+
modal: boolean;
|
|
67
69
|
responsive: boolean;
|
|
68
70
|
restore: boolean;
|
|
69
71
|
checkCrossOrigin: boolean;
|
|
70
72
|
checkOrientation: boolean;
|
|
71
73
|
crossorigin: "" | "anonymous" | "use-credentials";
|
|
72
|
-
modal: boolean;
|
|
73
74
|
guides: boolean;
|
|
74
75
|
highlight: boolean;
|
|
75
|
-
background: boolean;
|
|
76
76
|
autoCrop: boolean;
|
|
77
77
|
movable: boolean;
|
|
78
78
|
rotatable: boolean;
|
|
@@ -64,15 +64,15 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
|
|
|
64
64
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageCropperProps> & Readonly<{}>, {
|
|
65
65
|
src: string;
|
|
66
66
|
center: boolean;
|
|
67
|
+
background: boolean;
|
|
68
|
+
modal: boolean;
|
|
67
69
|
responsive: boolean;
|
|
68
70
|
restore: boolean;
|
|
69
71
|
checkCrossOrigin: boolean;
|
|
70
72
|
checkOrientation: boolean;
|
|
71
73
|
crossorigin: "" | "anonymous" | "use-credentials";
|
|
72
|
-
modal: boolean;
|
|
73
74
|
guides: boolean;
|
|
74
75
|
highlight: boolean;
|
|
75
|
-
background: boolean;
|
|
76
76
|
autoCrop: boolean;
|
|
77
77
|
movable: boolean;
|
|
78
78
|
rotatable: boolean;
|
|
@@ -31,8 +31,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
31
31
|
fullHeight: boolean;
|
|
32
32
|
name: string;
|
|
33
33
|
limit: number;
|
|
34
|
-
accept: string;
|
|
35
34
|
disabledErrorMessage: boolean;
|
|
35
|
+
accept: string;
|
|
36
36
|
labelIcon: string;
|
|
37
37
|
disabledDrop: boolean;
|
|
38
38
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -31,8 +31,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
31
31
|
fullHeight: boolean;
|
|
32
32
|
name: string;
|
|
33
33
|
limit: number;
|
|
34
|
-
accept: string;
|
|
35
34
|
disabledErrorMessage: boolean;
|
|
35
|
+
accept: string;
|
|
36
36
|
labelIcon: string;
|
|
37
37
|
disabledDrop: boolean;
|
|
38
38
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
22
22
|
}>, {
|
|
23
23
|
id: string;
|
|
24
24
|
name: string;
|
|
25
|
-
disabledForgotPassword: boolean;
|
|
26
25
|
new: boolean;
|
|
26
|
+
disabledForgotPassword: boolean;
|
|
27
27
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
28
|
declare const _default: typeof __VLS_export;
|
|
29
29
|
export default _default;
|
|
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
22
22
|
}>, {
|
|
23
23
|
id: string;
|
|
24
24
|
name: string;
|
|
25
|
-
disabledForgotPassword: boolean;
|
|
26
25
|
new: boolean;
|
|
26
|
+
disabledForgotPassword: boolean;
|
|
27
27
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
28
|
declare const _default: typeof __VLS_export;
|
|
29
29
|
export default _default;
|
|
@@ -12,9 +12,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
12
12
|
label: string;
|
|
13
13
|
color: InputSliderColor;
|
|
14
14
|
fullWidth: boolean;
|
|
15
|
-
step: number;
|
|
16
15
|
max: number;
|
|
17
16
|
min: number;
|
|
17
|
+
step: number;
|
|
18
18
|
lineHeight: number | string;
|
|
19
19
|
appearance: boolean;
|
|
20
20
|
thumbSize: number | string;
|