pukaad-ui-lib 1.460.0 → 1.462.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.
Files changed (33) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/card/card-mention/card-mention.d.vue.ts +2 -1
  3. package/dist/runtime/components/card/card-mention/card-mention.vue +3 -2
  4. package/dist/runtime/components/card/card-mention/card-mention.vue.d.ts +2 -1
  5. package/dist/runtime/components/chat/chat-message-bubble.d.vue.ts +3 -1
  6. package/dist/runtime/components/chat/chat-message-bubble.vue +16 -2
  7. package/dist/runtime/components/chat/chat-message-bubble.vue.d.ts +3 -1
  8. package/dist/runtime/components/chat/chat-room.d.vue.ts +6 -1
  9. package/dist/runtime/components/chat/chat-room.vue +20 -1
  10. package/dist/runtime/components/chat/chat-room.vue.d.ts +6 -1
  11. package/dist/runtime/components/draggable.d.vue.ts +26 -3
  12. package/dist/runtime/components/draggable.vue +67 -20
  13. package/dist/runtime/components/draggable.vue.d.ts +26 -3
  14. package/dist/runtime/components/input/input-chat.d.vue.ts +2 -0
  15. package/dist/runtime/components/input/input-chat.vue +7 -2
  16. package/dist/runtime/components/input/input-chat.vue.d.ts +2 -0
  17. package/dist/runtime/components/input/input-comment.d.vue.ts +1 -1
  18. package/dist/runtime/components/input/input-comment.vue.d.ts +1 -1
  19. package/dist/runtime/components/input/input-mention.vue +4 -20
  20. package/dist/runtime/components/modal/modal-chat-share.d.vue.ts +17 -0
  21. package/dist/runtime/components/modal/modal-chat-share.vue +214 -0
  22. package/dist/runtime/components/modal/modal-chat-share.vue.d.ts +17 -0
  23. package/dist/runtime/components/modal/modal-password-confirmed.d.vue.ts +1 -1
  24. package/dist/runtime/components/modal/modal-password-confirmed.vue.d.ts +1 -1
  25. package/dist/runtime/components/modal/modal-password-verify.d.vue.ts +1 -1
  26. package/dist/runtime/components/modal/modal-password-verify.vue.d.ts +1 -1
  27. package/dist/runtime/composables/store-chat.d.ts +83 -2
  28. package/dist/runtime/composables/store-chat.js +39 -5
  29. package/dist/runtime/composables/useChatSession.d.ts +82 -1
  30. package/dist/runtime/types/chat.d.ts +16 -1
  31. package/dist/runtime/utils/mention.d.ts +39 -0
  32. package/dist/runtime/utils/mention.js +52 -0
  33. package/package.json +1 -1
