pukaad-ui-lib 1.232.0 → 1.234.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.vue +105 -285
- package/dist/runtime/components/drawer/drawer-post-blog.d.vue.ts +1 -0
- package/dist/runtime/components/drawer/drawer-post-blog.vue +18 -23
- package/dist/runtime/components/drawer/drawer-post-blog.vue.d.ts +1 -0
- package/dist/runtime/components/input/input-tag.vue +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -10,72 +10,36 @@
|
|
|
10
10
|
<div class="flex flex-col gap-[16px]">
|
|
11
11
|
<div class="flex gap-[8px] w-full">
|
|
12
12
|
<Avatar :size="30" :src="currentUser.avatar" />
|
|
13
|
-
<div
|
|
14
|
-
ref="mainInputRef"
|
|
15
|
-
contenteditable="true"
|
|
13
|
+
<div ref="mainInputRef" contenteditable="true"
|
|
16
14
|
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"
|
|
17
|
-
data-placeholder="เพิ่มความคิดเห็น"
|
|
18
|
-
@
|
|
19
|
-
@input="onMainCommentInput"
|
|
20
|
-
@keydown.enter.exact.prevent="onSendComment"
|
|
21
|
-
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
22
|
-
/>
|
|
15
|
+
data-placeholder="เพิ่มความคิดเห็น" @focus="isShowMainActions = true" @input="onMainCommentInput"
|
|
16
|
+
@keydown.enter.exact.prevent="onSendComment" @keydown.shift.enter.prevent="onInsertLineBreak" />
|
|
23
17
|
</div>
|
|
24
18
|
<div v-if="isShowMainActions" class="flex justify-end gap-[8px]">
|
|
25
19
|
<Button variant="outline" class="w-[56px]" @click="onCancelComment">
|
|
26
20
|
ยกเลิก
|
|
27
21
|
</Button>
|
|
28
|
-
<Button
|
|
29
|
-
color="primary"
|
|
30
|
-
class="w-[56px]"
|
|
31
|
-
:disabled="!mainCommentInput"
|
|
32
|
-
@click="onSendComment"
|
|
33
|
-
>
|
|
22
|
+
<Button color="primary" class="w-[56px]" :disabled="!mainCommentInput" @click="onSendComment">
|
|
34
23
|
ส่ง
|
|
35
24
|
</Button>
|
|
36
25
|
</div>
|
|
37
26
|
</div>
|
|
38
|
-
<div
|
|
39
|
-
v-for="cmt in resolvedComments"
|
|
40
|
-
:key="cmt.id"
|
|
41
|
-
class="flex flex-col gap-[16px] w-full"
|
|
42
|
-
>
|
|
27
|
+
<div v-for="cmt in resolvedComments" :key="cmt.id" class="flex flex-col gap-[16px] w-full">
|
|
43
28
|
<div class="flex flex-col gap-[4px] w-full">
|
|
44
|
-
<div
|
|
45
|
-
class="
|
|
46
|
-
@mouseenter="hoveredId = cmt.id"
|
|
47
|
-
@mouseleave="hoveredId = null"
|
|
48
|
-
>
|
|
49
|
-
<Avatar
|
|
50
|
-
:size="30"
|
|
51
|
-
:src="cmt.user.avatar"
|
|
52
|
-
class="cursor-pointer"
|
|
53
|
-
@click="onViewProfileComment(cmt.user.id)"
|
|
54
|
-
/>
|
|
29
|
+
<div class="flex gap-[8px]" @mouseenter="hoveredId = cmt.id" @mouseleave="hoveredId = null">
|
|
30
|
+
<Avatar :size="30" :src="cmt.user.avatar" class="cursor-pointer" @click="onViewProfileComment(cmt.user.id)" />
|
|
55
31
|
<template v-if="editingId === cmt.id">
|
|
56
32
|
<div class="flex-1 flex flex-col gap-[4px]">
|
|
57
|
-
<div
|
|
58
|
-
:ref="(el) => setEditInputRef(el, cmt.id)"
|
|
59
|
-
contenteditable="true"
|
|
33
|
+
<div :ref="(el) => setEditInputRef(el, cmt.id)" contenteditable="true"
|
|
60
34
|
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"
|
|
61
|
-
@input="onEditInput($event, cmt.id)"
|
|
62
|
-
@keydown.enter.
|
|
63
|
-
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
64
|
-
/>
|
|
35
|
+
@input="onEditInput($event, cmt.id)" @keydown.enter.exact.prevent="onSendEdit(cmt, 'comment')"
|
|
36
|
+
@keydown.shift.enter.prevent="onInsertLineBreak" />
|
|
65
37
|
<div class="flex justify-end gap-[8px]">
|
|
66
|
-
<Button
|
|
67
|
-
variant="outline"
|
|
68
|
-
class="w-[56px]"
|
|
69
|
-
@click="onCancelEdit"
|
|
70
|
-
>
|
|
38
|
+
<Button variant="outline" class="w-[56px]" @click="onCancelEdit">
|
|
71
39
|
ยกเลิก
|
|
72
40
|
</Button>
|
|
73
|
-
<Button
|
|
74
|
-
|
|
75
|
-
class="w-[56px]"
|
|
76
|
-
:disabled="!editTextMap[cmt.id]"
|
|
77
|
-
@click="onSendEdit(cmt, 'comment')"
|
|
78
|
-
>
|
|
41
|
+
<Button color="primary" class="w-[56px]" :disabled="!editTextMap[cmt.id]"
|
|
42
|
+
@click="onSendEdit(cmt, 'comment')">
|
|
79
43
|
ส่ง
|
|
80
44
|
</Button>
|
|
81
45
|
</div>
|
|
@@ -83,51 +47,31 @@
|
|
|
83
47
|
</template>
|
|
84
48
|
<template v-else>
|
|
85
49
|
<div class="flex flex-col gap-[4px] bg-bright p-[8px] rounded-lg">
|
|
86
|
-
<div
|
|
87
|
-
class="font-body-large-prominent cursor-pointer"
|
|
88
|
-
@click="onViewProfileComment(cmt.user.id)"
|
|
89
|
-
>
|
|
50
|
+
<div class="font-body-large-prominent cursor-pointer" @click="onViewProfileComment(cmt.user.id)">
|
|
90
51
|
{{ cmt.user.name }}
|
|
91
52
|
</div>
|
|
92
|
-
<div
|
|
93
|
-
:ref="
|
|
53
|
+
<div :ref="
|
|
94
54
|
(el) => registerContentRef(el, `comment-${cmt.id}`)
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
>
|
|
98
|
-
<template
|
|
99
|
-
v-if="
|
|
55
|
+
" class="font-body-large whitespace-pre-wrap">
|
|
56
|
+
<template v-if="
|
|
100
57
|
isContentExpanded(`comment-${cmt.id}`) || !isContentOverflowing(`comment-${cmt.id}`)
|
|
101
|
-
"
|
|
102
|
-
>
|
|
58
|
+
">
|
|
103
59
|
{{ normalizeContent(cmt.content) }}
|
|
104
60
|
</template>
|
|
105
61
|
<template v-else>
|
|
106
|
-
{{ getTruncatedText(`comment-${cmt.id}`) }}...<span
|
|
107
|
-
|
|
108
|
-
@click="toggleContentExpand(`comment-${cmt.id}`)"
|
|
109
|
-
>
|
|
62
|
+
{{ getTruncatedText(`comment-${cmt.id}`) }}...<span class="text-primary cursor-pointer"
|
|
63
|
+
@click="toggleContentExpand(`comment-${cmt.id}`)">
|
|
110
64
|
ดูเพิ่มเติม
|
|
111
65
|
</span>
|
|
112
66
|
</template>
|
|
113
67
|
</div>
|
|
114
68
|
</div>
|
|
115
|
-
<div
|
|
116
|
-
class="self-center transition-opacity"
|
|
117
|
-
:class="
|
|
69
|
+
<div class="self-center transition-opacity" :class="
|
|
118
70
|
isMenuVisible(cmt.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
variant="ghost"
|
|
124
|
-
circle
|
|
125
|
-
size="icon-sm"
|
|
126
|
-
icon-size="20"
|
|
127
|
-
:items="getCommentMenuItems(cmt)"
|
|
128
|
-
:open="openMenuId === cmt.id"
|
|
129
|
-
@update:open="(v) => openMenuId = v ? cmt.id : null"
|
|
130
|
-
/>
|
|
71
|
+
">
|
|
72
|
+
<PickerOptionMenu horizontal variant="ghost" circle size="icon-sm" icon-size="20"
|
|
73
|
+
:items="getCommentMenuItems(cmt)" :open="openMenuId === cmt.id"
|
|
74
|
+
@update:open="(v) => openMenuId = v ? cmt.id : null" />
|
|
131
75
|
</div>
|
|
132
76
|
</template>
|
|
133
77
|
</div>
|
|
@@ -135,50 +79,29 @@
|
|
|
135
79
|
<div class="font-body-large">
|
|
136
80
|
{{ convertDateTorelativeText(cmt.created_at) }}
|
|
137
81
|
</div>
|
|
138
|
-
<div
|
|
139
|
-
|
|
140
|
-
@click="onToggleLikeComment(cmt)"
|
|
141
|
-
>
|
|
142
|
-
<Icon
|
|
143
|
-
:name="
|
|
82
|
+
<div class="flex gap-[4px] items-center cursor-pointer" @click="onToggleLikeComment(cmt)">
|
|
83
|
+
<Icon :name="
|
|
144
84
|
cmt.is_liked ? 'pukaad:thumbs-up-solid' : 'pukaad:thumbs-up-regular'
|
|
145
|
-
"
|
|
146
|
-
|
|
147
|
-
size="20"
|
|
148
|
-
/>
|
|
149
|
-
<div
|
|
150
|
-
v-if="cmt.like_count > 0"
|
|
151
|
-
:class="[cmt.is_liked && 'text-primary']"
|
|
152
|
-
>
|
|
85
|
+
" :class="[cmt.is_liked && 'text-primary']" size="20" />
|
|
86
|
+
<div v-if="cmt.like_count > 0" :class="[cmt.is_liked && 'text-primary']">
|
|
153
87
|
{{ $convert.convertNumber(cmt.like_count) }}
|
|
154
88
|
</div>
|
|
155
89
|
</div>
|
|
156
|
-
<div
|
|
157
|
-
class="font-body-large cursor-pointer"
|
|
158
|
-
@click="onReplyComment(cmt.id, cmt.user)"
|
|
159
|
-
>
|
|
90
|
+
<div class="font-body-large cursor-pointer" @click="onReplyComment(cmt.id, cmt.user)">
|
|
160
91
|
ตอบกลับ
|
|
161
92
|
</div>
|
|
162
93
|
</div>
|
|
163
|
-
<div
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
>
|
|
167
|
-
<Button
|
|
168
|
-
variant="text"
|
|
169
|
-
class="justify-start text-primary h-auto gap-[4px] w-fit"
|
|
170
|
-
@click="onToggleReplyComment(cmt.id)"
|
|
171
|
-
>
|
|
94
|
+
<div v-if="getHiddenCount(cmt) > 0" class="pl-[38px] flex flex-col gap-[16px]">
|
|
95
|
+
<Button variant="text" class="justify-start text-primary h-auto gap-[4px] w-fit"
|
|
96
|
+
@click="onToggleReplyComment(cmt.id)">
|
|
172
97
|
<div class="font-body-large">
|
|
173
98
|
การตอบกลับ
|
|
174
99
|
{{ $convert.convertNumber(getHiddenCount(cmt)) }}
|
|
175
100
|
รายการ
|
|
176
101
|
</div>
|
|
177
|
-
<Icon
|
|
178
|
-
:name="
|
|
102
|
+
<Icon :name="
|
|
179
103
|
openedComments.includes(cmt.id) ? 'lucide:chevron-up' : 'lucide:chevron-down'
|
|
180
|
-
"
|
|
181
|
-
/>
|
|
104
|
+
" />
|
|
182
105
|
</Button>
|
|
183
106
|
</div>
|
|
184
107
|
</div>
|
|
@@ -186,151 +109,76 @@
|
|
|
186
109
|
<div class="flex gap-[8px] w-full">
|
|
187
110
|
<Avatar :size="30" :src="currentUser.avatar" />
|
|
188
111
|
<div class="flex-1">
|
|
189
|
-
<div
|
|
190
|
-
:ref="(el) => setReplyInputRef(el, cmt.id)"
|
|
191
|
-
contenteditable="true"
|
|
112
|
+
<div :ref="(el) => setReplyInputRef(el, cmt.id)" contenteditable="true"
|
|
192
113
|
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"
|
|
193
|
-
data-placeholder="เขียนการตอบกลับ..."
|
|
194
|
-
@
|
|
195
|
-
@keydown.enter.exact.prevent="onSendReplyComment(cmt)"
|
|
196
|
-
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
197
|
-
/>
|
|
114
|
+
data-placeholder="เขียนการตอบกลับ..." @input="onReplyInput($event, cmt.id)"
|
|
115
|
+
@keydown.enter.exact.prevent="onSendReplyComment(cmt)" @keydown.shift.enter.prevent="onInsertLineBreak" />
|
|
198
116
|
</div>
|
|
199
117
|
</div>
|
|
200
118
|
<div class="flex justify-end gap-[8px]">
|
|
201
|
-
<Button
|
|
202
|
-
variant="outline"
|
|
203
|
-
class="w-[56px]"
|
|
204
|
-
@click="onCancelReplyComment"
|
|
205
|
-
>
|
|
119
|
+
<Button variant="outline" class="w-[56px]" @click="onCancelReplyComment">
|
|
206
120
|
ยกเลิก
|
|
207
121
|
</Button>
|
|
208
|
-
<Button
|
|
209
|
-
color="primary"
|
|
210
|
-
class="w-[68px]"
|
|
211
|
-
:disabled="!replyTextMap[cmt.id]"
|
|
212
|
-
@click="onSendReplyComment(cmt)"
|
|
213
|
-
>
|
|
122
|
+
<Button color="primary" class="w-[68px]" :disabled="!replyTextMap[cmt.id]" @click="onSendReplyComment(cmt)">
|
|
214
123
|
ตอบกลับ
|
|
215
124
|
</Button>
|
|
216
125
|
</div>
|
|
217
126
|
</div>
|
|
218
|
-
<div
|
|
219
|
-
v-if="cmt.reply.length > 0"
|
|
220
|
-
v-show="
|
|
127
|
+
<div v-if="cmt.reply.length > 0" v-show="
|
|
221
128
|
openedComments.includes(cmt.id) || cmt.reply.some((r) => pinnedReplyIds.has(r.id))
|
|
222
|
-
"
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<div
|
|
226
|
-
v-for="reply in cmt.reply"
|
|
227
|
-
v-show="isReplyVisible(cmt, reply)"
|
|
228
|
-
:key="reply.id"
|
|
229
|
-
class="flex flex-col gap-[16px]"
|
|
230
|
-
>
|
|
129
|
+
" class="pl-[38px] flex flex-col gap-[16px]">
|
|
130
|
+
<div v-for="reply in cmt.reply" v-show="isReplyVisible(cmt, reply)" :key="reply.id"
|
|
131
|
+
class="flex flex-col gap-[16px]">
|
|
231
132
|
<div class="flex flex-col gap-[4px]">
|
|
232
|
-
<div
|
|
233
|
-
class="
|
|
234
|
-
|
|
235
|
-
@mouseleave="hoveredId = null"
|
|
236
|
-
>
|
|
237
|
-
<Avatar
|
|
238
|
-
:size="30"
|
|
239
|
-
:src="reply.user.avatar"
|
|
240
|
-
class="cursor-pointer"
|
|
241
|
-
@click="onViewProfileComment(reply.user.id)"
|
|
242
|
-
/>
|
|
133
|
+
<div class="flex gap-[8px]" @mouseenter="hoveredId = reply.id" @mouseleave="hoveredId = null">
|
|
134
|
+
<Avatar :size="30" :src="reply.user.avatar" class="cursor-pointer"
|
|
135
|
+
@click="onViewProfileComment(reply.user.id)" />
|
|
243
136
|
<template v-if="editingId === reply.id">
|
|
244
137
|
<div class="flex-1 flex flex-col gap-[4px]">
|
|
245
|
-
<div
|
|
246
|
-
:ref="(el) => setEditInputRef(el, reply.id)"
|
|
247
|
-
contenteditable="true"
|
|
138
|
+
<div :ref="(el) => setEditInputRef(el, reply.id)" contenteditable="true"
|
|
248
139
|
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"
|
|
249
|
-
@input="onEditInput($event, reply.id)"
|
|
250
|
-
@keydown.enter.
|
|
251
|
-
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
252
|
-
/>
|
|
140
|
+
@input="onEditInput($event, reply.id)" @keydown.enter.exact.prevent="onSendEdit(reply, 'reply')"
|
|
141
|
+
@keydown.shift.enter.prevent="onInsertLineBreak" />
|
|
253
142
|
<div class="flex justify-end gap-[8px]">
|
|
254
|
-
<Button
|
|
255
|
-
variant="outline"
|
|
256
|
-
class="w-[56px]"
|
|
257
|
-
@click="onCancelEdit"
|
|
258
|
-
>
|
|
143
|
+
<Button variant="outline" class="w-[56px]" @click="onCancelEdit">
|
|
259
144
|
ยกเลิก
|
|
260
145
|
</Button>
|
|
261
|
-
<Button
|
|
262
|
-
|
|
263
|
-
class="w-[56px]"
|
|
264
|
-
:disabled="!editTextMap[reply.id]"
|
|
265
|
-
@click="onSendEdit(reply, 'reply')"
|
|
266
|
-
>
|
|
146
|
+
<Button color="primary" class="w-[56px]" :disabled="!editTextMap[reply.id]"
|
|
147
|
+
@click="onSendEdit(reply, 'reply')">
|
|
267
148
|
ส่ง
|
|
268
149
|
</Button>
|
|
269
150
|
</div>
|
|
270
151
|
</div>
|
|
271
152
|
</template>
|
|
272
153
|
<template v-else>
|
|
273
|
-
<div
|
|
274
|
-
class="
|
|
275
|
-
>
|
|
276
|
-
<div
|
|
277
|
-
class="font-body-large-prominent cursor-pointer"
|
|
278
|
-
@click="onViewProfileComment(reply.user.id)"
|
|
279
|
-
>
|
|
154
|
+
<div class="flex flex-col bg-bright p-[8px] rounded-lg gap-[4px]">
|
|
155
|
+
<div class="font-body-large-prominent cursor-pointer" @click="onViewProfileComment(reply.user.id)">
|
|
280
156
|
{{ reply.user.name }}
|
|
281
157
|
</div>
|
|
282
|
-
<div
|
|
283
|
-
:ref="
|
|
158
|
+
<div :ref="
|
|
284
159
|
(el) => registerContentRef(
|
|
285
160
|
el,
|
|
286
161
|
`reply-${reply.id}`
|
|
287
162
|
)
|
|
288
|
-
"
|
|
289
|
-
|
|
290
|
-
>
|
|
291
|
-
<template
|
|
292
|
-
v-if="
|
|
163
|
+
" class="font-body-large whitespace-pre-wrap">
|
|
164
|
+
<template v-if="
|
|
293
165
|
isContentExpanded(`reply-${reply.id}`) || !isContentOverflowing(`reply-${reply.id}`)
|
|
294
|
-
"
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
class="text-primary cursor-pointer mr-1"
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
>
|
|
302
|
-
<template v-else
|
|
303
|
-
><span
|
|
304
|
-
v-if="reply.reply_to"
|
|
305
|
-
@click="onViewProfileComment(reply.reply_to.id)"
|
|
306
|
-
class="text-primary cursor-pointer mr-1"
|
|
307
|
-
>{{ reply.reply_to.name }}</span
|
|
308
|
-
>{{ getTruncatedText(`reply-${reply.id}`) }}...<span
|
|
309
|
-
class="text-primary cursor-pointer"
|
|
310
|
-
@click="toggleContentExpand(`reply-${reply.id}`)"
|
|
311
|
-
>ดูเพิ่มเติม</span
|
|
312
|
-
></template
|
|
313
|
-
>
|
|
166
|
+
"><span v-if="reply.reply_to" @click="onViewProfileComment(reply.reply_to.id)"
|
|
167
|
+
class="text-primary cursor-pointer mr-1">{{ reply.reply_to.name }}</span>{{
|
|
168
|
+
normalizeContent(reply.content) }}</template>
|
|
169
|
+
<template v-else><span v-if="reply.reply_to" @click="onViewProfileComment(reply.reply_to.id)"
|
|
170
|
+
class="text-primary cursor-pointer mr-1">{{ reply.reply_to.name }}</span>{{
|
|
171
|
+
getTruncatedText(`reply-${reply.id}`) }}...<span class="text-primary cursor-pointer"
|
|
172
|
+
@click="toggleContentExpand(`reply-${reply.id}`)">ดูเพิ่มเติม</span></template>
|
|
314
173
|
</div>
|
|
315
174
|
</div>
|
|
316
|
-
<div
|
|
317
|
-
class="self-center transition-opacity"
|
|
318
|
-
:class="
|
|
175
|
+
<div class="self-center transition-opacity" :class="
|
|
319
176
|
isMenuVisible(reply.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
320
|
-
"
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
horizontal
|
|
324
|
-
variant="ghost"
|
|
325
|
-
circle
|
|
326
|
-
size="icon-xs"
|
|
327
|
-
icon-size="20"
|
|
328
|
-
:items="getReplyMenuItems(reply)"
|
|
329
|
-
:open="openMenuId === reply.id"
|
|
330
|
-
@update:open="
|
|
177
|
+
">
|
|
178
|
+
<PickerOptionMenu horizontal variant="ghost" circle size="icon-xs" icon-size="20"
|
|
179
|
+
:items="getReplyMenuItems(reply)" :open="openMenuId === reply.id" @update:open="
|
|
331
180
|
(v) => openMenuId = v ? reply.id : null
|
|
332
|
-
"
|
|
333
|
-
/>
|
|
181
|
+
" />
|
|
334
182
|
</div>
|
|
335
183
|
</template>
|
|
336
184
|
</div>
|
|
@@ -338,64 +186,36 @@
|
|
|
338
186
|
<div class="font-body-large">
|
|
339
187
|
{{ convertDateTorelativeText(reply.created_at) }}
|
|
340
188
|
</div>
|
|
341
|
-
<div
|
|
342
|
-
|
|
343
|
-
@click="onToggleLikeReplyComment(reply)"
|
|
344
|
-
>
|
|
345
|
-
<Icon
|
|
346
|
-
:name="
|
|
189
|
+
<div class="flex gap-[4px] items-center cursor-pointer" @click="onToggleLikeReplyComment(reply)">
|
|
190
|
+
<Icon :name="
|
|
347
191
|
reply.is_liked ? 'pukaad:thumbs-up-solid' : 'pukaad:thumbs-up-regular'
|
|
348
|
-
"
|
|
349
|
-
|
|
350
|
-
size="20"
|
|
351
|
-
/>
|
|
352
|
-
<div
|
|
353
|
-
v-if="reply.like_count > 0"
|
|
354
|
-
:class="[reply.is_liked && 'text-primary']"
|
|
355
|
-
>
|
|
192
|
+
" :class="[reply.is_liked && 'text-primary']" size="20" />
|
|
193
|
+
<div v-if="reply.like_count > 0" :class="[reply.is_liked && 'text-primary']">
|
|
356
194
|
{{ $convert.convertNumber(reply.like_count) }}
|
|
357
195
|
</div>
|
|
358
196
|
</div>
|
|
359
|
-
<div
|
|
360
|
-
class="font-body-large cursor-pointer"
|
|
361
|
-
@click="onReplyComment(reply.id, reply.user)"
|
|
362
|
-
>
|
|
197
|
+
<div class="font-body-large cursor-pointer" @click="onReplyComment(reply.id, reply.user)">
|
|
363
198
|
ตอบกลับ
|
|
364
199
|
</div>
|
|
365
200
|
</div>
|
|
366
201
|
</div>
|
|
367
|
-
<div
|
|
368
|
-
v-if="replyingToId === reply.id"
|
|
369
|
-
class="flex flex-col gap-[16px]"
|
|
370
|
-
>
|
|
202
|
+
<div v-if="replyingToId === reply.id" class="flex flex-col gap-[16px]">
|
|
371
203
|
<div class="flex gap-[8px] w-full">
|
|
372
204
|
<Avatar :size="30" :src="currentUser.avatar" />
|
|
373
205
|
<div class="flex-1">
|
|
374
|
-
<div
|
|
375
|
-
:ref="(el) => setReplyInputRef(el, reply.id)"
|
|
376
|
-
contenteditable="true"
|
|
206
|
+
<div :ref="(el) => setReplyInputRef(el, reply.id)" contenteditable="true"
|
|
377
207
|
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"
|
|
378
|
-
data-placeholder="เขียนการตอบกลับ..."
|
|
379
|
-
@input="onReplyInput($event, reply.id)"
|
|
208
|
+
data-placeholder="เขียนการตอบกลับ..." @input="onReplyInput($event, reply.id)"
|
|
380
209
|
@keydown.enter.exact.prevent="onSendReplyComment(cmt)"
|
|
381
|
-
@keydown.shift.enter.prevent="onInsertLineBreak"
|
|
382
|
-
/>
|
|
210
|
+
@keydown.shift.enter.prevent="onInsertLineBreak" />
|
|
383
211
|
</div>
|
|
384
212
|
</div>
|
|
385
213
|
<div class="flex justify-end gap-[8px]">
|
|
386
|
-
<Button
|
|
387
|
-
variant="outline"
|
|
388
|
-
class="w-[56px]"
|
|
389
|
-
@click="onCancelReplyComment"
|
|
390
|
-
>
|
|
214
|
+
<Button variant="outline" class="w-[56px]" @click="onCancelReplyComment">
|
|
391
215
|
ยกเลิก
|
|
392
216
|
</Button>
|
|
393
|
-
<Button
|
|
394
|
-
|
|
395
|
-
class="w-[68px]"
|
|
396
|
-
:disabled="!replyTextMap[reply.id]"
|
|
397
|
-
@click="onSendReplyComment(cmt)"
|
|
398
|
-
>
|
|
217
|
+
<Button color="primary" class="w-[68px]" :disabled="!replyTextMap[reply.id]"
|
|
218
|
+
@click="onSendReplyComment(cmt)">
|
|
399
219
|
ตอบกลับ
|
|
400
220
|
</Button>
|
|
401
221
|
</div>
|
|
@@ -404,10 +224,7 @@
|
|
|
404
224
|
</div>
|
|
405
225
|
</div>
|
|
406
226
|
<!-- Loading indicator for pagination -->
|
|
407
|
-
<div
|
|
408
|
-
v-if="fetchFn && paginationState.loading"
|
|
409
|
-
class="flex justify-center py-4"
|
|
410
|
-
>
|
|
227
|
+
<div v-if="fetchFn && paginationState.loading" class="flex justify-center py-4">
|
|
411
228
|
<Icon name="lucide:loader-2" class="animate-spin h-5 w-5 text-primary" />
|
|
412
229
|
</div>
|
|
413
230
|
</div>
|
|
@@ -459,29 +276,32 @@ const replyToUser = ref(null);
|
|
|
459
276
|
const replyInputRefs = ref({});
|
|
460
277
|
const replyTextMap = ref({});
|
|
461
278
|
const pinnedReplyIds = ref(/* @__PURE__ */ new Set());
|
|
462
|
-
|
|
279
|
+
const knownRepliesPerComment = /* @__PURE__ */ new WeakMap();
|
|
463
280
|
watch(
|
|
464
|
-
() => resolvedComments.value.
|
|
465
|
-
(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
281
|
+
() => resolvedComments.value.map((c) => ({ comment: c, replyIds: c.reply.map((r) => r.id) })),
|
|
282
|
+
(currentPairs) => {
|
|
283
|
+
currentPairs.forEach(({ comment, replyIds }) => {
|
|
284
|
+
const known = knownRepliesPerComment.get(comment);
|
|
285
|
+
if (!known) {
|
|
286
|
+
knownRepliesPerComment.set(comment, new Set(replyIds));
|
|
287
|
+
} else {
|
|
288
|
+
replyIds.forEach((id) => {
|
|
289
|
+
if (!known.has(id)) {
|
|
290
|
+
known.add(id);
|
|
291
|
+
pinnedReplyIds.value.add(id);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
const live = new Set(replyIds);
|
|
295
|
+
known.forEach((id) => {
|
|
296
|
+
if (!live.has(id)) {
|
|
297
|
+
known.delete(id);
|
|
298
|
+
pinnedReplyIds.value.delete(id);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
481
301
|
}
|
|
482
302
|
});
|
|
483
303
|
},
|
|
484
|
-
{ immediate: true }
|
|
304
|
+
{ immediate: true, deep: false }
|
|
485
305
|
);
|
|
486
306
|
const getHiddenCount = (cmt) => cmt.reply.filter((r) => !pinnedReplyIds.value.has(r.id)).length;
|
|
487
307
|
const isReplyVisible = (cmt, reply) => openedComments.value.includes(cmt.id) || pinnedReplyIds.value.has(reply.id);
|
|
@@ -60,9 +60,9 @@ const isLoading = ref(false);
|
|
|
60
60
|
const isOpen = defineModel({ type: Boolean, ...{
|
|
61
61
|
default: false
|
|
62
62
|
} });
|
|
63
|
+
const isEditMode = computed(() => !!props.item?.id);
|
|
63
64
|
const drawerTitle = computed(() => {
|
|
64
|
-
|
|
65
|
-
return hasTitle ? "\u0E41\u0E01\u0E49\u0E44\u0E02\u0E1A\u0E25\u0E47\u0E2D\u0E01" : "\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E1A\u0E25\u0E47\u0E2D\u0E01";
|
|
65
|
+
return isEditMode.value ? "\u0E41\u0E01\u0E49\u0E44\u0E02\u0E1A\u0E25\u0E47\u0E2D\u0E01" : "\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E1A\u0E25\u0E47\u0E2D\u0E01";
|
|
66
66
|
});
|
|
67
67
|
watch(isOpen, (newVal) => {
|
|
68
68
|
if (newVal) {
|
|
@@ -143,13 +143,11 @@ const onSubmit = async () => {
|
|
|
143
143
|
let coverImageUrl = "";
|
|
144
144
|
if (form.value.coverImage && form.value.coverImage.length > 0) {
|
|
145
145
|
const fileItem = form.value.coverImage[0];
|
|
146
|
-
if (fileItem
|
|
147
|
-
console.log("File found:", fileItem.file);
|
|
148
|
-
console.log("File name:", fileItem.file.name);
|
|
146
|
+
if (fileItem?.file) {
|
|
149
147
|
coverImageUrl = await uploadImage(fileItem.file);
|
|
148
|
+
} else if (fileItem?.url) {
|
|
149
|
+
coverImageUrl = fileItem.url;
|
|
150
150
|
}
|
|
151
|
-
} else {
|
|
152
|
-
console.log("No cover image selected");
|
|
153
151
|
}
|
|
154
152
|
const payload = {
|
|
155
153
|
title: form.value.title,
|
|
@@ -157,27 +155,24 @@ const onSubmit = async () => {
|
|
|
157
155
|
tags: form.value.tags.map((t) => t.name || t),
|
|
158
156
|
cover_image_url: coverImageUrl
|
|
159
157
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
const res = await api(
|
|
159
|
+
isEditMode.value ? `/blogs/${form.value.id}` : "/profiles/me/blog",
|
|
160
|
+
{
|
|
161
|
+
method: isEditMode.value ? "PUT" : "POST",
|
|
162
|
+
body: payload
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
const isSuccess = isEditMode.value ? res.code === "SUCCESS_OK" : res.code === "SUCCESS_CREATED";
|
|
166
|
+
if (isSuccess) {
|
|
167
|
+
$toast.success(isEditMode.value ? "\u0E2D\u0E31\u0E1B\u0E40\u0E14\u0E15\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08" : "\u0E2A\u0E23\u0E49\u0E32\u0E07\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08");
|
|
167
168
|
emit("submit", form.value);
|
|
168
|
-
emit("success", res.data
|
|
169
|
-
id: `temp-${Date.now()}`,
|
|
170
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
171
|
-
title: form.value.title,
|
|
172
|
-
content: form.value.content,
|
|
173
|
-
cover_image_url: coverImageUrl
|
|
174
|
-
});
|
|
169
|
+
emit("success", res.data);
|
|
175
170
|
isOpen.value = false;
|
|
176
171
|
} else {
|
|
177
|
-
throw new Error(res.message || "
|
|
172
|
+
throw new Error(res.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14");
|
|
178
173
|
}
|
|
179
174
|
} catch (error) {
|
|
180
|
-
$toast.error(error.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E43\u0E19\u0E01\u0E32\u0E23\
|
|
175
|
+
$toast.error(error.message || "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14\u0E43\u0E19\u0E01\u0E32\u0E23\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21");
|
|
181
176
|
console.error("Submit error:", error);
|
|
182
177
|
} finally {
|
|
183
178
|
isLoading.value = false;
|