pukaad-ui-lib 1.421.0 → 1.422.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-reaction.d.vue.ts +2 -0
- package/dist/runtime/components/card/card-reaction.vue +39 -22
- package/dist/runtime/components/card/card-reaction.vue.d.ts +2 -0
- package/dist/runtime/components/drawer/drawer-post-content.vue +1 -0
- package/dist/runtime/components/image/image-cropper.d.vue.ts +1 -1
- package/dist/runtime/components/image/image-cropper.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-address.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-address.vue.d.ts +1 -1
- 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-personal-card-id.d.vue.ts +2 -2
- package/dist/runtime/components/input/input-personal-card-id.vue.d.ts +2 -2
- package/dist/runtime/components/input/input-tag.d.vue.ts +2 -0
- package/dist/runtime/components/input/input-tag.vue +83 -31
- package/dist/runtime/components/input/input-tag.vue.d.ts +2 -0
- 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/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/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -20,6 +20,7 @@ type __VLS_Props = {
|
|
|
20
20
|
iconSize?: number | string;
|
|
21
21
|
id?: string;
|
|
22
22
|
state?: string;
|
|
23
|
+
loginPath?: string;
|
|
23
24
|
};
|
|
24
25
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
25
26
|
disabled: boolean;
|
|
@@ -32,6 +33,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
32
33
|
disabledDividerBottom: boolean;
|
|
33
34
|
iconSize: number | string;
|
|
34
35
|
state: string;
|
|
36
|
+
loginPath: string;
|
|
35
37
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
36
38
|
declare const _default: typeof __VLS_export;
|
|
37
39
|
export default _default;
|
|
@@ -32,8 +32,14 @@
|
|
|
32
32
|
|
|
33
33
|
<script setup>
|
|
34
34
|
import { computed, reactive, ref, watch } from "vue";
|
|
35
|
+
import { navigateTo, useCookie, useRoute, useRuntimeConfig } from "nuxt/app";
|
|
35
36
|
import { useApi } from "../../composables/useApi";
|
|
36
37
|
const api = useApi();
|
|
38
|
+
const route = useRoute();
|
|
39
|
+
const config = useRuntimeConfig();
|
|
40
|
+
const secId = useCookie(
|
|
41
|
+
config.public.APP_TYPE === "OFFICE" ? "OFFICE_SEC_ID" : "SEC_ID"
|
|
42
|
+
);
|
|
37
43
|
const isOpenModalShare = ref(false);
|
|
38
44
|
const props = defineProps({
|
|
39
45
|
itemCount: { type: Object, required: false, default: () => ({
|
|
@@ -54,7 +60,8 @@ const props = defineProps({
|
|
|
54
60
|
disabled: { type: Boolean, required: false, default: false },
|
|
55
61
|
iconSize: { type: [Number, String], required: false, default: 20 },
|
|
56
62
|
id: { type: String, required: false, default: "" },
|
|
57
|
-
state: { type: String, required: false, default: "" }
|
|
63
|
+
state: { type: String, required: false, default: "" },
|
|
64
|
+
loginPath: { type: String, required: false, default: "/auth/login" }
|
|
58
65
|
});
|
|
59
66
|
const computedPadding = computed(
|
|
60
67
|
() => props.disabledPadding ? 0 : props.padding
|
|
@@ -114,37 +121,46 @@ const reactions = [
|
|
|
114
121
|
action: toggleShared
|
|
115
122
|
}
|
|
116
123
|
];
|
|
124
|
+
function redirectToLogin() {
|
|
125
|
+
return navigateTo({
|
|
126
|
+
path: props.loginPath,
|
|
127
|
+
query: { redirect: route.fullPath }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function ensureLoggedIn() {
|
|
131
|
+
if (secId.value) return true;
|
|
132
|
+
redirectToLogin();
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
function isUnauthorized(error) {
|
|
136
|
+
const e = error;
|
|
137
|
+
return (e?.status ?? e?.statusCode) === 401;
|
|
138
|
+
}
|
|
117
139
|
async function toggleLike() {
|
|
140
|
+
const likeUrl = props.state === "blog" && props.id ? `/blogs/${props.id}/like` : props.state === "content" && props.id ? `/contents/${props.id}/like` : "";
|
|
141
|
+
if (likeUrl && !ensureLoggedIn()) return;
|
|
118
142
|
const previousLiked = reaction.liked;
|
|
119
143
|
reaction.liked = !previousLiked;
|
|
120
144
|
counts.liked += reaction.liked ? 1 : -1;
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} else if (props.state === "content" && props.id) {
|
|
132
|
-
try {
|
|
133
|
-
await api(`/contents/${props.id}/like`, {
|
|
134
|
-
method: reaction.liked ? "POST" : "DELETE"
|
|
135
|
-
});
|
|
136
|
-
} catch (error) {
|
|
137
|
-
console.error(error);
|
|
138
|
-
reaction.liked = previousLiked;
|
|
139
|
-
counts.liked += previousLiked ? 1 : -1;
|
|
140
|
-
}
|
|
145
|
+
if (!likeUrl) return;
|
|
146
|
+
try {
|
|
147
|
+
await api(likeUrl, {
|
|
148
|
+
method: reaction.liked ? "POST" : "DELETE"
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(error);
|
|
152
|
+
reaction.liked = previousLiked;
|
|
153
|
+
counts.liked += previousLiked ? 1 : -1;
|
|
154
|
+
if (isUnauthorized(error)) redirectToLogin();
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
157
|
async function toggleBookmark() {
|
|
158
|
+
const canCallApi = props.state === "content" && !!props.id;
|
|
159
|
+
if (canCallApi && !ensureLoggedIn()) return;
|
|
144
160
|
const previousBookmarked = reaction.bookmarked;
|
|
145
161
|
reaction.bookmarked = !previousBookmarked;
|
|
146
162
|
counts.bookmarked += reaction.bookmarked ? 1 : -1;
|
|
147
|
-
if (
|
|
163
|
+
if (canCallApi) {
|
|
148
164
|
try {
|
|
149
165
|
await api(`/contents/${props.id}/bookmark`, {
|
|
150
166
|
method: reaction.bookmarked ? "POST" : "DELETE"
|
|
@@ -153,6 +169,7 @@ async function toggleBookmark() {
|
|
|
153
169
|
console.error(error);
|
|
154
170
|
reaction.bookmarked = previousBookmarked;
|
|
155
171
|
counts.bookmarked += previousBookmarked ? 1 : -1;
|
|
172
|
+
if (isUnauthorized(error)) redirectToLogin();
|
|
156
173
|
}
|
|
157
174
|
}
|
|
158
175
|
}
|
|
@@ -20,6 +20,7 @@ type __VLS_Props = {
|
|
|
20
20
|
iconSize?: number | string;
|
|
21
21
|
id?: string;
|
|
22
22
|
state?: string;
|
|
23
|
+
loginPath?: string;
|
|
23
24
|
};
|
|
24
25
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
25
26
|
disabled: boolean;
|
|
@@ -32,6 +33,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
32
33
|
disabledDividerBottom: boolean;
|
|
33
34
|
iconSize: number | string;
|
|
34
35
|
state: string;
|
|
36
|
+
loginPath: string;
|
|
35
37
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
36
38
|
declare const _default: typeof __VLS_export;
|
|
37
39
|
export default _default;
|
|
@@ -64,11 +64,11 @@ 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
|
-
crossorigin: "" | "anonymous" | "use-credentials";
|
|
68
67
|
responsive: boolean;
|
|
69
68
|
restore: boolean;
|
|
70
69
|
checkCrossOrigin: boolean;
|
|
71
70
|
checkOrientation: boolean;
|
|
71
|
+
crossorigin: "" | "anonymous" | "use-credentials";
|
|
72
72
|
modal: boolean;
|
|
73
73
|
guides: boolean;
|
|
74
74
|
highlight: boolean;
|
|
@@ -64,11 +64,11 @@ 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
|
-
crossorigin: "" | "anonymous" | "use-credentials";
|
|
68
67
|
responsive: boolean;
|
|
69
68
|
restore: boolean;
|
|
70
69
|
checkCrossOrigin: boolean;
|
|
71
70
|
checkOrientation: boolean;
|
|
71
|
+
crossorigin: "" | "anonymous" | "use-credentials";
|
|
72
72
|
modal: boolean;
|
|
73
73
|
guides: boolean;
|
|
74
74
|
highlight: boolean;
|
|
@@ -50,10 +50,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
50
50
|
name: string;
|
|
51
51
|
required: boolean;
|
|
52
52
|
placeholder: string;
|
|
53
|
+
gap: string;
|
|
53
54
|
labelDetail: string;
|
|
54
55
|
placeholderDetail: string;
|
|
55
56
|
requiredDetail: boolean;
|
|
56
|
-
gap: string;
|
|
57
57
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
58
58
|
declare const _default: typeof __VLS_export;
|
|
59
59
|
export default _default;
|
|
@@ -50,10 +50,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
50
50
|
name: string;
|
|
51
51
|
required: boolean;
|
|
52
52
|
placeholder: string;
|
|
53
|
+
gap: string;
|
|
53
54
|
labelDetail: string;
|
|
54
55
|
placeholderDetail: string;
|
|
55
56
|
requiredDetail: boolean;
|
|
56
|
-
gap: string;
|
|
57
57
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
58
58
|
declare const _default: typeof __VLS_export;
|
|
59
59
|
export default _default;
|
|
@@ -35,8 +35,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
35
35
|
fullHeight: boolean;
|
|
36
36
|
fullWidth: boolean;
|
|
37
37
|
limit: number;
|
|
38
|
-
disabledErrorMessage: boolean;
|
|
39
38
|
accept: string;
|
|
39
|
+
disabledErrorMessage: boolean;
|
|
40
40
|
labelIcon: string;
|
|
41
41
|
disabledDrop: boolean;
|
|
42
42
|
column: boolean;
|
|
@@ -35,8 +35,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
35
35
|
fullHeight: boolean;
|
|
36
36
|
fullWidth: boolean;
|
|
37
37
|
limit: number;
|
|
38
|
-
disabledErrorMessage: boolean;
|
|
39
38
|
accept: string;
|
|
39
|
+
disabledErrorMessage: boolean;
|
|
40
40
|
labelIcon: string;
|
|
41
41
|
disabledDrop: boolean;
|
|
42
42
|
column: boolean;
|
|
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
22
22
|
}>, {
|
|
23
23
|
id: string;
|
|
24
24
|
name: string;
|
|
25
|
-
new: boolean;
|
|
26
25
|
disabledForgotPassword: boolean;
|
|
26
|
+
new: 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
|
-
new: boolean;
|
|
26
25
|
disabledForgotPassword: boolean;
|
|
26
|
+
new: boolean;
|
|
27
27
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
28
|
declare const _default: typeof __VLS_export;
|
|
29
29
|
export default _default;
|
|
@@ -11,12 +11,12 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
12
|
"update:modelValue": (value: File | null) => any;
|
|
13
13
|
} & {
|
|
14
|
-
remove: () => any;
|
|
15
14
|
complete: (file: File) => any;
|
|
15
|
+
remove: () => any;
|
|
16
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
17
17
|
"onUpdate:modelValue"?: ((value: File | null) => any) | undefined;
|
|
18
|
-
onRemove?: (() => any) | undefined;
|
|
19
18
|
onComplete?: ((file: File) => any) | undefined;
|
|
19
|
+
onRemove?: (() => any) | undefined;
|
|
20
20
|
}>, {
|
|
21
21
|
name: string;
|
|
22
22
|
disabledErrorMessage: boolean;
|
|
@@ -11,12 +11,12 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
12
|
"update:modelValue": (value: File | null) => any;
|
|
13
13
|
} & {
|
|
14
|
-
remove: () => any;
|
|
15
14
|
complete: (file: File) => any;
|
|
15
|
+
remove: () => any;
|
|
16
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
17
17
|
"onUpdate:modelValue"?: ((value: File | null) => any) | undefined;
|
|
18
|
-
onRemove?: (() => any) | undefined;
|
|
19
18
|
onComplete?: ((file: File) => any) | undefined;
|
|
19
|
+
onRemove?: (() => any) | undefined;
|
|
20
20
|
}>, {
|
|
21
21
|
name: string;
|
|
22
22
|
disabledErrorMessage: boolean;
|
|
@@ -13,6 +13,7 @@ interface InputTagProps {
|
|
|
13
13
|
placeholder?: string;
|
|
14
14
|
state?: "user" | "admin";
|
|
15
15
|
ignore?: string[];
|
|
16
|
+
profileScope?: "personal" | "business";
|
|
16
17
|
}
|
|
17
18
|
type __VLS_Props = InputTagProps;
|
|
18
19
|
type __VLS_ModelProps = {
|
|
@@ -29,6 +30,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
29
30
|
placeholder: string;
|
|
30
31
|
limit: number;
|
|
31
32
|
ignore: string[];
|
|
33
|
+
profileScope: "personal" | "business";
|
|
32
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
33
35
|
declare const _default: typeof __VLS_export;
|
|
34
36
|
export default _default;
|
|
@@ -51,12 +51,15 @@
|
|
|
51
51
|
<ShadCommand :filter="() => 1">
|
|
52
52
|
<ShadCommandInput
|
|
53
53
|
v-if="props.state === 'admin'"
|
|
54
|
+
v-model="searchQuery"
|
|
54
55
|
placeholder="ค้นหาแท็ก"
|
|
55
56
|
/>
|
|
56
57
|
<ShadTabs v-model="currentTab" class="w-full p-2">
|
|
57
58
|
<ShadTabsList class="w-full grid grid-cols-2">
|
|
58
59
|
<ShadTabsTrigger value="popular">ยอดนิยม</ShadTabsTrigger>
|
|
59
|
-
<ShadTabsTrigger value="
|
|
60
|
+
<ShadTabsTrigger value="recent">{{
|
|
61
|
+
isOffice ? "\u0E25\u0E48\u0E32\u0E2A\u0E38\u0E14" : "\u0E43\u0E0A\u0E49\u0E07\u0E32\u0E19\u0E25\u0E48\u0E32\u0E2A\u0E38\u0E14"
|
|
62
|
+
}}</ShadTabsTrigger>
|
|
60
63
|
</ShadTabsList>
|
|
61
64
|
|
|
62
65
|
<!-- Popular Tab -->
|
|
@@ -87,7 +90,9 @@
|
|
|
87
90
|
class="flex flex-col items-center justify-center h-full gap-2"
|
|
88
91
|
>
|
|
89
92
|
<Icon name="pukaad:tag-empty" class="h-[123px] w-[180px]" />
|
|
90
|
-
<div class="text-gray font-body-medium"
|
|
93
|
+
<div class="text-gray font-body-medium">
|
|
94
|
+
{{ popularState.error || "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25" }}
|
|
95
|
+
</div>
|
|
91
96
|
</div>
|
|
92
97
|
</ShadCommandEmpty>
|
|
93
98
|
|
|
@@ -104,12 +109,12 @@
|
|
|
104
109
|
</ShadCommandList>
|
|
105
110
|
</ShadTabsContent>
|
|
106
111
|
|
|
107
|
-
<!--
|
|
108
|
-
<ShadTabsContent value="
|
|
112
|
+
<!-- Recent / Office latest tab -->
|
|
113
|
+
<ShadTabsContent value="recent" class="m-0">
|
|
109
114
|
<ShadCommandList class="max-h-64 overflow-y-auto">
|
|
110
115
|
<ShadCommandGroup>
|
|
111
116
|
<ShadCommandItem
|
|
112
|
-
v-for="tag in
|
|
117
|
+
v-for="tag in filteredRecentTags"
|
|
113
118
|
:key="tag.value"
|
|
114
119
|
:value="tag.value"
|
|
115
120
|
class="flex w-full justify-between"
|
|
@@ -125,19 +130,23 @@
|
|
|
125
130
|
<!-- Initital Loading / Empty State -->
|
|
126
131
|
<ShadCommandEmpty
|
|
127
132
|
:force-show="
|
|
128
|
-
|
|
133
|
+
filteredRecentTags.length === 0 && !recentState.loading
|
|
129
134
|
"
|
|
130
135
|
>
|
|
131
136
|
<div
|
|
132
137
|
class="flex flex-col items-center justify-center h-full gap-2"
|
|
133
138
|
>
|
|
134
139
|
<Icon name="pukaad:tag-empty" class="h-[123px] w-[180px]" />
|
|
135
|
-
<div class="text-gray font-body-medium"
|
|
140
|
+
<div class="text-gray font-body-medium">
|
|
141
|
+
{{
|
|
142
|
+
recentState.error || (!isOffice && !searchQuery && recentState.data.length === 0 ? "\u0E22\u0E31\u0E07\u0E44\u0E21\u0E48\u0E21\u0E35\u0E41\u0E17\u0E47\u0E01\u0E17\u0E35\u0E48\u0E43\u0E0A\u0E49\u0E07\u0E32\u0E19\u0E25\u0E48\u0E32\u0E2A\u0E38\u0E14" : "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25")
|
|
143
|
+
}}
|
|
144
|
+
</div>
|
|
136
145
|
</div>
|
|
137
146
|
</ShadCommandEmpty>
|
|
138
147
|
|
|
139
148
|
<!-- Loading Indicator -->
|
|
140
|
-
<div v-if="
|
|
149
|
+
<div v-if="recentState.loading" class="flex justify-center p-2">
|
|
141
150
|
<Icon
|
|
142
151
|
name="lucide:loader-2"
|
|
143
152
|
class="animate-spin h-5 w-5 text-primary"
|
|
@@ -145,7 +154,7 @@
|
|
|
145
154
|
</div>
|
|
146
155
|
|
|
147
156
|
<!-- Sentinel for Infinite Scroll -->
|
|
148
|
-
<div ref="
|
|
157
|
+
<div ref="recentSentinel" class="h-1" />
|
|
149
158
|
</ShadCommandList>
|
|
150
159
|
</ShadTabsContent>
|
|
151
160
|
</ShadTabs>
|
|
@@ -155,13 +164,21 @@
|
|
|
155
164
|
</template>
|
|
156
165
|
|
|
157
166
|
<script setup>
|
|
158
|
-
import { ref, computed,
|
|
159
|
-
import { useNuxtApp } from "nuxt/app";
|
|
167
|
+
import { ref, computed, reactive, watch } from "vue";
|
|
168
|
+
import { useNuxtApp, useRuntimeConfig, useCookie } from "nuxt/app";
|
|
160
169
|
import { useApi } from "../../composables/useApi";
|
|
161
170
|
import { useIntersectionObserver } from "@vueuse/core";
|
|
162
171
|
const { $convert } = useNuxtApp();
|
|
163
172
|
const convertNumber = (val) => $convert?.convertNumber(val) || val;
|
|
164
173
|
const api = useApi();
|
|
174
|
+
const config = useRuntimeConfig();
|
|
175
|
+
const isOffice = computed(
|
|
176
|
+
() => props.state === "admin" || config.public.APP_TYPE === "OFFICE"
|
|
177
|
+
);
|
|
178
|
+
const businessProfileId = useCookie(
|
|
179
|
+
"PUKAAD_BUSINESS_PROFILE_ID"
|
|
180
|
+
);
|
|
181
|
+
const sessionId = useCookie("SEC_ID");
|
|
165
182
|
const props = defineProps({
|
|
166
183
|
name: { type: String, required: false, default: "input-tag" },
|
|
167
184
|
rules: { type: [Object, String, Function], required: false },
|
|
@@ -171,7 +188,8 @@ const props = defineProps({
|
|
|
171
188
|
limit: { type: Number, required: false, default: 0 },
|
|
172
189
|
placeholder: { type: String, required: false, default: "\u0E04\u0E49\u0E19\u0E2B\u0E32\u0E41\u0E17\u0E47\u0E01..." },
|
|
173
190
|
state: { type: String, required: false, default: "user" },
|
|
174
|
-
ignore: { type: Array, required: false, default: () => [] }
|
|
191
|
+
ignore: { type: Array, required: false, default: () => [] },
|
|
192
|
+
profileScope: { type: String, required: false, default: "personal" }
|
|
175
193
|
});
|
|
176
194
|
const modelValue = defineModel({ type: Array, ...{
|
|
177
195
|
default: () => []
|
|
@@ -185,13 +203,15 @@ const popularState = reactive({
|
|
|
185
203
|
data: [],
|
|
186
204
|
page: 1,
|
|
187
205
|
hasMore: true,
|
|
188
|
-
loading: false
|
|
206
|
+
loading: false,
|
|
207
|
+
error: ""
|
|
189
208
|
});
|
|
190
|
-
const
|
|
209
|
+
const recentState = reactive({
|
|
191
210
|
data: [],
|
|
192
211
|
page: 1,
|
|
193
212
|
hasMore: true,
|
|
194
|
-
loading: false
|
|
213
|
+
loading: false,
|
|
214
|
+
error: ""
|
|
195
215
|
});
|
|
196
216
|
const mapTag = (tag) => ({
|
|
197
217
|
label: tag.name,
|
|
@@ -199,14 +219,23 @@ const mapTag = (tag) => ({
|
|
|
199
219
|
postCount: tag.usage_count
|
|
200
220
|
});
|
|
201
221
|
const PAGE_SIZE = 20;
|
|
222
|
+
let requestGeneration = 0;
|
|
202
223
|
const fetchTags = async (type) => {
|
|
203
|
-
const state = type === "popular" ? popularState :
|
|
224
|
+
const state = type === "popular" ? popularState : recentState;
|
|
204
225
|
if (state.loading || !state.hasMore) return;
|
|
205
226
|
state.loading = true;
|
|
227
|
+
state.error = "";
|
|
228
|
+
const generation = requestGeneration;
|
|
206
229
|
try {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
230
|
+
const endpoint = type === "recent" && isOffice.value ? "latest" : type;
|
|
231
|
+
const res = await api(`/tags/${endpoint}`, {
|
|
232
|
+
query: {
|
|
233
|
+
page: state.page,
|
|
234
|
+
page_size: PAGE_SIZE,
|
|
235
|
+
scope: props.profileScope
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
if (generation !== requestGeneration) return;
|
|
210
239
|
if (res.data && Array.isArray(res.data)) {
|
|
211
240
|
const newTags = res.data.map(mapTag);
|
|
212
241
|
state.data = [...state.data, ...newTags];
|
|
@@ -216,42 +245,65 @@ const fetchTags = async (type) => {
|
|
|
216
245
|
state.hasMore = false;
|
|
217
246
|
}
|
|
218
247
|
} catch (e) {
|
|
248
|
+
if (generation !== requestGeneration) return;
|
|
219
249
|
console.error(`Failed to fetch ${type} tags:`, e);
|
|
220
|
-
state.
|
|
250
|
+
state.error = "\u0E42\u0E2B\u0E25\u0E14\u0E41\u0E17\u0E47\u0E01\u0E44\u0E21\u0E48\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E1B\u0E34\u0E14\u0E41\u0E25\u0E49\u0E27\u0E40\u0E1B\u0E34\u0E14\u0E43\u0E2B\u0E21\u0E48";
|
|
221
251
|
} finally {
|
|
222
|
-
state.loading = false;
|
|
252
|
+
if (generation === requestGeneration) state.loading = false;
|
|
223
253
|
}
|
|
224
254
|
};
|
|
225
255
|
watch(popoverOpen, (isOpen) => {
|
|
226
256
|
if (isOpen) {
|
|
257
|
+
resetLists();
|
|
227
258
|
if (currentTab.value === "popular" && popularState.data.length === 0) {
|
|
228
259
|
fetchTags("popular");
|
|
229
|
-
} else if (currentTab.value === "
|
|
230
|
-
fetchTags("
|
|
260
|
+
} else if (currentTab.value === "recent" && recentState.data.length === 0) {
|
|
261
|
+
fetchTags("recent");
|
|
231
262
|
}
|
|
232
263
|
}
|
|
233
264
|
});
|
|
234
265
|
const popularSentinel = ref(null);
|
|
235
|
-
const
|
|
266
|
+
const recentSentinel = ref(null);
|
|
236
267
|
useIntersectionObserver(popularSentinel, (entries) => {
|
|
237
268
|
const entry = entries[0];
|
|
238
269
|
if (entry?.isIntersecting && currentTab.value === "popular" && popoverOpen.value) {
|
|
239
270
|
fetchTags("popular");
|
|
240
271
|
}
|
|
241
272
|
});
|
|
242
|
-
useIntersectionObserver(
|
|
273
|
+
useIntersectionObserver(recentSentinel, (entries) => {
|
|
243
274
|
const entry = entries[0];
|
|
244
|
-
if (entry?.isIntersecting && currentTab.value === "
|
|
245
|
-
fetchTags("
|
|
275
|
+
if (entry?.isIntersecting && currentTab.value === "recent" && popoverOpen.value) {
|
|
276
|
+
fetchTags("recent");
|
|
246
277
|
}
|
|
247
278
|
});
|
|
248
279
|
watch(currentTab, (newTab) => {
|
|
280
|
+
if (!popoverOpen.value) return;
|
|
249
281
|
if (newTab === "popular" && popularState.data.length === 0) {
|
|
250
282
|
fetchTags("popular");
|
|
251
|
-
} else if (newTab === "
|
|
252
|
-
fetchTags("
|
|
283
|
+
} else if (newTab === "recent" && recentState.data.length === 0) {
|
|
284
|
+
fetchTags("recent");
|
|
253
285
|
}
|
|
254
286
|
});
|
|
287
|
+
const resetLists = () => {
|
|
288
|
+
requestGeneration++;
|
|
289
|
+
for (const state of [popularState, recentState]) {
|
|
290
|
+
Object.assign(state, {
|
|
291
|
+
data: [],
|
|
292
|
+
page: 1,
|
|
293
|
+
hasMore: true,
|
|
294
|
+
loading: false,
|
|
295
|
+
error: ""
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
watch(
|
|
300
|
+
[sessionId, businessProfileId, () => props.profileScope, isOffice],
|
|
301
|
+
() => {
|
|
302
|
+
resetLists();
|
|
303
|
+
if (popoverOpen.value)
|
|
304
|
+
fetchTags(currentTab.value === "popular" ? "popular" : "recent");
|
|
305
|
+
}
|
|
306
|
+
);
|
|
255
307
|
const selectedTags = computed(() => modelValue.value);
|
|
256
308
|
const isLimitReached = computed(() => {
|
|
257
309
|
if (props.limit <= 0) return false;
|
|
@@ -286,7 +338,7 @@ const filterTags = (tags) => {
|
|
|
286
338
|
);
|
|
287
339
|
};
|
|
288
340
|
const filteredPopularTags = computed(() => filterTags(popularState.data));
|
|
289
|
-
const
|
|
341
|
+
const filteredRecentTags = computed(() => filterTags(recentState.data));
|
|
290
342
|
const onSelectTag = (tag) => {
|
|
291
343
|
if (isSelected(tag.value)) {
|
|
292
344
|
modelValue.value = modelValue.value.filter((t) => t.id !== tag.value);
|
|
@@ -318,7 +370,7 @@ const onAddCustomTag = () => {
|
|
|
318
370
|
return;
|
|
319
371
|
}
|
|
320
372
|
const newValue = trimmedQuery.toLowerCase().replace(/\s+/g, "-");
|
|
321
|
-
const existingTag = [...popularState.data, ...
|
|
373
|
+
const existingTag = [...popularState.data, ...recentState.data].find(
|
|
322
374
|
(tag) => tag.value === newValue || tag.label === trimmedQuery
|
|
323
375
|
);
|
|
324
376
|
if (existingTag) {
|
|
@@ -13,6 +13,7 @@ interface InputTagProps {
|
|
|
13
13
|
placeholder?: string;
|
|
14
14
|
state?: "user" | "admin";
|
|
15
15
|
ignore?: string[];
|
|
16
|
+
profileScope?: "personal" | "business";
|
|
16
17
|
}
|
|
17
18
|
type __VLS_Props = InputTagProps;
|
|
18
19
|
type __VLS_ModelProps = {
|
|
@@ -29,6 +30,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
29
30
|
placeholder: string;
|
|
30
31
|
limit: number;
|
|
31
32
|
ignore: string[];
|
|
33
|
+
profileScope: "personal" | "business";
|
|
32
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
33
35
|
declare const _default: typeof __VLS_export;
|
|
34
36
|
export default _default;
|
|
@@ -59,8 +59,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
59
59
|
id: string;
|
|
60
60
|
name: string;
|
|
61
61
|
required: boolean;
|
|
62
|
-
limit: number;
|
|
63
62
|
readonly: boolean;
|
|
63
|
+
limit: number;
|
|
64
64
|
disabledErrorMessage: boolean;
|
|
65
65
|
disabledBorder: boolean;
|
|
66
66
|
showCounter: boolean;
|
|
@@ -59,8 +59,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
59
59
|
id: string;
|
|
60
60
|
name: string;
|
|
61
61
|
required: boolean;
|
|
62
|
-
limit: number;
|
|
63
62
|
readonly: boolean;
|
|
63
|
+
limit: number;
|
|
64
64
|
disabledErrorMessage: boolean;
|
|
65
65
|
disabledBorder: boolean;
|
|
66
66
|
showCounter: boolean;
|
|
@@ -47,8 +47,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
47
47
|
fullHeight: boolean;
|
|
48
48
|
fullWidth: boolean;
|
|
49
49
|
rows: number;
|
|
50
|
-
limit: number;
|
|
51
50
|
readonly: boolean;
|
|
51
|
+
limit: number;
|
|
52
52
|
disabledErrorMessage: boolean;
|
|
53
53
|
disabledBorder: boolean;
|
|
54
54
|
showCounter: boolean;
|
|
@@ -47,8 +47,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
47
47
|
fullHeight: boolean;
|
|
48
48
|
fullWidth: boolean;
|
|
49
49
|
rows: number;
|
|
50
|
-
limit: number;
|
|
51
50
|
readonly: boolean;
|
|
51
|
+
limit: number;
|
|
52
52
|
disabledErrorMessage: boolean;
|
|
53
53
|
disabledBorder: boolean;
|
|
54
54
|
showCounter: boolean;
|
|
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
24
24
|
}) => any) | undefined;
|
|
25
25
|
}>, {
|
|
26
26
|
title: string;
|
|
27
|
-
disabledForgotPassword: boolean;
|
|
28
27
|
confirmText: string;
|
|
28
|
+
disabledForgotPassword: boolean;
|
|
29
29
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
30
|
declare const _default: typeof __VLS_export;
|
|
31
31
|
export default _default;
|
|
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
24
24
|
}) => any) | undefined;
|
|
25
25
|
}>, {
|
|
26
26
|
title: string;
|
|
27
|
-
disabledForgotPassword: boolean;
|
|
28
27
|
confirmText: string;
|
|
28
|
+
disabledForgotPassword: boolean;
|
|
29
29
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
30
|
declare const _default: typeof __VLS_export;
|
|
31
31
|
export default _default;
|
|
@@ -32,8 +32,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
32
32
|
}>, {
|
|
33
33
|
title: string;
|
|
34
34
|
mode: "login" | "secure";
|
|
35
|
-
disabledForgotPassword: boolean;
|
|
36
35
|
confirmText: string;
|
|
36
|
+
disabledForgotPassword: boolean;
|
|
37
37
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
38
|
declare const _default: typeof __VLS_export;
|
|
39
39
|
export default _default;
|
|
@@ -32,8 +32,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
32
32
|
}>, {
|
|
33
33
|
title: string;
|
|
34
34
|
mode: "login" | "secure";
|
|
35
|
-
disabledForgotPassword: boolean;
|
|
36
35
|
confirmText: string;
|
|
36
|
+
disabledForgotPassword: boolean;
|
|
37
37
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
38
|
declare const _default: typeof __VLS_export;
|
|
39
39
|
export default _default;
|