@@ -0,0 +1,214 @@
1
+ <template>
2
+ <Modal v-model="isOpen" title="เลือกข้อมูลที่จะส่ง" class="w-[426px]">
3
+ <div class="flex flex-col gap-[16px]">
4
+ <InputSearch name="chat_share_search" v-model="searchTerm" />
5
+ <ShadTabs v-model="currentTab" class="w-full gap-[8px]">
6
+ <ShadTabsList class="grid grid-cols-3 w-full">
7
+ <ShadTabsTrigger
8
+ v-for="tab in listTabs"
9
+ :key="tab.value"
10
+ :value="tab.value"
11
+ >
12
+ {{ tab.label }}
13
+ </ShadTabsTrigger>
14
+ </ShadTabsList>
15
+ <ShadTabsContent
16
+ v-for="tab in listTabs"
17
+ :key="tab.value"
18
+ :ref="(el) => setPanel(tab.value, el)"
19
+ :value="tab.value"
20
+ class="flex-none h-[400px] overflow-y-auto"
21
+ >
22
+ <div
23
+ v-for="item in states[tab.value].items"
24
+ :key="item.id"
25
+ class="flex gap-[8px] items-center py-[6px] px-[8px]"
26
+ >
27
+ <CardMention :item="item" />
28
+ <Button
29
+ color="primary"
30
+ size="sm"
31
+ class="shrink-0"
32
+ @click="emit('send', item)"
33
+ >
34
+ ส่ง
35
+ </Button>
36
+ </div>
37
+ <div
38
+ v-if="
39
+ !states[tab.value].items.length && !states[tab.value].loading && !hasMore(tab.value)
40
+ "
41
+ class="h-full flex items-center justify-center font-body-medium text-gray"
42
+ >
43
+ ไม่พบข้อมูล
44
+ </div>
45
+ <template v-if="states[tab.value].loading">
46
+ <div
47
+ v-for="n in states[tab.value].items.length ? 2 : 5"
48
+ :key="`skeleton-${n}`"
49
+ class="py-[6px] px-[8px]"
50
+ >
51
+ <CardMentionSkeleton :mention-type="tab.value === 'post' ? 'post' : 'blog'" />
52
+ </div>
53
+ </template>
54
+ </ShadTabsContent>
55
+ </ShadTabs>
56
+ </div>
57
+ </Modal>
58
+ </template>
59
+
60
+ <script setup>
61
+ import { reactive, ref, shallowReactive, watch } from "vue";
62
+ import { useDebounceFn, useInfiniteScroll } from "@vueuse/core";
63
+ import { useApi } from "../../composables/useApi";
64
+ import CardMention, {
65
+ } from "../card/card-mention/card-mention.vue";
66
+ import CardMentionSkeleton from "../card/card-mention/card-mention-skeleton.vue";
67
+ import {
68
+ mapBlog,
69
+ mapPlace,
70
+ mapPost
71
+ } from "../../utils/mention";
72
+ const props = defineProps({
73
+ profileIds: { type: Array, required: true }
74
+ });
75
+ const emit = defineEmits(["send"]);
76
+ const isOpen = defineModel({ type: Boolean, ...{ default: false } });
77
+ const PAGE_SIZE = 10;
78
+ const listTabs = [
79
+ { value: "post", label: "\u0E40\u0E19\u0E37\u0E49\u0E2D\u0E2B\u0E32" },
80
+ { value: "place", label: "\u0E2A\u0E16\u0E32\u0E19\u0E17\u0E35\u0E48" },
81
+ { value: "blog", label: "\u0E1A\u0E25\u0E47\u0E2D\u0E01" }
82
+ ];
83
+ const api = useApi({ includeProvince: false });
84
+ const currentTab = ref("post");
85
+ const searchTerm = ref("");
86
+ const createState = () => ({
87
+ items: [],
88
+ sources: Object.fromEntries(
89
+ props.profileIds.map((id) => [id, { page: 1, hasMore: true }])
90
+ ),
91
+ loading: false
92
+ });
93
+ const states = reactive({
94
+ post: createState(),
95
+ place: createState(),
96
+ blog: createState()
97
+ });
98
+ const generations = { post: 0, place: 0, blog: 0 };
99
+ const hasMore = (tab) => Object.values(states[tab].sources).some((source) => source.hasMore);
100
+ const requestPage = async (tab, profileId, page, q) => {
101
+ if (tab === "post") {
102
+ const res2 = await api(
103
+ `/profiles/${profileId}/contents`,
104
+ {
105
+ query: {
106
+ page,
107
+ page_size: PAGE_SIZE,
108
+ status: "PUBLISHED",
109
+ query: q || void 0
110
+ }
111
+ }
112
+ );
113
+ return {
114
+ items: (res2.data ?? []).map(mapPost),
115
+ totalPage: res2.meta?.total_page ?? 0
116
+ };
117
+ }
118
+ if (tab === "blog") {
119
+ const res2 = await api(
120
+ `/profiles/${profileId}/blogs`,
121
+ {
122
+ query: {
123
+ page,
124
+ page_size: PAGE_SIZE,
125
+ status: "PUBLISHED",
126
+ query: q || void 0
127
+ }
128
+ }
129
+ );
130
+ return {
131
+ items: (res2.data ?? []).map(mapBlog),
132
+ totalPage: res2.meta?.total_page ?? 0
133
+ };
134
+ }
135
+ const res = await api(`/profiles/${profileId}/places`, {
136
+ query: { page, page_size: PAGE_SIZE, q: q || void 0 }
137
+ });
138
+ return {
139
+ items: (res.data?.items ?? []).map(mapPlace),
140
+ totalPage: res.data?.pagination?.total_page ?? 0
141
+ };
142
+ };
143
+ const loadMore = async (tab) => {
144
+ const state = states[tab];
145
+ if (state.loading || !hasMore(tab)) return;
146
+ state.loading = true;
147
+ const generation = generations[tab];
148
+ const q = searchTerm.value.trim();
149
+ const pending = Object.entries(state.sources).filter(
150
+ ([, source]) => source.hasMore
151
+ );
152
+ const results = await Promise.allSettled(
153
+ pending.map(
154
+ ([profileId, source]) => requestPage(tab, profileId, source.page, q)
155
+ )
156
+ );
157
+ if (generation !== generations[tab]) return;
158
+ const seen = new Set(state.items.map((item) => item.id));
159
+ const merged = [...state.items];
160
+ results.forEach((result, index) => {
161
+ const source = state.sources[pending[index][0]];
162
+ if (result.status === "rejected") {
163
+ source.hasMore = false;
164
+ return;
165
+ }
166
+ source.page++;
167
+ source.hasMore = source.page <= result.value.totalPage;
168
+ for (const item of result.value.items) {
169
+ if (seen.has(item.id)) continue;
170
+ seen.add(item.id);
171
+ merged.push(item);
172
+ }
173
+ });
174
+ state.items = merged;
175
+ state.loading = false;
176
+ };
177
+ const panels = shallowReactive({
178
+ post: null,
179
+ place: null,
180
+ blog: null
181
+ });
182
+ const setPanel = (tab, ref2) => {
183
+ const el = ref2?.$el ?? ref2;
184
+ const panel = el instanceof HTMLElement ? el : null;
185
+ if (panels[tab] !== panel) panels[tab] = panel;
186
+ };
187
+ useInfiniteScroll(
188
+ () => panels[currentTab.value],
189
+ () => loadMore(currentTab.value),
190
+ {
191
+ distance: 80,
192
+ canLoadMore: () => hasMore(currentTab.value)
193
+ }
194
+ );
195
+ const resetAll = () => {
196
+ for (const { value: tab } of listTabs) {
197
+ generations[tab]++;
198
+ Object.assign(states[tab], createState());
199
+ const panel = panels[tab];
200
+ if (panel) panel.scrollTop = 0;
201
+ }
202
+ void loadMore(currentTab.value);
203
+ };
204
+ watch(searchTerm, useDebounceFn(resetAll, 300));
205
+ watch(currentTab, (tab) => {
206
+ if (!states[tab].items.length) void loadMore(tab);
207
+ });
208
+ watch(
209
+ [isOpen, () => props.profileIds.join(",")],
210
+ ([open]) => {
211
+ if (open) resetAll();
212
+ }
213
+ );
214
+ </script>
@@ -0,0 +1,17 @@
1
+ import { type CardMentionItem } from "../card/card-mention/card-mention.vue.js";
2
+ type __VLS_Props = {
3
+ profileIds: string[];
4
+ };
5
+ type __VLS_ModelProps = {
6
+ modelValue?: boolean;
7
+ };
8
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ "update:modelValue": (value: boolean) => any;
11
+ send: (item: CardMentionItem) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
13
+ "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
14
+ onSend?: ((item: CardMentionItem) => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ declare const _default: typeof __VLS_export;
17
+ 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;
@@ -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;
@@ -1,4 +1,4 @@
1
- import type { ChatConversation, ChatMessage } from "../types/chat.js";
1
+ import type { ChatConversation, ChatMessage, ChatSharePreview } from "../types/chat.js";
2
2
  export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat", Pick<{
3
3
  conversations: import("vue").Ref<{
4
4
  id: string;
@@ -31,6 +31,19 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
31
31
  is_deleted: boolean;
32
32
  thumbnail_url: string | null;
33
33
  } | null | undefined;
34
+ share?: {
35
+ type: import("../types/chat.js").ChatShareType;
36
+ id: string;
37
+ title: string;
38
+ content_type?: string | undefined;
39
+ cover_color?: string | undefined;
40
+ image?: string | undefined;
41
+ thumbnail?: string | undefined;
42
+ place_name?: string | undefined;
43
+ address?: string | undefined;
44
+ author_name?: string | undefined;
45
+ author_avatar?: string | null | undefined;
46
+ } | null | undefined;
34
47
  is_deleted: boolean;
35
48
  created_at: string;
36
49
  status?: import("../types/chat.js").ChatMessageStatus | undefined;
@@ -74,6 +87,19 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
74
87
  is_deleted: boolean;
75
88
  thumbnail_url: string | null;
76
89
  } | null | undefined;
