contactstudiocstools 1.0.224

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 (91) hide show
  1. package/README.md +94 -0
  2. package/dist/module.cjs +5 -0
  3. package/dist/module.d.ts +7 -0
  4. package/dist/module.json +5 -0
  5. package/dist/module.mjs +72 -0
  6. package/dist/runtime/components/Atom.Alert.vue +46 -0
  7. package/dist/runtime/components/Atom.Auth.vue +37 -0
  8. package/dist/runtime/components/Atom.BannerChatEmpty.vue +18 -0
  9. package/dist/runtime/components/Atom.BannerPage404.vue +28 -0
  10. package/dist/runtime/components/Atom.BannerPageUnauthorized.vue +18 -0
  11. package/dist/runtime/components/Atom.Breadcrumb.vue +26 -0
  12. package/dist/runtime/components/Atom.ChatContact.vue +136 -0
  13. package/dist/runtime/components/Atom.ChatContactSchedule.vue +87 -0
  14. package/dist/runtime/components/Atom.ChatMessageFooter.vue +25 -0
  15. package/dist/runtime/components/Atom.DarkMode.vue +67 -0
  16. package/dist/runtime/components/Atom.DraggableWindow.vue +102 -0
  17. package/dist/runtime/components/Atom.Dropdown.vue +9 -0
  18. package/dist/runtime/components/Atom.DropdownSearchable.vue +25 -0
  19. package/dist/runtime/components/Atom.Fetch.vue +46 -0
  20. package/dist/runtime/components/Atom.Field.vue +43 -0
  21. package/dist/runtime/components/Atom.FieldDate.vue +19 -0
  22. package/dist/runtime/components/Atom.FieldNumber.vue +19 -0
  23. package/dist/runtime/components/Atom.FieldPhone.vue +92 -0
  24. package/dist/runtime/components/Atom.FieldSelect.vue +28 -0
  25. package/dist/runtime/components/Atom.FieldSelectMultiple.vue +49 -0
  26. package/dist/runtime/components/Atom.FieldText.vue +19 -0
  27. package/dist/runtime/components/Atom.FieldTextarea.vue +41 -0
  28. package/dist/runtime/components/Atom.Loading.vue +80 -0
  29. package/dist/runtime/components/Atom.Notification.vue +48 -0
  30. package/dist/runtime/components/Atom.Ringtone.vue +23 -0
  31. package/dist/runtime/components/Atom.SelectTreeField.vue +49 -0
  32. package/dist/runtime/components/Atom.Snapshot.vue +33 -0
  33. package/dist/runtime/components/Atom.Tabs.vue +60 -0
  34. package/dist/runtime/components/Molecule.ChatMessageFile.vue +102 -0
  35. package/dist/runtime/components/Molecule.ChatMessageOption.vue +85 -0
  36. package/dist/runtime/components/Molecule.ChatMessageText.vue +36 -0
  37. package/dist/runtime/components/Molecule.ClientHistory.vue +62 -0
  38. package/dist/runtime/components/Molecule.DropdownDDI.vue +333 -0
  39. package/dist/runtime/components/Molecule.FieldGroup.vue +73 -0
  40. package/dist/runtime/components/Molecule.FieldSelectMultiple.vue +19 -0
  41. package/dist/runtime/components/Molecule.File.vue +84 -0
  42. package/dist/runtime/components/Molecule.SelectTreeSearchable.vue +126 -0
  43. package/dist/runtime/components/Molecule.Status.vue +154 -0
  44. package/dist/runtime/components/Molecule.TimeDaily.vue +9 -0
  45. package/dist/runtime/components/Organism.Attachments.vue +139 -0
  46. package/dist/runtime/components/Organism.ChatMessages.vue +31 -0
  47. package/dist/runtime/components/Organism.ChatRoom.vue +342 -0
  48. package/dist/runtime/components/Organism.ChatSchedule.vue +110 -0
  49. package/dist/runtime/components/Organism.ClientHistoryTable.vue +85 -0
  50. package/dist/runtime/components/Organism.ClientHistoryTimeline.vue +77 -0
  51. package/dist/runtime/components/Organism.FAQ.vue +88 -0
  52. package/dist/runtime/components/Organism.Form.vue +67 -0
  53. package/dist/runtime/components/Organism.FormMailing.vue +112 -0
  54. package/dist/runtime/components/Organism.HeaderMain.vue +79 -0
  55. package/dist/runtime/components/Organism.Manifestation.vue +146 -0
  56. package/dist/runtime/components/Organism.Nav.vue +27 -0
  57. package/dist/runtime/components/Organism.NavMain.vue +187 -0
  58. package/dist/runtime/components/Organism.PageContainer.vue +22 -0
  59. package/dist/runtime/components/Organism.Schedule.vue +170 -0
  60. package/dist/runtime/components/Organism.Tabulation.vue +237 -0
  61. package/dist/runtime/components/types/dto.d.ts +16 -0
  62. package/dist/runtime/components/types/dto.mjs +236 -0
  63. package/dist/runtime/components/types/helpers.d.ts +39 -0
  64. package/dist/runtime/components/types/helpers.mjs +295 -0
  65. package/dist/runtime/components/types/index.d.ts +4 -0
  66. package/dist/runtime/components/types/index.mjs +4 -0
  67. package/dist/runtime/components/types/types.d.ts +198 -0
  68. package/dist/runtime/components/types/types.mjs +35 -0
  69. package/dist/runtime/index.css +1 -0
  70. package/dist/runtime/plugins/clickOutside.d.ts +2 -0
  71. package/dist/runtime/plugins/clickOutside.mjs +16 -0
  72. package/dist/runtime/plugins/emitter.d.ts +2 -0
  73. package/dist/runtime/plugins/emitter.mjs +17 -0
  74. package/dist/runtime/public/192x192.png +0 -0
  75. package/dist/runtime/public/404.svg +1 -0
  76. package/dist/runtime/public/512x512.png +0 -0
  77. package/dist/runtime/public/chat.svg +138 -0
  78. package/dist/runtime/public/chatbg.png +0 -0
  79. package/dist/runtime/public/dev-sw.d.ts +0 -0
  80. package/dist/runtime/public/dev-sw.mjs +0 -0
  81. package/dist/runtime/public/empty.svg +1 -0
  82. package/dist/runtime/public/loading.svg +1 -0
  83. package/dist/runtime/public/messages.svg +1 -0
  84. package/dist/runtime/public/privacy.svg +1 -0
  85. package/dist/runtime/public/ringtone.mp3 +0 -0
  86. package/dist/runtime/public/security.svg +188 -0
  87. package/dist/runtime/public/snapshot.d.ts +15 -0
  88. package/dist/runtime/public/snapshot.mjs +77 -0
  89. package/dist/runtime/public/unauthorized.svg +1 -0
  90. package/dist/types.d.ts +10 -0
  91. package/package.json +50 -0
