pukaad-ui-lib 1.454.0 → 1.458.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/card/card-blog.d.vue.ts +1 -1
- package/dist/runtime/components/card/card-blog.vue.d.ts +1 -1
- package/dist/runtime/components/card/card-profile/card-profile-header.d.vue.ts +1 -1
- package/dist/runtime/components/card/card-profile/card-profile-header.vue.d.ts +1 -1
- package/dist/runtime/components/card/card-reaction.d.vue.ts +1 -1
- package/dist/runtime/components/card/card-reaction.vue.d.ts +1 -1
- package/dist/runtime/components/chat/chat-conversation-item.d.vue.ts +1 -1
- package/dist/runtime/components/chat/chat-conversation-item.vue.d.ts +1 -1
- package/dist/runtime/components/comment.d.vue.ts +3 -0
- package/dist/runtime/components/comment.vue +8 -6
- package/dist/runtime/components/comment.vue.d.ts +3 -0
- package/dist/runtime/components/display/display-image.vue +6 -1
- package/dist/runtime/components/display/display-video.d.vue.ts +1 -1
- package/dist/runtime/components/display/display-video.vue.d.ts +1 -1
- package/dist/runtime/components/drawer/drawer-post-content.vue +18 -0
- package/dist/runtime/components/input/input-comment.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-comment.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-rating.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-rating.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-search.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-search.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-select-place.vue +10 -3
- package/dist/runtime/components/input/input-text-field.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-text-field.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/preview/preview-content.d.vue.ts +8 -6
- package/dist/runtime/components/preview/preview-content.vue +209 -112
- package/dist/runtime/components/preview/preview-content.vue.d.ts +8 -6
- package/dist/runtime/components/preview/preview-place.d.vue.ts +0 -10
- package/dist/runtime/components/preview/preview-place.vue +7 -7
- package/dist/runtime/components/preview/preview-place.vue.d.ts +0 -10
- package/dist/runtime/components/ui/chart/ChartTooltipContent.d.vue.ts +1 -1
- package/dist/runtime/components/ui/chart/ChartTooltipContent.vue.d.ts +1 -1
- package/dist/runtime/components/ui/native-select/NativeSelectOptGroup.d.vue.ts +2 -2
- package/dist/runtime/components/ui/native-select/NativeSelectOptGroup.vue.d.ts +2 -2
- package/dist/runtime/components/ui/native-select/NativeSelectOption.d.vue.ts +2 -2
- package/dist/runtime/components/ui/native-select/NativeSelectOption.vue.d.ts +2 -2
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -38,8 +38,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
38
38
|
"onBlog-updated"?: ((id: string, data: any) => any) | undefined;
|
|
39
39
|
"onBlog-deleted"?: ((id: string) => any) | undefined;
|
|
40
40
|
}>, {
|
|
41
|
-
disabledMenu: boolean;
|
|
42
41
|
isMyProfile: boolean;
|
|
42
|
+
disabledMenu: boolean;
|
|
43
43
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
44
44
|
declare const _default: typeof __VLS_export;
|
|
45
45
|
export default _default;
|
|
@@ -38,8 +38,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
38
38
|
"onBlog-updated"?: ((id: string, data: any) => any) | undefined;
|
|
39
39
|
"onBlog-deleted"?: ((id: string) => any) | undefined;
|
|
40
40
|
}>, {
|
|
41
|
-
disabledMenu: boolean;
|
|
42
41
|
isMyProfile: boolean;
|
|
42
|
+
disabledMenu: boolean;
|
|
43
43
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
44
44
|
declare const _default: typeof __VLS_export;
|
|
45
45
|
export default _default;
|
|
@@ -7,8 +7,8 @@ declare const __VLS_export: import("vue").DefineComponent<CardProfileAboutProps,
|
|
|
7
7
|
onFollow?: ((isFollowing: boolean) => any) | undefined;
|
|
8
8
|
}>, {
|
|
9
9
|
type: "article" | "media" | "sale-info";
|
|
10
|
-
preview: boolean;
|
|
11
10
|
disabledPadding: boolean;
|
|
11
|
+
preview: boolean;
|
|
12
12
|
optionMenuButtonType: "solid" | "text";
|
|
13
13
|
actionsHidden: boolean;
|
|
14
14
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -7,8 +7,8 @@ declare const __VLS_export: import("vue").DefineComponent<CardProfileAboutProps,
|
|
|
7
7
|
onFollow?: ((isFollowing: boolean) => any) | undefined;
|
|
8
8
|
}>, {
|
|
9
9
|
type: "article" | "media" | "sale-info";
|
|
10
|
-
preview: boolean;
|
|
11
10
|
disabledPadding: boolean;
|
|
11
|
+
preview: boolean;
|
|
12
12
|
optionMenuButtonType: "solid" | "text";
|
|
13
13
|
actionsHidden: boolean;
|
|
14
14
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -25,7 +25,6 @@ type __VLS_Props = {
|
|
|
25
25
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
26
26
|
disabled: boolean;
|
|
27
27
|
id: string;
|
|
28
|
-
state: string;
|
|
29
28
|
itemCount: Count;
|
|
30
29
|
itemActive: Reacted;
|
|
31
30
|
padding: string | number;
|
|
@@ -33,6 +32,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
33
32
|
disabledDividerTop: boolean;
|
|
34
33
|
disabledDividerBottom: boolean;
|
|
35
34
|
iconSize: number | string;
|
|
35
|
+
state: string;
|
|
36
36
|
loginPath: string;
|
|
37
37
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
38
|
declare const _default: typeof __VLS_export;
|
|
@@ -25,7 +25,6 @@ type __VLS_Props = {
|
|
|
25
25
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
26
26
|
disabled: boolean;
|
|
27
27
|
id: string;
|
|
28
|
-
state: string;
|
|
29
28
|
itemCount: Count;
|
|
30
29
|
itemActive: Reacted;
|
|
31
30
|
padding: string | number;
|
|
@@ -33,6 +32,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
33
32
|
disabledDividerTop: boolean;
|
|
34
33
|
disabledDividerBottom: boolean;
|
|
35
34
|
iconSize: number | string;
|
|
35
|
+
state: string;
|
|
36
36
|
loginPath: string;
|
|
37
37
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
38
|
declare const _default: typeof __VLS_export;
|
|
@@ -26,13 +26,13 @@ declare const __VLS_export: import("vue").DefineComponent<ChatConversationItemPr
|
|
|
26
26
|
name: string;
|
|
27
27
|
avatar: string | null;
|
|
28
28
|
muted: boolean;
|
|
29
|
+
disabledMenu: boolean;
|
|
29
30
|
preview: string;
|
|
30
31
|
to: string;
|
|
31
32
|
active: boolean;
|
|
32
33
|
typing: boolean;
|
|
33
34
|
unreadCount: number;
|
|
34
35
|
pinned: boolean;
|
|
35
|
-
disabledMenu: boolean;
|
|
36
36
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
37
37
|
declare const _default: typeof __VLS_export;
|
|
38
38
|
export default _default;
|
|
@@ -26,13 +26,13 @@ declare const __VLS_export: import("vue").DefineComponent<ChatConversationItemPr
|
|
|
26
26
|
name: string;
|
|
27
27
|
avatar: string | null;
|
|
28
28
|
muted: boolean;
|
|
29
|
+
disabledMenu: boolean;
|
|
29
30
|
preview: string;
|
|
30
31
|
to: string;
|
|
31
32
|
active: boolean;
|
|
32
33
|
typing: boolean;
|
|
33
34
|
unreadCount: number;
|
|
34
35
|
pinned: boolean;
|
|
35
|
-
disabledMenu: boolean;
|
|
36
36
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
37
37
|
declare const _default: typeof __VLS_export;
|
|
38
38
|
export default _default;
|
|
@@ -35,6 +35,8 @@ type __VLS_Props = {
|
|
|
35
35
|
currentUser: User;
|
|
36
36
|
fetchFn?: (page: number, pageSize: number) => Promise<CommentFetchResult>;
|
|
37
37
|
pageSize?: number;
|
|
38
|
+
/** ดูอย่างเดียว (เช่นหน้าพรีวิว) — ไม่มีช่องพิมพ์/เมนู กดถูกใจ/ตอบกลับ/ดูโปรไฟล์ไม่ได้ แต่ยังกดดูการตอบกลับ/ดูเพิ่มเติมได้ */
|
|
39
|
+
readonly?: boolean;
|
|
38
40
|
};
|
|
39
41
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
40
42
|
addComment: (comment: CommentItem) => void;
|
|
@@ -67,6 +69,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
67
69
|
comments: CommentItem[];
|
|
68
70
|
commentCount: number;
|
|
69
71
|
pageSize: number;
|
|
72
|
+
readonly: boolean;
|
|
70
73
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
71
74
|
declare const _default: typeof __VLS_export;
|
|
72
75
|
export default _default;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<!-- กล่องคอมเมนต์หลัก — ปุ่มโผล่ตอนคลิกเข้าช่องพิมพ์ (actions-on-focus) -->
|
|
11
|
-
<InputComment ref="mainInputRef" v-model="mainCommentInput" :avatar="currentUser.avatar"
|
|
11
|
+
<InputComment v-if="!props.readonly" ref="mainInputRef" v-model="mainCommentInput" :avatar="currentUser.avatar"
|
|
12
12
|
placeholder="เพิ่มความคิดเห็น" actions-on-focus @submit="onSendComment" />
|
|
13
13
|
<div v-for="cmt in resolvedComments" :key="cmt.id" class="flex flex-col gap-[16px] w-full">
|
|
14
14
|
<div class="flex flex-col gap-[4px] w-full">
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
</template>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
43
|
-
<div class="self-center transition-opacity" :class="
|
|
43
|
+
<div v-if="!props.readonly" class="self-center transition-opacity" :class="
|
|
44
44
|
isMenuVisible(cmt.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
45
45
|
">
|
|
46
46
|
<PickerOptionMenu horizontal variant="ghost" circle size="icon-sm" icon-size="20"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
</template>
|
|
51
51
|
</div>
|
|
52
|
-
<div class="pl-[38px] flex gap-[16px] text-gray">
|
|
52
|
+
<div class="pl-[38px] flex gap-[16px] text-gray" :inert="props.readonly || void 0">
|
|
53
53
|
<div class="font-body-large">
|
|
54
54
|
{{ convertDateToRelativeText(cmt.created_at) }}
|
|
55
55
|
</div>
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
@click="toggleContentExpand(`reply-${reply.id}`)">ดูเพิ่มเติม</span></template>
|
|
120
120
|
</div>
|
|
121
121
|
</div>
|
|
122
|
-
<div class="self-center transition-opacity" :class="
|
|
122
|
+
<div v-if="!props.readonly" class="self-center transition-opacity" :class="
|
|
123
123
|
isMenuVisible(reply.id) ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
124
124
|
">
|
|
125
125
|
<PickerOptionMenu horizontal variant="ghost" circle size="icon-xs" icon-size="20"
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
</div>
|
|
130
130
|
</template>
|
|
131
131
|
</div>
|
|
132
|
-
<div class="pl-[38px] flex gap-[16px] text-gray">
|
|
132
|
+
<div class="pl-[38px] flex gap-[16px] text-gray" :inert="props.readonly || void 0">
|
|
133
133
|
<div class="font-body-large">
|
|
134
134
|
{{ convertDateToRelativeText(reply.created_at) }}
|
|
135
135
|
</div>
|
|
@@ -177,7 +177,8 @@ const props = defineProps({
|
|
|
177
177
|
commentCount: { type: Number, required: false, default: 0 },
|
|
178
178
|
currentUser: { type: Object, required: true },
|
|
179
179
|
fetchFn: { type: Function, required: false },
|
|
180
|
-
pageSize: { type: Number, required: false, default: 20 }
|
|
180
|
+
pageSize: { type: Number, required: false, default: 20 },
|
|
181
|
+
readonly: { type: Boolean, required: false, default: false }
|
|
181
182
|
});
|
|
182
183
|
const emit = defineEmits(["submit", "reply", "like-comment", "like-reply", "edit-comment", "edit-reply", "delete-comment", "delete-reply", "report-comment", "report-reply"]);
|
|
183
184
|
const internalComments = ref([]);
|
|
@@ -343,6 +344,7 @@ const onToggleLikeReplyComment = (reply) => {
|
|
|
343
344
|
emit("like-reply", reply);
|
|
344
345
|
};
|
|
345
346
|
const onViewProfileComment = (id) => {
|
|
347
|
+
if (props.readonly) return;
|
|
346
348
|
if (id) router.push(`/@${id}`);
|
|
347
349
|
};
|
|
348
350
|
const onEditClick = (id, content, replyTo) => {
|
|
@@ -35,6 +35,8 @@ type __VLS_Props = {
|
|
|
35
35
|
currentUser: User;
|
|
36
36
|
fetchFn?: (page: number, pageSize: number) => Promise<CommentFetchResult>;
|
|
37
37
|
pageSize?: number;
|
|
38
|
+
/** ดูอย่างเดียว (เช่นหน้าพรีวิว) — ไม่มีช่องพิมพ์/เมนู กดถูกใจ/ตอบกลับ/ดูโปรไฟล์ไม่ได้ แต่ยังกดดูการตอบกลับ/ดูเพิ่มเติมได้ */
|
|
39
|
+
readonly?: boolean;
|
|
38
40
|
};
|
|
39
41
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
40
42
|
addComment: (comment: CommentItem) => void;
|
|
@@ -67,6 +69,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
67
69
|
comments: CommentItem[];
|
|
68
70
|
commentCount: number;
|
|
69
71
|
pageSize: number;
|
|
72
|
+
readonly: boolean;
|
|
70
73
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
71
74
|
declare const _default: typeof __VLS_export;
|
|
72
75
|
export default _default;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<Image
|
|
20
20
|
:src="getSrc((props.items ?? [])[0])"
|
|
21
21
|
class="rounded-[8px]"
|
|
22
|
-
width="
|
|
22
|
+
:width="widthMainImg"
|
|
23
23
|
height="auto"
|
|
24
24
|
/>
|
|
25
25
|
</div>
|
|
@@ -66,6 +66,11 @@ const heightBox = computed(() => {
|
|
|
66
66
|
if (length > 1 && length <= 4) return 422;
|
|
67
67
|
return 480;
|
|
68
68
|
});
|
|
69
|
+
const widthMainImg = computed(() => {
|
|
70
|
+
const length = props.items?.length ?? 0;
|
|
71
|
+
if (length === 3 || length === 4) return 633;
|
|
72
|
+
return 720;
|
|
73
|
+
});
|
|
69
74
|
const widthSubImg = computed(() => {
|
|
70
75
|
const length = props.items?.length ?? 0;
|
|
71
76
|
if (length === 0) return 0;
|
|
@@ -102,9 +102,9 @@ declare const __VLS_base: import("vue").DefineComponent<DisplayVideoProps, {
|
|
|
102
102
|
loop: boolean;
|
|
103
103
|
controls: boolean;
|
|
104
104
|
preload: "none" | "metadata" | "auto";
|
|
105
|
-
playsinline: boolean;
|
|
106
105
|
sources: DisplayVideoSource[];
|
|
107
106
|
tracks: DisplayVideoTrack[];
|
|
107
|
+
playsinline: boolean;
|
|
108
108
|
disablePictureInPicture: boolean;
|
|
109
109
|
disableRemotePlayback: boolean;
|
|
110
110
|
volume: number;
|
|
@@ -102,9 +102,9 @@ declare const __VLS_base: import("vue").DefineComponent<DisplayVideoProps, {
|
|
|
102
102
|
loop: boolean;
|
|
103
103
|
controls: boolean;
|
|
104
104
|
preload: "none" | "metadata" | "auto";
|
|
105
|
-
playsinline: boolean;
|
|
106
105
|
sources: DisplayVideoSource[];
|
|
107
106
|
tracks: DisplayVideoTrack[];
|
|
107
|
+
playsinline: boolean;
|
|
108
108
|
disablePictureInPicture: boolean;
|
|
109
109
|
disableRemotePlayback: boolean;
|
|
110
110
|
volume: number;
|
|
@@ -74,6 +74,9 @@
|
|
|
74
74
|
</template>
|
|
75
75
|
</InputFile>
|
|
76
76
|
</div>
|
|
77
|
+
<div v-if="needsManualPoster" class="font-body-small text-destructive">
|
|
78
|
+
ไม่สามารถสร้างหน้าปกจากวิดีโอได้ กรุณาอัปโหลดภาพหน้าปก
|
|
79
|
+
</div>
|
|
77
80
|
<div
|
|
78
81
|
v-show="hasCoverText && !hasCoverFile"
|
|
79
82
|
class="flex gap-[8px] py-[4px] px-[12px]"
|
|
@@ -190,6 +193,7 @@ const coverText = ref("");
|
|
|
190
193
|
const coverFiles = ref([]);
|
|
191
194
|
const posterFiles = ref([]);
|
|
192
195
|
const generatedPoster = ref(null);
|
|
196
|
+
const posterGenerationFailed = ref(false);
|
|
193
197
|
const description = ref("");
|
|
194
198
|
const fileColumns = ref(false);
|
|
195
199
|
const selectedPlaces = ref([]);
|
|
@@ -245,6 +249,7 @@ watch(
|
|
|
245
249
|
([videoFile, manualPoster]) => {
|
|
246
250
|
const generation = ++thumbnailGeneration;
|
|
247
251
|
clearGeneratedPoster();
|
|
252
|
+
posterGenerationFailed.value = false;
|
|
248
253
|
if (manualPoster || typeof File === "undefined" || !(videoFile instanceof File) || !videoFile.type.startsWith("video/")) {
|
|
249
254
|
thumbnailPromise = Promise.resolve();
|
|
250
255
|
return;
|
|
@@ -258,6 +263,9 @@ watch(
|
|
|
258
263
|
mediaType: "image"
|
|
259
264
|
};
|
|
260
265
|
}).catch(() => {
|
|
266
|
+
if (generation === thumbnailGeneration) {
|
|
267
|
+
posterGenerationFailed.value = true;
|
|
268
|
+
}
|
|
261
269
|
});
|
|
262
270
|
},
|
|
263
271
|
{ immediate: true }
|
|
@@ -269,6 +277,9 @@ onBeforeUnmount(() => {
|
|
|
269
277
|
const effectivePoster = computed(
|
|
270
278
|
() => posterFiles.value[0] ?? generatedPoster.value
|
|
271
279
|
);
|
|
280
|
+
const needsManualPoster = computed(
|
|
281
|
+
() => coverFileType.value === "video" && !posterFiles.value[0] && (posterGenerationFailed.value || typeof File === "undefined" || !(coverFiles.value[0]?.file instanceof File))
|
|
282
|
+
);
|
|
272
283
|
const previewItem = computed(() => {
|
|
273
284
|
if (hasCoverFile.value) {
|
|
274
285
|
const file = coverFiles.value[0];
|
|
@@ -449,6 +460,13 @@ const submitContent = async (status) => {
|
|
|
449
460
|
if (contentType === "video" && !posterFiles.value[0]) {
|
|
450
461
|
await thumbnailPromise;
|
|
451
462
|
}
|
|
463
|
+
if (contentType === "video" && status === "PUBLISHED" && !effectivePoster.value) {
|
|
464
|
+
posterGenerationFailed.value = true;
|
|
465
|
+
$toast?.error?.(
|
|
466
|
+
"\u0E01\u0E23\u0E38\u0E13\u0E32\u0E2D\u0E31\u0E1B\u0E42\u0E2B\u0E25\u0E14\u0E20\u0E32\u0E1E\u0E2B\u0E19\u0E49\u0E32\u0E1B\u0E01\u0E02\u0E2D\u0E07\u0E27\u0E34\u0E14\u0E35\u0E42\u0E2D"
|
|
467
|
+
);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
452
470
|
const [uploadedMedia, uploadedPoster] = await Promise.all([
|
|
453
471
|
uploadFiles(coverFiles.value, "content-media"),
|
|
454
472
|
contentType === "video" && effectivePoster.value ? uploadFiles([effectivePoster.value], "content-media") : Promise.resolve([])
|
|
@@ -46,8 +46,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
46
46
|
}>, {
|
|
47
47
|
loading: boolean;
|
|
48
48
|
disabled: boolean;
|
|
49
|
-
cancelText: string;
|
|
50
49
|
placeholder: string;
|
|
50
|
+
cancelText: string;
|
|
51
51
|
autofocus: boolean;
|
|
52
52
|
disabledAvatar: boolean;
|
|
53
53
|
submitText: string;
|
|
@@ -46,8 +46,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
46
46
|
}>, {
|
|
47
47
|
loading: boolean;
|
|
48
48
|
disabled: boolean;
|
|
49
|
-
cancelText: string;
|
|
50
49
|
placeholder: string;
|
|
50
|
+
cancelText: string;
|
|
51
51
|
autofocus: boolean;
|
|
52
52
|
disabledAvatar: boolean;
|
|
53
53
|
submitText: string;
|
|
@@ -10,10 +10,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
10
10
|
"onUpdate:modelValue"?: ((value: number) => any) | undefined;
|
|
11
11
|
}>, {
|
|
12
12
|
size: number;
|
|
13
|
+
readonly: boolean;
|
|
13
14
|
borderColor: string;
|
|
14
15
|
modelValue: number;
|
|
15
16
|
backgroundColor: string;
|
|
16
|
-
readonly: boolean;
|
|
17
17
|
ratingStep: number;
|
|
18
18
|
borderSize: number;
|
|
19
19
|
activeColor: string;
|
|
@@ -10,10 +10,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
10
10
|
"onUpdate:modelValue"?: ((value: number) => any) | undefined;
|
|
11
11
|
}>, {
|
|
12
12
|
size: number;
|
|
13
|
+
readonly: boolean;
|
|
13
14
|
borderColor: string;
|
|
14
15
|
modelValue: number;
|
|
15
16
|
backgroundColor: string;
|
|
16
|
-
readonly: boolean;
|
|
17
17
|
ratingStep: number;
|
|
18
18
|
borderSize: number;
|
|
19
19
|
activeColor: string;
|
|
@@ -33,11 +33,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
33
33
|
type: import("./input-text-field.vue.js").InputTextFieldType;
|
|
34
34
|
icon: string;
|
|
35
35
|
disabled: boolean;
|
|
36
|
+
readonly: boolean;
|
|
36
37
|
id: string;
|
|
37
38
|
name: string;
|
|
38
39
|
required: boolean;
|
|
39
40
|
placeholder: string;
|
|
40
|
-
readonly: boolean;
|
|
41
41
|
disabledErrorMessage: boolean;
|
|
42
42
|
iconPosition: import("#pukaad-ui/types/components/input/input-search").InputSearchIconPosition;
|
|
43
43
|
disabledIcon: boolean;
|
|
@@ -33,11 +33,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
33
33
|
type: import("./input-text-field.vue.js").InputTextFieldType;
|
|
34
34
|
icon: string;
|
|
35
35
|
disabled: boolean;
|
|
36
|
+
readonly: boolean;
|
|
36
37
|
id: string;
|
|
37
38
|
name: string;
|
|
38
39
|
required: boolean;
|
|
39
40
|
placeholder: string;
|
|
40
|
-
readonly: boolean;
|
|
41
41
|
disabledErrorMessage: boolean;
|
|
42
42
|
iconPosition: import("#pukaad-ui/types/components/input/input-search").InputSearchIconPosition;
|
|
43
43
|
disabledIcon: boolean;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col gap-[4px] w-full">
|
|
2
|
+
<div class="relative flex flex-col gap-[4px] w-full">
|
|
3
3
|
<div class="font-body-large-prominent text-gray">
|
|
4
4
|
{{ props.label }}
|
|
5
5
|
<span v-if="props.required" class="text-destructive">*</span>
|
|
6
6
|
</div>
|
|
7
|
-
|
|
7
|
+
<!-- input เป็น anchor ของ popover — ห้ามซ่อนด้วย display:none (v-show) ตอนมีการเลือก ไม่งั้น anchor ไม่มี
|
|
8
|
+
ตำแหน่ง popover ลอยไปมุมซ้ายบนจอ ใช้ invisible + วางทับขอบล่างการ์ดแทน ให้เปิดจากการ์ดแล้วโผล่ใต้การ์ดพอดี -->
|
|
9
|
+
<div
|
|
10
|
+
:class="
|
|
11
|
+
selectedItems.length > 0 ? 'invisible absolute inset-x-0 bottom-0' : ''
|
|
12
|
+
"
|
|
13
|
+
>
|
|
8
14
|
<InputCombobox
|
|
9
15
|
ref="comboboxRef"
|
|
10
16
|
name="input_select_place"
|
|
@@ -96,7 +102,7 @@
|
|
|
96
102
|
</InputCombobox>
|
|
97
103
|
</div>
|
|
98
104
|
|
|
99
|
-
<!-- แสดงแทน input ตอนมีการเลือกอยู่ (input จริงยังอยู่ใน DOM
|
|
105
|
+
<!-- แสดงแทน input ตอนมีการเลือกอยู่ (input จริงยังอยู่ใน DOM แค่ซ่อนแบบ invisible ไว้ ไม่ได้ unmount — กันเสีย
|
|
100
106
|
state/ref ของ InputCombobox) คลิกที่การ์ดเปิด popover กลับมาเพื่อเลือกเพิ่ม/แก้ไขได้ ผ่าน openPopover()
|
|
101
107
|
ที่ expose ไว้ (ไม่ต้องแตะ template ของ input-combobox.vue เลย ปลอดภัยกับ component กลาง) -->
|
|
102
108
|
<div
|
|
@@ -262,6 +268,7 @@ const toggleSelect = (id) => {
|
|
|
262
268
|
}
|
|
263
269
|
if (item) selectedCache.value.set(id, item);
|
|
264
270
|
selectedIds.value = [...selectedIds.value, id];
|
|
271
|
+
comboboxRef.value?.closePopover();
|
|
265
272
|
}
|
|
266
273
|
};
|
|
267
274
|
const selectedItems = computed(
|
|
@@ -56,11 +56,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
56
56
|
}>, {
|
|
57
57
|
type: InputTextFieldType;
|
|
58
58
|
disabled: boolean;
|
|
59
|
+
readonly: boolean;
|
|
59
60
|
id: string;
|
|
60
61
|
name: string;
|
|
61
62
|
required: boolean;
|
|
62
63
|
limit: number;
|
|
63
|
-
readonly: boolean;
|
|
64
64
|
disabledErrorMessage: boolean;
|
|
65
65
|
disabledBorder: boolean;
|
|
66
66
|
showCounter: boolean;
|
|
@@ -56,11 +56,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
56
56
|
}>, {
|
|
57
57
|
type: InputTextFieldType;
|
|
58
58
|
disabled: boolean;
|
|
59
|
+
readonly: boolean;
|
|
59
60
|
id: string;
|
|
60
61
|
name: string;
|
|
61
62
|
required: boolean;
|
|
62
63
|
limit: number;
|
|
63
|
-
readonly: boolean;
|
|
64
64
|
disabledErrorMessage: boolean;
|
|
65
65
|
disabledBorder: boolean;
|
|
66
66
|
showCounter: boolean;
|
|
@@ -40,6 +40,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
40
40
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
41
41
|
}>, {
|
|
42
42
|
disabled: boolean;
|
|
43
|
+
readonly: boolean;
|
|
43
44
|
id: string;
|
|
44
45
|
name: string;
|
|
45
46
|
required: boolean;
|
|
@@ -48,7 +49,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
48
49
|
fullHeight: boolean;
|
|
49
50
|
rows: number;
|
|
50
51
|
limit: number;
|
|
51
|
-
readonly: boolean;
|
|
52
52
|
disabledErrorMessage: boolean;
|
|
53
53
|
disabledBorder: boolean;
|
|
54
54
|
showCounter: boolean;
|
|
@@ -40,6 +40,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
40
40
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
41
41
|
}>, {
|
|
42
42
|
disabled: boolean;
|
|
43
|
+
readonly: boolean;
|
|
43
44
|
id: string;
|
|
44
45
|
name: string;
|
|
45
46
|
required: boolean;
|
|
@@ -48,7 +49,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
48
49
|
fullHeight: boolean;
|
|
49
50
|
rows: number;
|
|
50
51
|
limit: number;
|
|
51
|
-
readonly: boolean;
|
|
52
52
|
disabledErrorMessage: boolean;
|
|
53
53
|
disabledBorder: boolean;
|
|
54
54
|
showCounter: boolean;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { CardReferItem } from "#pukaad-ui/runtime/components/card/card-refer.vue";
|
|
2
|
-
export type PreviewContentState = "personal" | "business";
|
|
2
|
+
export type PreviewContentState = "personal" | "office" | "business";
|
|
3
|
+
export interface PreviewContentMedia {
|
|
4
|
+
id?: string | number;
|
|
5
|
+
media_type: string;
|
|
6
|
+
url: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
3
9
|
export interface PreviewContentData {
|
|
4
10
|
id?: string;
|
|
5
11
|
content_type?: "post_it" | "image" | "video";
|
|
@@ -11,11 +17,7 @@ export interface PreviewContentData {
|
|
|
11
17
|
cover_color?: string;
|
|
12
18
|
video_poster_url?: string;
|
|
13
19
|
description?: string;
|
|
14
|
-
media?:
|
|
15
|
-
media_type: string;
|
|
16
|
-
url: string;
|
|
17
|
-
description?: string;
|
|
18
|
-
}[];
|
|
20
|
+
media?: PreviewContentMedia[];
|
|
19
21
|
tags?: {
|
|
20
22
|
id: string;
|
|
21
23
|
name: string;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- Figma: Preview - Video (2743:66169) — post_it / image ใช้โครงเดียวกัน -->
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="h-screen w-full overflow-hidden bg-white flex flex-col">
|
|
4
4
|
<!-- Figma: Navbar Preview (new tab) — Property 1=Admin -->
|
|
5
|
-
|
|
6
|
-
<div class="sticky top-0 z-overlay w-full bg-cloud">
|
|
5
|
+
<div class="shrink-0 w-full bg-cloud">
|
|
7
6
|
<div class="max-w-[1280px] mx-auto flex justify-end p-[16px]">
|
|
8
7
|
<Button variant="outline" class="rounded-[6px]" @click="emit('exit')">
|
|
9
8
|
ออกจากพรีวิว
|
|
@@ -11,124 +10,142 @@
|
|
|
11
10
|
</div>
|
|
12
11
|
</div>
|
|
13
12
|
|
|
14
|
-
<!--
|
|
15
|
-
<div class="flex
|
|
16
|
-
<!--
|
|
17
|
-
<div class="
|
|
18
|
-
|
|
13
|
+
<!-- เลื่อนในกล่องนี้ ไม่พึ่ง scroll ของหน้า (บางแอปล็อก html/body ไว้) -->
|
|
14
|
+
<div class="flex-1 overflow-y-auto">
|
|
15
|
+
<!-- Figma: เนื้อหาเริ่มห่างจากแถบ 22px (ช่องว่าง 6 + padding 16) -->
|
|
16
|
+
<div class="flex justify-center px-[16px] pt-[22px] pb-[16px]">
|
|
17
|
+
<!-- กว้าง 844 เท่าวิดีโอ — DisplayVideo ยืดตามความกว้างกล่องนี้ ปุ่มเล่นจะได้อยู่กลางวิดีโอ -->
|
|
18
|
+
<div class="w-[844px] flex flex-col gap-[16px]">
|
|
19
|
+
<Label v-if="labelItems.length" :items="labelItems" />
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
<div
|
|
22
|
-
v-if="content.content_type === 'post_it'"
|
|
23
|
-
class="flex gap-[16px] py-[16px] items-center"
|
|
24
|
-
>
|
|
21
|
+
<!-- Post-it -->
|
|
25
22
|
<div
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
v-if="contentData.content_type === 'post_it'"
|
|
24
|
+
class="flex gap-[16px] py-[16px] items-center"
|
|
25
|
+
>
|
|
26
|
+
<div
|
|
27
|
+
class="w-[6px] h-full shrink-0"
|
|
28
|
+
:style="{ backgroundColor: contentData.cover_color }"
|
|
29
|
+
/>
|
|
30
|
+
<div class="font-headline-large-prominent min-w-0 break-words">
|
|
31
|
+
{{ contentData.cover_text }}
|
|
32
|
+
</div>
|
|
31
33
|
</div>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
<!-- Image -->
|
|
35
|
+
<DisplayImage
|
|
36
|
+
v-else-if="contentData.content_type === 'image'"
|
|
37
|
+
:items="contentData.media"
|
|
38
|
+
item-src="url"
|
|
39
|
+
@select="openImage"
|
|
40
|
+
/>
|
|
41
|
+
<!-- Video -->
|
|
42
|
+
<DisplayVideo
|
|
43
|
+
v-else-if="contentData.content_type === 'video'"
|
|
44
|
+
:src="contentData.media?.[0]?.url"
|
|
45
|
+
:poster="contentData.video_poster_url"
|
|
46
|
+
play-button
|
|
47
|
+
width="844"
|
|
48
|
+
height="475"
|
|
49
|
+
preload="metadata"
|
|
50
|
+
class="bg-black"
|
|
51
|
+
/>
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
<CardRefer
|
|
54
|
+
v-if="contentData.mentions?.[0]"
|
|
55
|
+
:item="contentData.mentions[0]"
|
|
56
|
+
/>
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</div>
|
|
64
|
-
<div class="flex gap-[8px] font-body-large">
|
|
65
|
-
<div class="flex gap-[4px]">
|
|
66
|
-
<span class="text-gray">ผู้ติดตาม</span>
|
|
67
|
-
{{ convertNumber(content.author.follower_count ?? 0) }}
|
|
68
|
-
</div>
|
|
69
|
-
<div class="flex gap-[4px]">
|
|
70
|
-
<span class="text-gray">กำลังเผยแพร่</span>
|
|
71
|
-
{{ convertNumber(content.author.published_count ?? 0) }}
|
|
58
|
+
<!-- Figma: Announce Description -->
|
|
59
|
+
<div
|
|
60
|
+
v-if="contentData.author"
|
|
61
|
+
class="flex items-center justify-between gap-[16px]"
|
|
62
|
+
>
|
|
63
|
+
<div class="flex items-center gap-[8px] min-w-0">
|
|
64
|
+
<Avatar :src="contentData.author.avatar ?? ''" :size="40" />
|
|
65
|
+
<div class="flex flex-col gap-[4px] min-w-0">
|
|
66
|
+
<div class="font-body-large-prominent truncate">
|
|
67
|
+
{{ contentData.author.name }}
|
|
72
68
|
</div>
|
|
73
|
-
<div
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
<div
|
|
70
|
+
v-if="authorStats.length"
|
|
71
|
+
class="flex gap-[8px] font-body-large"
|
|
72
|
+
>
|
|
73
|
+
<div
|
|
74
|
+
v-for="stat in authorStats"
|
|
75
|
+
:key="stat.label"
|
|
76
|
+
class="flex gap-[4px]"
|
|
77
|
+
>
|
|
78
|
+
<span class="text-gray">{{ stat.label }}</span>
|
|
79
|
+
{{ stat.value }}
|
|
80
|
+
</div>
|
|
76
81
|
</div>
|
|
77
82
|
</div>
|
|
78
83
|
</div>
|
|
84
|
+
<!-- พรีวิว = ดูหน้าตาได้แต่กดไม่ได้ -->
|
|
85
|
+
<Button circle size="icon-sm" class="shrink-0" inert>
|
|
86
|
+
<Icon name="lucide:ellipsis-vertical" />
|
|
87
|
+
</Button>
|
|
79
88
|
</div>
|
|
80
|
-
<!-- พรีวิว = ดูหน้าตาได้แต่กดไม่ได้ -->
|
|
81
|
-
<Button circle size="icon-sm" class="shrink-0" inert>
|
|
82
|
-
<Icon name="lucide:ellipsis-vertical" />
|
|
83
|
-
</Button>
|
|
84
|
-
</div>
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
<div
|
|
91
|
+
v-if="contentData.description"
|
|
92
|
+
class="font-body-large whitespace-pre-wrap"
|
|
93
|
+
>
|
|
94
|
+
{{ contentData.description }}
|
|
95
|
+
</div>
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
<div v-if="place" class="flex gap-[8px] font-body-large">
|
|
98
|
+
<span class="text-gray">อยู่ที่</span>
|
|
99
|
+
<span class="text-primary">{{ place.name }}</span>
|
|
100
|
+
</div>
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
<div v-if="contentData.tags?.length" class="flex flex-wrap gap-[8px]">
|
|
103
|
+
<Chip v-for="tag in contentData.tags" :key="tag.id" color="bright">
|
|
104
|
+
{{ tag.name }}
|
|
105
|
+
</Chip>
|
|
106
|
+
</div>
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
liked:
|
|
107
|
-
bookmarked:
|
|
108
|
-
shared:
|
|
108
|
+
<CardReaction
|
|
109
|
+
:item-count="{
|
|
110
|
+
liked: contentData.like_count ?? 0,
|
|
111
|
+
bookmarked: contentData.bookmark_count ?? 0,
|
|
112
|
+
shared: contentData.share_count ?? 0
|
|
109
113
|
}"
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
disabled
|
|
115
|
+
/>
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
<!-- Figma: Spotlight -->
|
|
118
|
+
<div v-if="recommendedContents.length" inert>
|
|
119
|
+
<CardContentRecommend :items="recommendedContents" />
|
|
120
|
+
</div>
|
|
117
121
|
|
|
118
|
-
|
|
122
|
+
<!-- ส่งรายการเข้าไปเอง (ไม่ใช้ fetchFn) เพราะ fetchFn โหลดหน้าถัดไปจาก window scroll -->
|
|
119
123
|
<Comment
|
|
120
|
-
:
|
|
121
|
-
:
|
|
124
|
+
:comments="comments"
|
|
125
|
+
:comment-count="commentTotal"
|
|
122
126
|
:current-user="previewUser"
|
|
127
|
+
readonly
|
|
123
128
|
/>
|
|
129
|
+
|
|
130
|
+
<div ref="sentinel" class="h-[1px]" />
|
|
131
|
+
<div v-if="isLoadingComments" class="flex justify-center py-[16px]">
|
|
132
|
+
<Icon
|
|
133
|
+
name="lucide:loader-circle"
|
|
134
|
+
:size="40"
|
|
135
|
+
class="animate-spin text-cloud"
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
124
138
|
</div>
|
|
125
139
|
</div>
|
|
126
140
|
</div>
|
|
141
|
+
|
|
142
|
+
<ModalDisplayImage v-model="isImageOpen" :src="selectedImage" />
|
|
127
143
|
</div>
|
|
128
144
|
</template>
|
|
129
145
|
|
|
130
146
|
<script setup>
|
|
131
|
-
import { computed, onMounted, ref, watch } from "vue";
|
|
147
|
+
import { computed, onMounted, ref, useTemplateRef, watch } from "vue";
|
|
148
|
+
import { useElementVisibility } from "@vueuse/core";
|
|
132
149
|
import { useApi } from "#pukaad-ui/runtime/composables/useApi";
|
|
133
150
|
import { useConvert } from "#pukaad-ui/runtime/composables/useConvert";
|
|
134
151
|
import { mapContentToCard } from "#pukaad-ui/runtime/composables/useContentCard";
|
|
@@ -141,24 +158,75 @@ const api = useApi();
|
|
|
141
158
|
const { convertNumber } = useConvert();
|
|
142
159
|
const endpointMap = {
|
|
143
160
|
personal: "/contents",
|
|
161
|
+
// endpoint เดียวกับที่หน้าจัดการป้ายกำกับเนื้อหาของ office ใช้
|
|
162
|
+
office: "/office/content-labels/contents",
|
|
144
163
|
business: "/business/contents"
|
|
145
164
|
};
|
|
146
|
-
const
|
|
165
|
+
const endpoint = computed(() => endpointMap[props.state]);
|
|
166
|
+
const contentData = ref({});
|
|
147
167
|
const recommendedContents = ref([]);
|
|
168
|
+
const comments = ref([]);
|
|
169
|
+
const commentTotal = ref(0);
|
|
170
|
+
const commentPage = ref(1);
|
|
171
|
+
const commentTotalPages = ref(1);
|
|
172
|
+
const commentPageSize = 20;
|
|
173
|
+
const isLoadingComments = ref(false);
|
|
174
|
+
const hasMoreComments = computed(
|
|
175
|
+
() => commentPage.value < commentTotalPages.value
|
|
176
|
+
);
|
|
177
|
+
const isImageOpen = ref(false);
|
|
178
|
+
const selectedImage = ref("");
|
|
148
179
|
const previewUser = { id: "", name: "", avatar: "" };
|
|
149
180
|
const labelItems = computed(
|
|
150
|
-
() => (
|
|
181
|
+
() => (contentData.value.labels ?? []).map((label) => ({
|
|
151
182
|
id: label.id,
|
|
152
183
|
label: label.name
|
|
153
184
|
}))
|
|
154
185
|
);
|
|
155
|
-
const place = computed(() =>
|
|
156
|
-
const
|
|
186
|
+
const place = computed(() => contentData.value.places?.[0]);
|
|
187
|
+
const authorStats = computed(() => {
|
|
188
|
+
const author = contentData.value.author;
|
|
189
|
+
const stats = [];
|
|
190
|
+
if (author?.follower_count !== void 0) {
|
|
191
|
+
stats.push({
|
|
192
|
+
label: "\u0E1C\u0E39\u0E49\u0E15\u0E34\u0E14\u0E15\u0E32\u0E21",
|
|
193
|
+
value: convertNumber(author.follower_count)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (author?.published_count !== void 0) {
|
|
197
|
+
stats.push({
|
|
198
|
+
label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E40\u0E1C\u0E22\u0E41\u0E1E\u0E23\u0E48",
|
|
199
|
+
value: convertNumber(author.published_count)
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (author?.response_rate !== void 0) {
|
|
203
|
+
stats.push({ label: "\u0E2D\u0E31\u0E15\u0E23\u0E32\u0E15\u0E2D\u0E1A\u0E41\u0E0A\u0E17", value: `${author.response_rate}%` });
|
|
204
|
+
}
|
|
205
|
+
return stats;
|
|
206
|
+
});
|
|
207
|
+
const fromOfficeContent = (data) => {
|
|
208
|
+
if (!data?.content) return {};
|
|
209
|
+
const { profile_name, profile_avatar, ...rest } = data.content;
|
|
210
|
+
return {
|
|
211
|
+
...rest,
|
|
212
|
+
media: data.media,
|
|
213
|
+
author: {
|
|
214
|
+
id: "",
|
|
215
|
+
name: profile_name ?? "",
|
|
216
|
+
avatar: profile_avatar ?? null
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
const fetchContentData = async () => {
|
|
221
|
+
const url = `${endpoint.value}/${props.contentId}`;
|
|
157
222
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
223
|
+
if (props.state === "office") {
|
|
224
|
+
const res = await api(url);
|
|
225
|
+
contentData.value = fromOfficeContent(res.data);
|
|
226
|
+
} else {
|
|
227
|
+
const res = await api(url);
|
|
228
|
+
contentData.value = res.data ?? {};
|
|
229
|
+
}
|
|
162
230
|
} catch (err) {
|
|
163
231
|
console.error(err);
|
|
164
232
|
}
|
|
@@ -200,20 +268,49 @@ const toCommentItem = (raw) => ({
|
|
|
200
268
|
is_liked: r.is_liked ?? false
|
|
201
269
|
}))
|
|
202
270
|
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
271
|
+
let commentRequestId = 0;
|
|
272
|
+
const fetchComments = async (page = 1) => {
|
|
273
|
+
if (page > 1 && isLoadingComments.value) return;
|
|
274
|
+
const requestId = ++commentRequestId;
|
|
275
|
+
isLoadingComments.value = true;
|
|
276
|
+
try {
|
|
277
|
+
const res = await api(`/contents/${props.contentId}/comments`, {
|
|
278
|
+
query: {
|
|
279
|
+
limit: commentPageSize,
|
|
280
|
+
offset: (page - 1) * commentPageSize
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
if (requestId !== commentRequestId) return;
|
|
284
|
+
const items = (res.data ?? []).map(toCommentItem);
|
|
285
|
+
comments.value = page === 1 ? items : [...comments.value, ...items];
|
|
286
|
+
commentTotal.value = res.meta?.total_comments ?? 0;
|
|
287
|
+
commentTotalPages.value = res.meta?.total_pages ?? 1;
|
|
288
|
+
commentPage.value = page;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error(err);
|
|
291
|
+
if (requestId === commentRequestId && page === 1) comments.value = [];
|
|
292
|
+
} finally {
|
|
293
|
+
if (requestId === commentRequestId) isLoadingComments.value = false;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const openImage = (item) => {
|
|
297
|
+
if (!item?.url) return;
|
|
298
|
+
selectedImage.value = item.url;
|
|
299
|
+
isImageOpen.value = true;
|
|
212
300
|
};
|
|
213
301
|
const loadAll = () => {
|
|
214
|
-
|
|
302
|
+
fetchContentData();
|
|
215
303
|
fetchRecommendations();
|
|
304
|
+
fetchComments(1);
|
|
216
305
|
};
|
|
306
|
+
const sentinel = useTemplateRef("sentinel");
|
|
307
|
+
const isSentinelVisible = useElementVisibility(sentinel);
|
|
308
|
+
watch(
|
|
309
|
+
[isSentinelVisible, hasMoreComments, isLoadingComments],
|
|
310
|
+
([visible, more, loading]) => {
|
|
311
|
+
if (visible && more && !loading) fetchComments(commentPage.value + 1);
|
|
312
|
+
}
|
|
313
|
+
);
|
|
217
314
|
watch(() => [props.contentId, props.state], loadAll);
|
|
218
315
|
onMounted(loadAll);
|
|
219
316
|
</script>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { CardReferItem } from "#pukaad-ui/runtime/components/card/card-refer.vue";
|
|
2
|
-
export type PreviewContentState = "personal" | "business";
|
|
2
|
+
export type PreviewContentState = "personal" | "office" | "business";
|
|
3
|
+
export interface PreviewContentMedia {
|
|
4
|
+
id?: string | number;
|
|
5
|
+
media_type: string;
|
|
6
|
+
url: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
3
9
|
export interface PreviewContentData {
|
|
4
10
|
id?: string;
|
|
5
11
|
content_type?: "post_it" | "image" | "video";
|
|
@@ -11,11 +17,7 @@ export interface PreviewContentData {
|
|
|
11
17
|
cover_color?: string;
|
|
12
18
|
video_poster_url?: string;
|
|
13
19
|
description?: string;
|
|
14
|
-
media?:
|
|
15
|
-
media_type: string;
|
|
16
|
-
url: string;
|
|
17
|
-
description?: string;
|
|
18
|
-
}[];
|
|
20
|
+
media?: PreviewContentMedia[];
|
|
19
21
|
tags?: {
|
|
20
22
|
id: string;
|
|
21
23
|
name: string;
|
|
@@ -31,19 +31,10 @@ export interface PreviewPlaceData {
|
|
|
31
31
|
opening_hours?: OpeningHour[];
|
|
32
32
|
special_days?: SpecialDay[];
|
|
33
33
|
}
|
|
34
|
-
export interface PreviewPlaceRecommendItem {
|
|
35
|
-
id: string | number;
|
|
36
|
-
title: string;
|
|
37
|
-
image: string;
|
|
38
|
-
rating: number;
|
|
39
|
-
review_count: number;
|
|
40
|
-
}
|
|
41
34
|
type __VLS_Props = {
|
|
42
35
|
placeId: string | number;
|
|
43
36
|
/** เลือก endpoint ตามแอปที่เปิดพรีวิว */
|
|
44
37
|
state?: PreviewPlaceState;
|
|
45
|
-
/** "ที่ไหนน่าสนใจ" — ยังไม่มี API ให้แอปส่งมาเอง ไม่ส่ง = ซ่อนการ์ด */
|
|
46
|
-
recommendedPlaces?: PreviewPlaceRecommendItem[];
|
|
47
38
|
};
|
|
48
39
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
49
40
|
exit: () => any;
|
|
@@ -51,7 +42,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
51
42
|
onExit?: (() => any) | undefined;
|
|
52
43
|
}>, {
|
|
53
44
|
state: PreviewPlaceState;
|
|
54
|
-
recommendedPlaces: PreviewPlaceRecommendItem[];
|
|
55
45
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
56
46
|
declare const _default: typeof __VLS_export;
|
|
57
47
|
export default _default;
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
<!-- Figma: Navbar Preview (new tab) — Property 1=Admin -->
|
|
5
5
|
<div class="shrink-0 w-full bg-cloud">
|
|
6
6
|
<div class="max-w-[1280px] mx-auto flex justify-end p-[16px]">
|
|
7
|
-
<Button variant="outline" @click="emit('exit')"
|
|
7
|
+
<Button variant="outline" class="rounded-[6px]" @click="emit('exit')">
|
|
8
|
+
ออกจากพรีวิว
|
|
9
|
+
</Button>
|
|
8
10
|
</div>
|
|
9
11
|
</div>
|
|
10
12
|
|
|
13
|
+
<!-- เลื่อนในกล่องนี้ ไม่พึ่ง scroll ของหน้า (บางแอปล็อก html/body ไว้) -->
|
|
11
14
|
<div class="flex-1 overflow-y-auto">
|
|
12
15
|
<div class="max-w-[1280px] mx-auto p-[16px] flex flex-col gap-[16px]">
|
|
13
16
|
<!-- Place Cover -->
|
|
@@ -181,12 +184,10 @@
|
|
|
181
184
|
</div>
|
|
182
185
|
</div>
|
|
183
186
|
|
|
184
|
-
<!-- ขวา: Place About
|
|
187
|
+
<!-- ขวา: Place About -->
|
|
188
|
+
<!-- TODO: "ที่ไหนน่าสนใจ" (Figma: Reccommended Places) — ยังไม่มี API -->
|
|
185
189
|
<div class="w-[380px] shrink-0 flex flex-col gap-[16px]">
|
|
186
190
|
<CardPlaceDetail :item="placeDetailItem" />
|
|
187
|
-
<div v-if="props.recommendedPlaces.length > 0" inert>
|
|
188
|
-
<CardRecommendPlace :items="props.recommendedPlaces" />
|
|
189
|
-
</div>
|
|
190
191
|
</div>
|
|
191
192
|
</div>
|
|
192
193
|
</div>
|
|
@@ -201,8 +202,7 @@ import { useApi } from "#pukaad-ui/runtime/composables/useApi";
|
|
|
201
202
|
import { useConvert } from "#pukaad-ui/runtime/composables/useConvert";
|
|
202
203
|
const props = defineProps({
|
|
203
204
|
placeId: { type: [String, Number], required: true },
|
|
204
|
-
state: { type: String, required: false, default: "personal" }
|
|
205
|
-
recommendedPlaces: { type: Array, required: false, default: () => [] }
|
|
205
|
+
state: { type: String, required: false, default: "personal" }
|
|
206
206
|
});
|
|
207
207
|
const emit = defineEmits(["exit"]);
|
|
208
208
|
const api = useApi();
|
|
@@ -31,19 +31,10 @@ export interface PreviewPlaceData {
|
|
|
31
31
|
opening_hours?: OpeningHour[];
|
|
32
32
|
special_days?: SpecialDay[];
|
|
33
33
|
}
|
|
34
|
-
export interface PreviewPlaceRecommendItem {
|
|
35
|
-
id: string | number;
|
|
36
|
-
title: string;
|
|
37
|
-
image: string;
|
|
38
|
-
rating: number;
|
|
39
|
-
review_count: number;
|
|
40
|
-
}
|
|
41
34
|
type __VLS_Props = {
|
|
42
35
|
placeId: string | number;
|
|
43
36
|
/** เลือก endpoint ตามแอปที่เปิดพรีวิว */
|
|
44
37
|
state?: PreviewPlaceState;
|
|
45
|
-
/** "ที่ไหนน่าสนใจ" — ยังไม่มี API ให้แอปส่งมาเอง ไม่ส่ง = ซ่อนการ์ด */
|
|
46
|
-
recommendedPlaces?: PreviewPlaceRecommendItem[];
|
|
47
38
|
};
|
|
48
39
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
49
40
|
exit: () => any;
|
|
@@ -51,7 +42,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
51
42
|
onExit?: (() => any) | undefined;
|
|
52
43
|
}>, {
|
|
53
44
|
state: PreviewPlaceState;
|
|
54
|
-
recommendedPlaces: PreviewPlaceRecommendItem[];
|
|
55
45
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
56
46
|
declare const _default: typeof __VLS_export;
|
|
57
47
|
export default _default;
|
|
@@ -18,8 +18,8 @@ type __VLS_Slots = {} & {
|
|
|
18
18
|
default?: (props: typeof __VLS_1) => any;
|
|
19
19
|
};
|
|
20
20
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
21
|
-
payload: Record<string, any>;
|
|
22
21
|
config: ChartConfig;
|
|
22
|
+
payload: Record<string, any>;
|
|
23
23
|
indicator: "line" | "dot" | "dashed";
|
|
24
24
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
25
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -18,8 +18,8 @@ type __VLS_Slots = {} & {
|
|
|
18
18
|
default?: (props: typeof __VLS_1) => any;
|
|
19
19
|
};
|
|
20
20
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
21
|
-
payload: Record<string, any>;
|
|
22
21
|
config: ChartConfig;
|
|
22
|
+
payload: Record<string, any>;
|
|
23
23
|
indicator: "line" | "dot" | "dashed";
|
|
24
24
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
25
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -14,7 +14,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
14
14
|
dir?: string | undefined | undefined;
|
|
15
15
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
16
16
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
17
|
-
enterKeyHint?: "search" | "done" | "
|
|
17
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
18
18
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
19
19
|
id?: string | undefined | undefined;
|
|
20
20
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -208,7 +208,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
208
208
|
dir?: string | undefined | undefined;
|
|
209
209
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
210
210
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
211
|
-
enterKeyHint?: "search" | "done" | "
|
|
211
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
212
212
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
213
213
|
id?: string | undefined | undefined;
|
|
214
214
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -14,7 +14,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
14
14
|
dir?: string | undefined | undefined;
|
|
15
15
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
16
16
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
17
|
-
enterKeyHint?: "search" | "done" | "
|
|
17
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
18
18
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
19
19
|
id?: string | undefined | undefined;
|
|
20
20
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -208,7 +208,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
208
208
|
dir?: string | undefined | undefined;
|
|
209
209
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
210
210
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
211
|
-
enterKeyHint?: "search" | "done" | "
|
|
211
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
212
212
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
213
213
|
id?: string | undefined | undefined;
|
|
214
214
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -16,7 +16,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
16
16
|
dir?: string | undefined | undefined;
|
|
17
17
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
18
18
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
19
|
-
enterKeyHint?: "search" | "done" | "
|
|
19
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
20
20
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
21
21
|
id?: string | undefined | undefined;
|
|
22
22
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -212,7 +212,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
212
212
|
dir?: string | undefined | undefined;
|
|
213
213
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
214
214
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
215
|
-
enterKeyHint?: "search" | "done" | "
|
|
215
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
216
216
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
217
217
|
id?: string | undefined | undefined;
|
|
218
218
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -16,7 +16,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
16
16
|
dir?: string | undefined | undefined;
|
|
17
17
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
18
18
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
19
|
-
enterKeyHint?: "search" | "done" | "
|
|
19
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
20
20
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
21
21
|
id?: string | undefined | undefined;
|
|
22
22
|
inert?: (boolean | "true" | "false") | undefined;
|
|
@@ -212,7 +212,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
212
212
|
dir?: string | undefined | undefined;
|
|
213
213
|
draggable?: (boolean | "true" | "false") | undefined;
|
|
214
214
|
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
|
|
215
|
-
enterKeyHint?: "search" | "done" | "
|
|
215
|
+
enterKeyHint?: "search" | "done" | "next" | "send" | "enter" | "go" | "previous" | undefined;
|
|
216
216
|
hidden?: "" | (boolean | "true" | "false") | "hidden" | "until-found" | undefined;
|
|
217
217
|
id?: string | undefined | undefined;
|
|
218
218
|
inert?: (boolean | "true" | "false") | undefined;
|