90
+ share?: {
91
+ type: import("../types/chat.js").ChatShareType;
92
+ id: string;
93
+ title: string;
94
+ content_type?: string | undefined;
95
+ cover_color?: string | undefined;
96
+ image?: string | undefined;
97
+ thumbnail?: string | undefined;
98
+ place_name?: string | undefined;
99
+ address?: string | undefined;
100
+ author_name?: string | undefined;
101
+ author_avatar?: string | null | undefined;
102
+ } | null | undefined;
77
103
  is_deleted: boolean;
78
104
  created_at: string;
79
105
  status?: import("../types/chat.js").ChatMessageStatus | undefined;
@@ -107,6 +133,7 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
107
133
  fetchOlderMessages: (conversationId: string) => Promise<void>;
108
134
  sendMessage: (conversationId: string, body: string, replyTo?: ChatMessage) => Promise<void>;
109
135
  sendMedia: (conversationId: string, files: File[], replyTo?: ChatMessage) => Promise<void>;
136
+ sendShare: (conversationId: string, share: ChatSharePreview) => Promise<void>;
110
137
  retryMessage: (conversationId: string, clientId: string) => Promise<void>;
111
138
  markRead: (conversationId: string, messageId: string) => Promise<void>;
112
139
  isReadByPeer: (conversationId: string, messageId: string) => boolean;