@@ -0,0 +1,154 @@
1
+ <template>
2
+ <div
3
+ :disabled="disabled"
4
+ class="w-4 bg-slate-700 rounded-full"
5
+ >
6
+ <!-- current -->
7
+ <div
8
+ :class="getStatusColor(current)"
9
+ class="flex justify-end rounded-full dark"
10
+ >
11
+ <p class="text-xs mr-2 whitespace-nowrap max-sm:hidden">
12
+ <span
13
+ :class="getStatusColor(current)"
14
+ class="badge bg-transparent badge-outline font-bold"
15
+ >
16
+ {{ new Date(milliseconds).toISOString().slice(11, 19) }}
17
+ </span>
18
+ {{ getStatusLabel(current) }}
19
+ </p>
20
+ <p
21
+ :dropdown-trigger="dropdownID"
22
+ class="sizes cursor-pointer flex justify-center items-center"
23
+ >
24
+ <i
25
+ class="bi-caret-down-fill !text-[11px] mt-0.5 text-white pointer-events-none"
26
+ />
27
+ </p>
28
+ </div>
29
+
30
+ <div class="my-2" />
31
+
32
+ <!-- next -->
33
+ <div class="flex justify-end">
34
+ <span
35
+ :class="getStatusColor(next)"
36
+ class="text-xs mr-2 !bg-transparent max-sm:hidden"
37
+ v-text="getStatusLabel(next)"
38
+ />
39
+
40
+ <p class="sizes rounded-full flex justify-center items-center bottom-0">
41
+ <span
42
+ :class="getStatusColor(next)"
43
+ class="w-2 h-2 rounded-full"
44
+ />
45
+ </p>
46
+ </div>
47
+
48
+ <!-- dropdown -->
49
+ <AtomDropdown
50
+ :dropdown="dropdownID"
51
+ class="!p-1"
52
+ >
53
+ <li
54
+ :class="getStatusColor(current)"
55
+ class="text-xs !text-[11px] !bg-transparent !p-2 !py-0.5 sm:hidden"
56
+ >
57
+ <i class="bi-caret-down-fill text-[10px]" />
58
+ {{ getStatusLabel(current) }}
59
+ </li>
60
+
61
+ <li
62
+ :class="getStatusColor(next)"
63
+ class="text-xs !text-[11px] !bg-transparent !p-2 !py-0.5 sm:hidden"
64
+ >
65
+ <i class="bi-circle-fill text-[6px] px-0.5" />
66
+ {{ getStatusLabel(next) }}
67
+ </li>
68
+
69
+ <div class="divider sm:hidden" />
70
+
71
+ <li
72
+ v-for="(item, i) in pauses"
73
+ :key="i"
74
+ class="item text-xs !p-2"
75
+ @click="set(item)"
76
+ v-text="item.label"
77
+ />
78
+ </AtomDropdown>
79
+ </div>
80
+ </template>
81
+
82
+ <script setup lang="ts">
83
+ import { ref, computed, watch, onMounted } from "vue";
84
+ import { randomID } from "./types";
85
+ import { EUserStatus, IPause, IPauses } from "./types";
86
+
87
+ // props
88
+ interface IProps {
89
+ startedAt: Date;
90
+ current: EUserStatus;
91
+ next: EUserStatus;
92
+ pauses: IPauses;
93
+ pause?: IPause;
94
+ set: Function;
95
+ }
96
+ const props = defineProps<IProps>();
97
+
98
+ // mounted
99
+ onMounted(() => {
100
+ resetSeconds();
101
+ });
102
+
103
+ // computed
104
+ const getStatusLabel = computed<Function>(() => (status: EUserStatus) => {
105
+ if (status !== EUserStatus.pause) return status;
106
+
107
+ return props.pause?.label;
108
+ });
109
+ const getStatusColor = computed<Function>(() => (status: EUserStatus) => {
110
+ if (status === EUserStatus.active) return "bg-active text-active";
111
+ if (status === EUserStatus.attendance) return "bg-attendance text-attendance";
112
+ if (status === EUserStatus.available) return "bg-available text-available";
113
+
114
+ return "bg-pause text-pause";
115
+ });
116
+
117
+ // data
118
+ let counter: any;
119
+ const dropdownID = ref<string>(randomID());
120
+ const disabled = ref<boolean>(false);
121
+ const milliseconds = ref<number>(0);
122
+
123
+ // methods
124
+ async function set(pause: IPause): Promise<void> {
125
+ disabled.value = true;
126
+ await props.set(pause);
127
+ disabled.value = false;
128
+ }
129
+ function resetSeconds(): void {
130
+ milliseconds.value = 0;
131
+
132
+ window.clearInterval(counter);
133
+ counter = window.setInterval(() => {
134
+ milliseconds.value = Math.abs(props.startedAt.getTime() - new Date().getTime());
135
+ }, 1000);
136
+ }
137
+
138
+ // watch
139
+ watch(
140
+ () => props.current,
141
+ () => {
142
+ resetSeconds();
143
+ }
144
+ );
145
+ </script>
146
+
147
+ <style scoped>
148
+ .sizes {
149
+ min-width: 1rem;
150
+ max-width: 1rem;
151
+ min-height: 1rem;
152
+ max-height: 1rem;
153
+ }
154
+ </style>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <div>teste</div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+
7
+ </script>
8
+
9
+ <style scoped></style>
@@ -0,0 +1,139 @@
1
+ <template>
2
+ <div class="flex flex-wrap gap-2">
3
+ <MoleculeFile
4
+ v-for="({ file, ID }, i) in attachments"
5
+ :key="i"
6
+ :file="file.file"
7
+ :hints="file.hints"
8
+ :status="file.status"
9
+ class="flex-1"
10
+ actions
11
+ >
12
+ <li
13
+ class="item !p-2 text-error"
14
+ @click="deleteAttachment(ID)"
15
+ >
16
+ <i class="bi-trash" /> Excluir
17
+ </li>
18
+ </MoleculeFile>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { reactive, watch } from "vue";
24
+ import {
25
+ EFieldStatus,
26
+ IAttachment,
27
+ IAttachments,
28
+ logger,
29
+ randomID,
30
+ } from "./types";
31
+ import { onMounted } from "vue";
32
+
33
+ // props
34
+ interface IProps {
35
+ inputFileRef?: HTMLInputElement;
36
+ init: () => Promise<IAttachments>;
37
+ delete: (attachment: IAttachment) => Promise<void>;
38
+ upload: (file: File) => Promise<any>;
39
+ }
40
+ const props = defineProps<IProps>();
41
+
42
+ // mounted
43
+ onMounted(async () => {
44
+ const uploadedAttachments = await props.init();
45
+
46
+ if (!uploadedAttachments) return;
47
+
48
+ attachments.push(...uploadedAttachments);
49
+ });
50
+
51
+ // data
52
+ const attachments = reactive<IAttachments>([]);
53
+
54
+ // methods
55
+ function setOnChangeEvent(): void {
56
+ if (!props.inputFileRef) return;
57
+
58
+ props.inputFileRef.onchange = getAttachmentsOnChange;
59
+ }
60
+
61
+ function getAttachmentsOnChange(e: Event): void {
62
+ const inputFiles = (e.target as HTMLInputElement).files;
63
+
64
+ if (!inputFiles) return;
65
+
66
+ for (const file of inputFiles) {
67
+ const ID = randomID();
68
+
69
+ attachments.push({
70
+ ID,
71
+ file: { file },
72
+ });
73
+
74
+ addAttachment({ ID, file });
75
+ }
76
+
77
+ resetInputFile();
78
+ }
79
+
80
+ async function addAttachment({ ID, file }: any): Promise<void> {
81
+ const index = getAttachmentIndexByID(ID);
82
+
83
+ setLoadingStatusOnFile(index);
84
+
85
+ if (!props.upload) return;
86
+
87
+ try {
88
+ const primitive = await props.upload(file);
89
+ setPrimitiveOnAttachment({ index, primitive });
90
+ setSuccessStatusOnFile(index);
91
+ } catch (e: any) {
92
+ setErrorStatusOnFile(index);
93
+ logger.error("OrganismAttachments:AddAttachment", { e });
94
+ }
95
+ }
96
+
97
+ function getAttachmentIndexByID(ID: string): number {
98
+ return attachments.findIndex((attachment) => attachment.ID === ID);
99
+ }
100
+
101
+ function resetInputFile(): void {
102
+ if (!props.inputFileRef) return;
103
+ props.inputFileRef.value = "";
104
+ }
105
+
106
+ async function deleteAttachment(ID: string): Promise<void> {
107
+ const index = getAttachmentIndexByID(ID);
108
+
109
+ try {
110
+ setLoadingStatusOnFile(index);
111
+
112
+ await props.delete(attachments[index]);
113
+
114
+ attachments.splice(index, 1);
115
+ } catch (e: any) {
116
+ setErrorStatusOnFile(index);
117
+ logger.error("OrganismAttachments:deleteAttachment", { e });
118
+ }
119
+ }
120
+
121
+ function setPrimitiveOnAttachment({ index, primitive }: any): void {
122
+ attachments[index].primitive = primitive;
123
+ }
124
+ function setLoadingStatusOnFile(index: number): void {
125
+ attachments[index].file.status = EFieldStatus.loading;
126
+ }
127
+ function setSuccessStatusOnFile(index: number): void {
128
+ attachments[index].file.status = EFieldStatus.success;
129
+ }
130
+
131
+ function setErrorStatusOnFile(index: number): void {
132
+ attachments[index].file.status = EFieldStatus.error;
133
+ }
134
+
135
+ // watch
136
+ watch(() => props.inputFileRef, setOnChangeEvent);
137
+ </script>
138
+
139
+ <style scoped></style>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div
3
+ v-for="(message, i) in messages"
4
+ :key="i"
5
+ >
6
+ <MoleculeChatMessageText
7
+ v-if="message.type === EChatMessageTypes.text"
8
+ :message="message"
9
+ />
10
+ <MoleculeChatMessageOption
11
+ v-if="message.type === EChatMessageTypes.option"
12
+ :message="message"
13
+ />
14
+ <MoleculeChatMessageFile
15
+ v-if="message.type === EChatMessageTypes.file"
16
+ :message="message"
17
+ />
18
+ </div>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { IChatMessages, EChatMessageTypes } from './types';
23
+
24
+ // props
25
+ interface IProps {
26
+ messages?: IChatMessages;
27
+ }
28
+ defineProps<IProps>();
29
+ </script>
30
+
31
+ <style scoped></style>
@@ -0,0 +1,342 @@
1
+ <template>
2
+ <AtomBannerChatEmpty v-if="!contact" />
3
+ <div
4
+ v-else
5
+ class="w-full flex flex-col max-h-screen"
6
+ >
7
+ <!-- header -->
8
+ <header class="flex items-center h-12 px-5">
9
+ <!-- avatar -->
10
+ <figure
11
+ :class="[getStatusClass]"
12
+ class="w-10 h-10 avatar avatar-soft-secondary"
13
+ >
14
+ <img
15
+ v-if="contact.avatar"
16
+ :src="contact.avatar"
17
+ class="rounded-md"
18
+ >
19
+ <i
20
+ v-else
21
+ class="bi-person-fill text-xl"
22
+ />
23
+ </figure>
24
+
25
+ <!-- info -->
26
+ <div class="ml-3">
27
+ <p
28
+ class="text-sm"
29
+ v-text="contact?.label"
30
+ />
31
+ <p
32
+ v-if="contact.typing"
33
+ class="text-success text-xs"
34
+ >
35
+ Digitando...
36
+ </p>
37
+ </div>
38
+ </header>
39
+
40
+ <div class="divider" />
41
+
42
+ <!-- messages -->
43
+ <article
44
+ ref="OrganismChatMessagesRef"
45
+ class="scrollbar flex flex-col flex-1 overflow-y-auto p-3"
46
+ >
47
+ <div
48
+ v-if="!messages?.length"
49
+ class="w-full h-full flex justify-center items-center"
50
+ >
51
+ <span class="spin" />
52
+ </div>
53
+
54
+ <OrganismChatMessages :messages="messages" />
55
+ </article>
56
+
57
+ <!-- subject -->
58
+ <span
59
+ v-if="subject"
60
+ :tooltip="subject"
61
+ class="badge badge-outline-secondary text-xs flex justify-center items-center gap-1 cursor-pointer"
62
+ >
63
+ <i class="bi-info-circle text-sm" />
64
+ Resumo Jornada
65
+ </span>
66
+
67
+ <!-- suggestions -->
68
+ <div
69
+ v-if="greetings?.length || goodbyes?.length || absents?.length"
70
+ class="divider"
71
+ />
72
+
73
+ <div
74
+ v-if="greetings?.length || goodbyes?.length || absents?.length"
75
+ :disabled="!contact?.on"
76
+ class="flex flex-wrap gap-2 overflow-x-auto scrollbar px-3 my-2"
77
+ >
78
+ <div
79
+ v-for="(greeting, i) in greetings"
80
+ :key="i"
81
+ :tooltip="marked(greeting)"
82
+ class="suggestions badge badge-solid-success rounded w-14 whitespace-nowrap cursor-pointer"
83
+ @click="send(greeting)"
84
+ v-html="marked(greeting)"
85
+ />
86
+ <div
87
+ v-for="(absent, i) in absents"
88
+ :key="i"
89
+ :tooltip="marked(absent)"
90
+ class="suggestions badge badge-solid-secondary rounded w-14 whitespace-nowrap cursor-pointer"
91
+ @click="send(absent)"
92
+ v-html="marked(absent)"
93
+ />
94
+ <div
95
+ v-for="(goodbye, i) in goodbyes"
96
+ :key="i"
97
+ :tooltip="marked(goodbye)"
98
+ class="suggestions badge badge-solid-error rounded w-14 whitespace-nowrap cursor-pointer"
99
+ @click="send(goodbye)"
100
+ v-html="marked(goodbye)"
101
+ />
102
+ </div>
103
+
104
+ <div class="divider" />
105
+
106
+ <!-- footer -->
107
+ <footer
108
+ :disabled="!contact.on"
109
+ class="flex item p-3"
110
+ >
111
+ <label class="field-group flex-1 cursor-text mr-2">
112
+ <div
113
+ ref="textareaRef"
114
+ class="textarea input w-48 !max-h-36 overflow-y-auto scrollbar whitespace-pre-wrap cursor-text"
115
+ contenteditable
116
+ @paste.prevent="verifyPaste"
117
+ @keydown.enter="inputSend"
118
+ @keyup="typing(true), persist()"
119
+ @blur="typing(false)"
120
+ />
121
+ </label>
122
+ <label
123
+ class="h-fit btn"
124
+ @click="reset"
125
+ >
126
+ <i class="bi bi-x-lg text-base text-error" />
127
+ </label>
128
+ <label
129
+ for="chatfiles"
130
+ class="h-fit btn"
131
+ >
132
+ <input
133
+ id="chatfiles"
134
+ type="file"
135
+ class="hidden"
136
+ @change="sendFile"
137
+ >
138
+ <i class="bi bi-paperclip text-xl leading-none" />
139
+ </label>
140
+
141
+ <button
142
+ class="btn-rounded btn btn-solid-primary rounded-full"
143
+ @click="send(getText())"
144
+ >
145
+ <i class="bi-send-fill" />
146
+ </button>
147
+ </footer>
148
+ </div>
149
+ </template>
150
+
151
+ <script setup lang="ts">
152
+ import { ref, computed, watch } from "vue";
153
+ import { IChatContact, IChatMessages, sleep } from "./types";
154
+ import { marked } from "marked";
155
+ import { useNuxtApp } from "#app";
156
+
157
+ // props
158
+ interface IProps {
159
+ contact?: IChatContact;
160
+ messages?: IChatMessages;
161
+ loading?: boolean;
162
+ goodbyes?: string[];
163
+ greetings?: string[];
164
+ absents?: string[];
165
+ persist?: Function;
166
+ defaults?: string;
167
+ subject?: string;
168
+ }
169
+ const props = defineProps<IProps>();
170
+
171
+ // emits
172
+ interface IEmits {
173
+ (e: "back"): void;
174
+ (e: "typing", typing: boolean): void;
175
+ (e: "send", text: string): void;
176
+ (e: "sendFile", file: File): void;
177
+ }
178
+ const emit = defineEmits<IEmits>();
179
+
180
+ // listen
181
+ const { $listen } = useNuxtApp();
182
+ $listen("message:concatenate", (message: any) => {
183
+ if (!textareaRef.value) return;
184
+
185
+ if (textareaRef.value.innerText.trim() === "") {
186
+ textareaRef.value.innerText = message;
187
+ return;
188
+ }
189
+
190
+ textareaRef.value.innerText = `${textareaRef.value.innerText}
191
+
192
+ ${message}`;
193
+ });
194
+ $listen("message:clear", clear)
195
+
196
+ // computed
197
+ const getStatusClass = computed<string>(() => {
198
+ if (props.contact?.on) return "avatar-status-success";
199
+ return "avatar-status-error";
200
+ });
201
+
202
+ // data
203
+ const textareaRef = ref<HTMLElement>();
204
+ const OrganismChatMessagesRef = ref<HTMLElement>();
205
+ const lastTyping = ref<boolean>(false);
206
+
207
+ // methods
208
+ async function verifyPaste(): Promise<void> {
209
+ const clipboard = await navigator.clipboard.readText();
210
+
211
+ if (clipboard === "") return;
212
+ set(getText() + clipboard);
213
+
214
+ setEndOfContenteditable(textareaRef.value);
215
+ }
216
+ function persist(): void {
217
+ if (!props.persist) return;
218
+
219
+ props.persist(getText());
220
+ }
221
+ function typing(typing: boolean): void {
222
+ if (lastTyping.value === typing) return;
223
+
224
+ lastTyping.value = typing;
225
+ emit("typing", typing);
226
+ }
227
+ function getText(): string {
228
+ return textareaRef.value?.innerText ?? "";
229
+ }
230
+ async function send(text: string): Promise<void> {
231
+ if (!text.trim().length) return;
232
+ emit("send", text);
233
+ reset();
234
+ await sleep(200);
235
+ typing(false);
236
+ }
237
+ function sendFile({ target }: Event): void {
238
+ const input = target as HTMLInputElement;
239
+ if (!input.files) return;
240
+ const file = input.files[0];
241
+ emit("sendFile", file);
242
+ input.value = "";
243
+ }
244
+ function inputSend(e: KeyboardEvent): void {
245
+ if (e.shiftKey || e.ctrlKey || e.altKey) return;
246
+
247
+ e.preventDefault();
248
+ send(getText());
249
+ }
250
+ function clear(): void {
251
+ if (!textareaRef.value) return;
252
+ textareaRef.value.innerText = "";
253
+ }
254
+ function reset(): void {
255
+ clear()
256
+ persist();
257
+ }
258
+ function set(text: string): void {
259
+ if (textareaRef.value) {
260
+ textareaRef.value.innerText = text;
261
+ return;
262
+ }
263
+
264
+ setTimeout(() => {
265
+ set(text);
266
+ }, 1000);
267
+ }
268
+
269
+ let timer: any;
270
+ function scrollBottom() {
271
+ clearTimeout(timer);
272
+ timer = setTimeout(() => {
273
+ OrganismChatMessagesRef.value?.scroll({
274
+ top: OrganismChatMessagesRef.value.scrollHeight,
275
+ behavior: "smooth",
276
+ });
277
+ }, 0);
278
+ }
279
+
280
+ function setEndOfContenteditable(contentEditableElement?: HTMLElement) {
281
+ if (!contentEditableElement) return;
282
+
283
+ const range = document.createRange();
284
+ range.selectNodeContents(contentEditableElement);
285
+ range.collapse(false);
286
+
287
+ const selection = window.getSelection();
288
+ selection?.removeAllRanges();
289
+ selection?.addRange(range);
290
+ }
291
+
292
+ // watch
293
+ watch(
294
+ () => props.messages,
295
+ () => {
296
+ scrollBottom();
297
+ },
298
+ { deep: true }
299
+ );
300
+
301
+ watch(
302
+ () => props.defaults,
303
+ () => {
304
+ set(props.defaults ?? "");
305
+ },
306
+ { deep: true }
307
+ );
308
+
309
+ // expose
310
+ defineExpose({ set, reset });
311
+ </script>
312
+
313
+ <style>
314
+ .textarea {
315
+ min-height: 30px;
316
+ border: none !important;
317
+ box-shadow: none !important;
318
+ }
319
+
320
+ .textarea:focus {
321
+ box-shadow: none !important;
322
+ }
323
+
324
+ .dark .textarea,
325
+ .dark .textarea:focus {
326
+ border: none !important;
327
+ box-shadow: none !important;
328
+ }
329
+
330
+ .suggestions > p {
331
+ overflow: hidden;
332
+ text-overflow: ellipsis;
333
+ }
334
+
335
+ .btn-rounded {
336
+ min-width: 35px;
337
+ max-width: 35px;
338
+ min-height: 35px;
339
+ max-height: 35px;
340
+ padding: 0px !important;
341
+ }
342
+ </style>