@@ -153,6 +180,19 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
153
180
  is_deleted: boolean;
154
181
  thumbnail_url: string | null;
155
182
  } | null | undefined;
183
+ share?: {
184
+ type: import("../types/chat.js").ChatShareType;
185
+ id: string;
186
+ title: string;
187
+ content_type?: string | undefined;
188
+ cover_color?: string | undefined;
189
+ image?: string | undefined;
190
+ thumbnail?: string | undefined;
191
+ place_name?: string | undefined;
192
+ address?: string | undefined;
193
+ author_name?: string | undefined;
194
+ author_avatar?: string | null | undefined;
195
+ } | null | undefined;
156
196
  is_deleted: boolean;
157
197
  created_at: string;
158
198
  status?: import("../types/chat.js").ChatMessageStatus | undefined;
@@ -196,6 +236,19 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
196
236
  is_deleted: boolean;
197
237
  thumbnail_url: string | null;
198
238
  } | null | undefined;
239
+ share?: {
240
+ type: import("../types/chat.js").ChatShareType;
241
+ id: string;
242
+ title: string;
243
+ content_type?: string | undefined;
244
+ cover_color?: string | undefined;
245
+ image?: string | undefined;
246
+ thumbnail?: string | undefined;
247
+ place_name?: string | undefined;
248
+ address?: string | undefined;
249
+ author_name?: string | undefined;
250
+ author_avatar?: string | null | undefined;
251
+ } | null | undefined;
199
252
  is_deleted: boolean;
200
253
  created_at: string;
201
254
  status?: import("../types/chat.js").ChatMessageStatus | undefined;
@@ -229,6 +282,7 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
229
282
  fetchOlderMessages: (conversationId: string) => Promise<void>;
230
283
  sendMessage: (conversationId: string, body: string, replyTo?: ChatMessage) => Promise<void>;
231
284
  sendMedia: (conversationId: string, files: File[], replyTo?: ChatMessage) => Promise<void>;
285
+ sendShare: (conversationId: string, share: ChatSharePreview) => Promise<void>;
232
286
  retryMessage: (conversationId: string, clientId: string) => Promise<void>;
233
287
  markRead: (conversationId: string, messageId: string) => Promise<void>;
234
288
  isReadByPeer: (conversationId: string, messageId: string) => boolean;
@@ -275,6 +329,19 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
275
329
  is_deleted: boolean;
276
330
  thumbnail_url: string | null;
277
331
  } | null | undefined;
332
+ share?: {
333
+ type: import("../types/chat.js").ChatShareType;
334
+ id: string;
335
+ title: string;
336
+ content_type?: string | undefined;
337
+ cover_color?: string | undefined;
338
+ image?: string | undefined;
339
+ thumbnail?: string | undefined;
340
+ place_name?: string | undefined;
341
+ address?: string | undefined;
342
+ author_name?: string | undefined;
343
+ author_avatar?: string | null | undefined;
344
+ } | null | undefined;
278
345
  is_deleted: boolean;
279
346
  created_at: string;
280
347
  status?: import("../types/chat.js").ChatMessageStatus | undefined;
@@ -318,6 +385,19 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
318
385
  is_deleted: boolean;
319
386
  thumbnail_url: string | null;
320
387
  } | null | undefined;
388
+ share?: {
389
+ type: import("../types/chat.js").ChatShareType;
390
+ id: string;
391
+ title: string;
392
+ content_type?: string | undefined;
393
+ cover_color?: string | undefined;
394
+ image?: string | undefined;
395
+ thumbnail?: string | undefined;
396
+ place_name?: string | undefined;
397
+ address?: string | undefined;
398
+ author_name?: string | undefined;
399
+ author_avatar?: string | null | undefined;
400
+ } | null | undefined;
321
401
  is_deleted: boolean;
322
402
  created_at: string;
323
403
  status?: import("../types/chat.js").ChatMessageStatus | undefined;
@@ -351,6 +431,7 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
351
431
  fetchOlderMessages: (conversationId: string) => Promise<void>;
352
432
  sendMessage: (conversationId: string, body: string, replyTo?: ChatMessage) => Promise<void>;
353
433
  sendMedia: (conversationId: string, files: File[], replyTo?: ChatMessage) => Promise<void>;
434
+ sendShare: (conversationId: string, share: ChatSharePreview) => Promise<void>;
354
435
  retryMessage: (conversationId: string, clientId: string) => Promise<void>;
355
436
  markRead: (conversationId: string, messageId: string) => Promise<void>;
356
437
  isReadByPeer: (conversationId: string, messageId: string) => boolean;
@@ -365,4 +446,4 @@ export declare const useChatStore: import("pinia").StoreDefinition<"pukaad-chat"
365
446
  startRealtime: () => void;
366
447
  stopRealtime: () => void;
367
448
  resync: (activeConversationId?: string) => Promise<void>;
368
- }, "reset" | "markRead" | "setUnreadFirst" | "setMyProfileId" | "fetchConversations" | "fetchConversation" | "fetchLatestMessages" | "fetchOlderMessages" | "sendMessage" | "sendMedia" | "retryMessage" | "isReadByPeer" | "unreadCountOf" | "startUnreadBadge" | "stopUnreadBadge" | "previewOf" | "sendTyping" | "isPeerTyping" | "deleteMessage" | "startRealtime" | "stopRealtime" | "resync">>;
449
+ }, "reset" | "markRead" | "setUnreadFirst" | "setMyProfileId" | "fetchConversations" | "fetchConversation" | "fetchLatestMessages" | "fetchOlderMessages" | "sendMessage" | "sendMedia" | "sendShare" | "retryMessage" | "isReadByPeer" | "unreadCountOf" | "startUnreadBadge" | "stopUnreadBadge" | "previewOf" | "sendTyping" | "isPeerTyping" | "deleteMessage" | "startRealtime" | "stopRealtime" | "resync">>;
@@ -117,10 +117,15 @@ export const useChatStore = defineStore("pukaad-chat", () => {
117
117
  const last = myLastRead[conversationId];
118
118
  return !last || BigInt(msg.id) > BigInt(last);
119
119
  };
120
+ const shareLabels = {
121
+ post: "\u0E2A\u0E48\u0E07\u0E40\u0E19\u0E37\u0E49\u0E2D\u0E2B\u0E32",
122
+ place: "\u0E2A\u0E48\u0E07\u0E2A\u0E16\u0E32\u0E19\u0E17\u0E35\u0E48",
123
+ blog: "\u0E2A\u0E48\u0E07\u0E1A\u0E25\u0E47\u0E2D\u0E01"
124
+ };
120
125
  const previewOf = (conversation) => {
121
126
  const msg = conversation.last_message;
122
127
  if (!msg) return "";
123
- const text = msg.is_deleted ? "\u0E02\u0E49\u0E2D\u0E04\u0E27\u0E32\u0E21\u0E16\u0E39\u0E01\u0E25\u0E1A\u0E41\u0E25\u0E49\u0E27" : msg.kind === "image" ? "\u0E2A\u0E48\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E" : msg.kind === "video" ? "\u0E2A\u0E48\u0E07\u0E27\u0E34\u0E14\u0E35\u0E42\u0E2D" : msg.body.replace(/\s+/g, " ");
128
+ const text = msg.is_deleted ? "\u0E02\u0E49\u0E2D\u0E04\u0E27\u0E32\u0E21\u0E16\u0E39\u0E01\u0E25\u0E1A\u0E41\u0E25\u0E49\u0E27" : msg.kind === "image" ? "\u0E2A\u0E48\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E" : msg.kind === "video" ? "\u0E2A\u0E48\u0E07\u0E27\u0E34\u0E14\u0E35\u0E42\u0E2D" : msg.kind === "share" ? shareLabels[msg.share?.type ?? "post"] : msg.body.replace(/\s+/g, " ");
124
129
  return msg.sender_profile_id === myProfileId.value ? `\u0E04\u0E38\u0E13: ${text}` : text;
125
130
  };
126
131
  const unreadCountOf = (conversation) => {
@@ -239,23 +244,29 @@ export const useChatStore = defineStore("pukaad-chat", () => {
239
244
  };
240
245
  const toReplyPreview = (original) => {
241
246
  const first = original.attachments[0];
247
+ const share = original.share;
242
248
  return {
243
249
  id: original.id,
244
250
  sender_profile_id: original.sender_profile_id,
245
251
  kind: original.kind,
246
- body: [...original.body].slice(0, 100).join(""),
252
+ body: [...share?.title ?? original.body].slice(0, 100).join(""),
247
253
  is_deleted: false,
248
- thumbnail_url: first ? first.thumbnail_url ?? (first.kind === "image" ? first.url : null) : null
254
+ thumbnail_url: share ? share.image || share.thumbnail || null : first ? first.thumbnail_url ?? (first.kind === "image" ? first.url : null) : null
249
255
  };
250
256
  };
251
257
  const replyIdOf = (conversationId, clientId) => messages[conversationId]?.find((m) => m.client_id === clientId)?.reply_to?.id;
252
- const postMessage = async (conversationId, clientId, body) => {
258
+ const postMessage = async (conversationId, clientId, body, share) => {
253
259
  try {
254
260
  const res = await api(
255
261
  `/chat/conversations/${conversationId}/messages`,
256
262
  {
257
263
  method: "POST",
258
- body: { client_id: clientId, body, reply_to_message_id: replyIdOf(conversationId, clientId) },
264
+ body: {
265
+ client_id: clientId,
266
+ body,
267
+ share: share ? { type: share.type, id: share.id } : void 0,
268
+ reply_to_message_id: replyIdOf(conversationId, clientId)
269
+ },
259
270
  timeout: REQUEST_TIMEOUT
260
271
  }
261
272
  );
@@ -286,6 +297,23 @@ export const useChatStore = defineStore("pukaad-chat", () => {
286
297
  messages[conversationId] = [...messages[conversationId] ?? [], local];
287
298
  await postMessage(conversationId, clientId, body);
288
299
  };
300
+ const sendShare = async (conversationId, share) => {
301
+ const clientId = crypto.randomUUID();
302
+ const local = {
303
+ id: `local-${clientId}`,
304
+ client_id: clientId,
305
+ sender_profile_id: myProfileId.value,
306
+ kind: "share",
307
+ body: "",
308
+ attachments: [],
309
+ share,
310
+ is_deleted: false,
311
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
312
+ status: "sending"
313
+ };
314
+ messages[conversationId] = [...messages[conversationId] ?? [], local];
315
+ await postMessage(conversationId, clientId, "", share);
316
+ };
289
317
  const pendingFiles = /* @__PURE__ */ new Map();
290
318
  const setProgress = (conversationId, clientId, progress) => {
291
319
  const msg = messages[conversationId]?.find((m) => m.client_id === clientId);
@@ -389,6 +417,10 @@ export const useChatStore = defineStore("pukaad-chat", () => {
389
417
  if (!msg || msg.status !== "failed" || msg.failure?.retryable === false) return;
390
418
  msg.status = "sending";
391
419
  msg.failure = void 0;
420
+ if (msg.kind === "share") {
421
+ await postMessage(conversationId, clientId, "", msg.share);
422
+ return;
423
+ }
392
424
  if (msg.kind !== "text") {
393
425
  msg.progress = 0;
394
426
  await postMedia(conversationId, clientId);
@@ -403,6 +435,7 @@ export const useChatStore = defineStore("pukaad-chat", () => {
403
435
  msg.body = "";
404
436
  msg.attachments = [];
405
437
  msg.reply_to = null;
438
+ msg.share = null;
406
439
  }
407
440
  for (const m of messages[conversationId] ?? []) {
408
441
  if (m.reply_to?.id === messageId) {
@@ -564,6 +597,7 @@ export const useChatStore = defineStore("pukaad-chat", () => {
564
597
  fetchOlderMessages,
565
598
  sendMessage,
566
599
  sendMedia,
600
+ sendShare,
567
601
  retryMessage,
568
602
  markRead,
569
603
  isReadByPeer,