vue-intergrall-plugins 1.1.90 → 1.2.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 (45) hide show
  1. package/README.md +220 -220
  2. package/dist/dist/vue-intergrall-plugins.css +1 -1
  3. package/dist/vue-intergrall-plugins.esm.js +193 -78
  4. package/dist/vue-intergrall-plugins.min.js +7 -7
  5. package/dist/vue-intergrall-plugins.ssr.js +245 -136
  6. package/package.json +61 -61
  7. package/src/lib-components/Buttons/IconButton.vue +27 -27
  8. package/src/lib-components/Buttons/SimpleButton.vue +140 -140
  9. package/src/lib-components/Cards/Card.vue +490 -490
  10. package/src/lib-components/Cards/CardCheck.vue +35 -35
  11. package/src/lib-components/Cards/CardFile.vue +163 -163
  12. package/src/lib-components/Chat/BtnDownloadAllFiles.vue +36 -36
  13. package/src/lib-components/Chat/BtnEmojis.vue +118 -118
  14. package/src/lib-components/Chat/BtnExpand.vue +17 -17
  15. package/src/lib-components/Chat/BtnFiles.vue +486 -486
  16. package/src/lib-components/Chat/BtnMic.vue +60 -60
  17. package/src/lib-components/Chat/BtnScreenShare.vue +31 -31
  18. package/src/lib-components/Chat/BtnStandardMessages.vue +17 -17
  19. package/src/lib-components/Chat/ExpandTextarea.vue +427 -427
  20. package/src/lib-components/Chat/MultipleFilePreview.vue +291 -291
  21. package/src/lib-components/Chat/Picker.vue +525 -525
  22. package/src/lib-components/Chat/RemainingCharacters.vue +28 -28
  23. package/src/lib-components/Chat/SingleFilePreview.vue +94 -94
  24. package/src/lib-components/Chat/SkeletonPicker.vue +110 -110
  25. package/src/lib-components/Chat/StandardMessages.vue +252 -252
  26. package/src/lib-components/Chat/TextFooter.vue +1007 -1007
  27. package/src/lib-components/Email/EmailExpanded.vue +270 -270
  28. package/src/lib-components/Email/EmailFile.vue +192 -192
  29. package/src/lib-components/Email/EmailFrom.vue +66 -66
  30. package/src/lib-components/Email/EmailItem.vue +867 -850
  31. package/src/lib-components/Email/EmailTo.vue +64 -64
  32. package/src/lib-components/Loader/Loader.vue +78 -78
  33. package/src/lib-components/Messages/AnexoMensagem.vue +534 -497
  34. package/src/lib-components/Messages/CardAttachment.vue +61 -61
  35. package/src/lib-components/Messages/CardMessages.vue +687 -687
  36. package/src/lib-components/Messages/InteratividadeBotoes.vue +197 -197
  37. package/src/lib-components/Messages/InteratividadeContato.vue +32 -32
  38. package/src/lib-components/Messages/InteratividadeContatoItem.vue +235 -235
  39. package/src/lib-components/Messages/InteratividadeFormulario.vue +334 -334
  40. package/src/lib-components/Messages/InteratividadePopup.vue +95 -95
  41. package/src/lib-components/Messages/LinkPreview.vue +176 -176
  42. package/src/lib-components/Scroll/ScrollContent.vue +166 -166
  43. package/src/lib-components/Templates/TemplateGenerator.vue +640 -640
  44. package/src/lib-components/Templates/TemplateMessage.vue +83 -83
  45. package/src/lib-components/Templates/TemplateSingle.vue +478 -478
@@ -1,478 +1,478 @@
1
- <template>
2
- <div class="ts-container">
3
- <div class="ts-content">
4
- <div v-for="(component, cIndex) in template.components" :key="cIndex"
5
- :class="`${component.type == 'footer' ? 'order-1 custom-box-shadow-bottom' : ''}${template.components.length == 1 ? 'custom-box-shadow-bottom' : ''}`">
6
- <header v-if="component.type === 'header'"
7
- :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''}`"
8
- id="template_header">
9
- <div v-for="(param, pIndex) in component.parameters" :key="pIndex">
10
- <template v-if="param.type === 'text'">
11
- <div v-if="regexVars.test(param.text)" v-html="param.text.replace(
12
- regexVars,
13
- `<div class='ts-content__var'>${htmlInputString}${varListValues}</div>`
14
- )
15
- "></div>
16
- <p v-else v-html="param.text"></p>
17
- </template>
18
- <div v-else-if="param.type == 'image'" class="ts-image-type">
19
- <input class="d-none" type="file" :ref="`ts-input-header-${pIndex}`" :accept="acceptedExtensions"
20
- @change="fileUpload($event)" />
21
- <template v-if="headerFiles[0]">
22
- <fa-icon :icon="['fas', headerFiles[0].current_icon]" :class="`color-${headerFiles[0].current_color}`"
23
- @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }" :content="headerFiles[0].image_preview ?
24
- `<div class='custom-tooltip-image'>
25
- <img src='${headerFiles[0].image_preview}' alt='${headerFiles[0].name}' />
26
- </div>`
27
- : headerFiles[0].name
28
- " />
29
- </template>
30
- <fa-icon v-else class="select-image" :icon="['fas', 'image']"
31
- @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }"
32
- :content="`Selecionar anexo`" />
33
- </div>
34
- <div v-else-if="param.type == 'document'" class="ts-image-type">
35
- <input class="d-none" type="file" :ref="`ts-input-header-${pIndex}`" :accept="acceptedExtensions"
36
- @change="fileUpload($event)" />
37
- <template v-if="headerFiles[0]">
38
- <fa-icon :icon="['fas', headerFiles[0].current_icon]" :class="`color-${headerFiles[0].current_color}`"
39
- @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }" :content="headerFiles[0].name
40
- " />
41
- </template>
42
- <fa-icon v-else class="select-image" :icon="['fas', 'file-alt']"
43
- @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }"
44
- :content="`Selecionar anexo`" />
45
- </div>
46
- <div v-else-if="param.type == 'video'" class="ts-image-type">
47
- <input class="d-none" type="file" :ref="`ts-input-header-${pIndex}`" :accept="acceptedExtensions"
48
- @change="fileUpload($event)" />
49
- <template v-if="headerFiles[0]">
50
- <fa-icon :icon="['fas', headerFiles[0].current_icon]" :class="`color-${headerFiles[0].current_color}`"
51
- @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }" :content="headerFiles[0].name
52
- " />
53
- </template>
54
- <fa-icon v-else class="select-image" :icon="['fas', 'video']" style="font-size: 19.2px"
55
- @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }"
56
- :content="`Selecionar anexo`" />
57
- </div>
58
- </div>
59
- </header>
60
- <section v-if="component.type === 'body'"
61
- :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''} ${!hasButtonOrFooterComponent ? 'custom-footer-style custom-box-shadow-bottom custom-border-gray' : ''}`"
62
- id="template_body">
63
- <div v-for="(param, pIndex) in component.parameters" :key="pIndex">
64
- <template v-if="param.type === 'text'">
65
- <div v-if="regexVars.test(param.text)" v-html="param.text.replace(
66
- regexVars,
67
- `<div class='ts-content__var'>${htmlInputString}${varListValues}</div>`
68
- )
69
- "></div>
70
- <p v-else v-html="param.text"></p>
71
- </template>
72
- </div>
73
- </section>
74
- <section v-if="component.type == 'button'"
75
- :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''} ${!hasFooterComponent ? 'custom-footer-style custom-box-shadow-bottom' : ''}`"
76
- id="template_buttons">
77
- <div v-for="(param, pIndex) in component.parameters" :key="pIndex" class="ts-button">
78
- <p v-if="param.text" v-html="param.text"></p>
79
- </div>
80
- </section>
81
- <footer v-if="component.type == 'footer'"
82
- :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''}`"
83
- id="template_footer">
84
- <div v-for="(param, pIndex) in component.parameters" :key="pIndex">
85
- <small v-if="param.type == 'text'" v-html="param.text"> </small>
86
- </div>
87
- </footer>
88
- </div>
89
- </div>
90
-
91
- <div class="container-btns">
92
- <div class="tg-btn" :class="{ 'small-btn': iconButton }" v-if="hasButton">
93
- <button @click="$emit('click-trigger')" ref="template-single-button">
94
- <template v-if="!iconButton">
95
- <fa-icon :icon="['fas', 'paper-plane']" />
96
- {{ dictionary.btn_contatar_clientes }}
97
- </template>
98
- <fa-icon v-else :icon="['fas', 'paper-plane']" />
99
- </button>
100
-
101
- </div>
102
-
103
- <div class="tg-btn btn-red" :class="{ 'small-btn': iconButton }" v-if="hasSecondaryButton">
104
- <button v-if="hasSecondaryButton" @click="$emit('dispatch-clients-with-bot')"
105
- ref="template-single-button-secondary">
106
- <template v-if="!iconButton">
107
- <fa-icon :icon="['fas', 'paper-plane']" />
108
- {{ dictionary.btn_contatar_clientes_com_bot }}
109
- </template>
110
- <fa-icon v-else :icon="['fas', 'paper-plane']" />
111
- </button>
112
- </div>
113
- </div>
114
-
115
- </div>
116
- </template>
117
-
118
- <style>
119
- .btn-red button {
120
- background-color: red;
121
- }
122
-
123
- .btn-red svg {
124
- color: rgb(182, 0, 0);
125
- }
126
-
127
- .container-btns {
128
- display: flex;
129
- flex-direction: column;
130
- justify-content: center;
131
- align-items: center;
132
- gap: 10px;
133
- }
134
- </style>
135
-
136
- <script>
137
- export default {
138
- data() {
139
- return {
140
- varValues: {},
141
- regexVars: /{{var_\d}}/g,
142
- lastVar: 0,
143
- htmlInputString: `<input type='text' class='input-var input-var-${this.identifier}' autocomplete='off' />`,
144
- varListValues: "",
145
- acceptedExtensions: "audio/aac, audio/mp4, audio/mpeg, audio/amr, audio/ogg, text/plain, application/pdf, application/vnd.ms-powerpoint, application/msword, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.openxmlformats-officedocument.spreasheetml.sheet, image/jpeg, image/png, video/mp4, video/3gp, image/webp",
146
- headerFiles: [],
147
- };
148
- },
149
- props: {
150
- template: {
151
- type: Object,
152
- required: true,
153
- },
154
- hasButton: {
155
- type: Boolean,
156
- required: false,
157
- default: true,
158
- },
159
- hasSecondaryButton: {
160
- type: Boolean,
161
- required: false,
162
- default: false
163
- },
164
- allVariables: {
165
- type: Boolean,
166
- required: false,
167
- default: true
168
- },
169
- iconButton: {
170
- type: Boolean,
171
- required: false,
172
- default: false
173
- },
174
- identifier: {
175
- type: String,
176
- required: true
177
- },
178
- dictionary: {
179
- type: Object,
180
- required: true
181
- }
182
- },
183
- computed: {
184
- hasFooterComponent() {
185
- if (!this.template.components || !this.template.components.length) return false
186
- const types = []
187
- this.template.components.forEach(({ type }) => types.push(type))
188
- if (types.includes('footer')) return true
189
- return false
190
- },
191
- hasButtonOrFooterComponent() {
192
- if (!this.template.components || !this.template.components.length) return false
193
- const types = []
194
- this.template.components.forEach(({ type }) => types.push(type))
195
- if (types.includes('button')) return true
196
- return this.hasFooterComponent
197
- },
198
- },
199
- created() {
200
- this.$root.$refs[`template-single-${this.identifier}`] = this
201
- },
202
- mounted() {
203
- if (this.allVariables) this.getListOpts()
204
- this.setInputs();
205
- this.setImageVar(false)
206
- },
207
- updated() {
208
- this.lastVar = 0;
209
- this.setInputs();
210
-
211
- const allInputs = document.querySelectorAll(`.input-var-${this.identifier}`);
212
- allInputs.forEach(input => {
213
- input.dispatchEvent(new Event('focus'))
214
- input.dispatchEvent(new Event('blur'))
215
- })
216
- },
217
- methods: {
218
- handleInitialFocus() {
219
- const allInputs = document.querySelectorAll(`.input-var-${this.identifier}`);
220
- if (allInputs.length) allInputs[0].focus()
221
- else if (this.$refs["template-single-button"]) this.$refs["template-single-button"].focus()
222
- },
223
- limparInputVar() {
224
- const allInputs = document.querySelectorAll(`.input-var-${this.identifier}`);
225
- allInputs.forEach((input) => {
226
- input.focus();
227
- input.value = "";
228
- input.blur();
229
- this.setVar({ target: input }, input.id.replace(/[{}]+/g, ''));
230
- })
231
- this.setImageVar(false)
232
- },
233
- getListOpts() {
234
- this.varListValues =
235
- `<ul class="ts-dropdown">
236
- <li> nome </li>
237
- ${this.allVariables ? '<li> telefone </li>' : ''}
238
- </ul>`
239
- },
240
- fileUpload(event) {
241
- try {
242
- const allFiles = event.target.files ? event.target.files : event.dataTransfer.files ? event.dataTransfer.files : false
243
- if (!allFiles || !allFiles.length) return this.setImageVar(false)
244
- if (allFiles.length > 1) {
245
- this.$toasted.global.defaultInfo({ msg: `Limite de 1 arquivo por vez` })
246
- return this.setImageVar(false)
247
- }
248
- const file = allFiles[0]
249
- const { type, name } = file
250
- const validTypes = ['audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/amr', 'audio/ogg', 'text/plain', 'application/pdf', 'application/vnd.ms-powerpoint', 'application/msword', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.spreasheetml.sheet', 'image/jpeg', 'image/png', 'video/mp4', 'video/3gp', 'image/webp']
251
- if (!validTypes.includes(type)) {
252
- this.$toasted.global.defaultInfo({ msg: `(${type}) ${this.dictionary.msg_arquivo_invalido}` })
253
- return this.setImageVar(false)
254
- }
255
- const returnArrType = str => {
256
- const arr = []
257
- validTypes.forEach(currentType => {
258
- if (currentType.indexOf(str) > -1) arr.push(currentType)
259
- })
260
- return arr
261
- }
262
-
263
- const imageTypes = returnArrType('image/')
264
- const docTypes = returnArrType('application/')
265
- const audioTypes = returnArrType('audio/')
266
- const videoTypes = returnArrType('video/')
267
- const isPdf = type.indexOf('application/pdf') > -1
268
-
269
- let sizeInBytes = 0
270
- Array.from(allFiles).forEach(file => sizeInBytes += file.size)
271
- const sizeInMb = parseFloat((sizeInBytes / (1024 * 1024)).toFixed(2))
272
- if (sizeInMb == 0 && sizeInBytes == 0) {
273
- this.$toasted.global.defaultInfo({ msg: this.dictionary.msg_arquivo_invalido })
274
- return this.setImageVar(false)
275
- }
276
- const max = imageTypes.includes(type) ? 5 : 15
277
- if (sizeInMb >= max) {
278
- this.$toasted.global.defaultInfo({ msg: `Limite de ${max} MB por arquivo` })
279
- return this.setImageVar(false)
280
- }
281
-
282
- const fileReader = new FileReader()
283
- let current_icon = 'file-alt', current_color = 'blue', setValues = true
284
- if (imageTypes.includes(type)) {
285
- setValues = false
286
- fileReader.readAsDataURL(file)
287
- fileReader.onload = () => {
288
- const image_preview = fileReader.result
289
- current_icon = 'image'
290
- current_color = 'purple'
291
- this.headerFiles[0] = { file, image_preview, current_icon, current_color, name }
292
- this.$forceUpdate()
293
- this.setImageVar(file)
294
- }
295
- } else if (docTypes.includes(type) && isPdf) {
296
- current_icon = 'file-pdf'
297
- current_color = 'red'
298
- } else if (audioTypes.includes(type)) {
299
- current_icon = 'microphone'
300
- current_color = 'black'
301
- } else if (videoTypes.includes(type)) {
302
- current_icon = 'video',
303
- current_color = 'black'
304
- }
305
-
306
- if (setValues) {
307
- this.headerFiles[0] = { file, current_icon, current_color, name }
308
- this.$forceUpdate()
309
- this.setImageVar(file)
310
- }
311
- } catch (error) {
312
- console.error("Erro file upload: ", error)
313
- }
314
- },
315
- triggerInputFile(origin) {
316
- if (this.$refs[`ts-input-${origin}`]) {
317
- const elem = this.$refs[`ts-input-${origin}`][0] ? this.$refs[`ts-input-${origin}`][0] : this.$refs[`ts-input-${origin}`]
318
- elem && elem.click()
319
- }
320
- },
321
- setInputs() {
322
- let qtdInputs = 0
323
- const header = document.querySelector("#template_header");
324
- const body = document.querySelector("#template_body");
325
- const footer = document.querySelector("#template_footer");
326
-
327
- let hasText = false
328
- if (header !== null) {
329
- header.querySelectorAll(`.input-var-${this.identifier}`).forEach((input) => { this.setEvent(input) })
330
- qtdInputs += header.querySelectorAll(`.input-var-${this.identifier}`).length
331
- if (qtdInputs > 0 && header.innerText) hasText = true
332
- }
333
- if (this.lastVar === 0) this.lastVar += 1
334
- if (body !== null) {
335
- body.querySelectorAll(`.input-var-${this.identifier}`).forEach((input) => { this.setEvent(input) });
336
- qtdInputs += body.querySelectorAll(`.input-var-${this.identifier}`).length
337
- if (qtdInputs > 0 && body.innerText) hasText = true
338
- }
339
- if (footer !== null) {
340
- footer.querySelectorAll(`.input-var-${this.identifier}`).forEach((input) => { this.setEvent(input) });
341
- qtdInputs += footer.querySelectorAll(`.input-var-${this.identifier}`).length
342
- if (qtdInputs > 0 && footer.innerText) hasText = true
343
- }
344
-
345
- if (qtdInputs === 1 && !hasText) {
346
- document.querySelector(`.input-var-${this.identifier}`).parentElement.style.width = "100%"
347
- document.querySelector(`.input-var-${this.identifier}`).style.width = "100%"
348
- } else if (qtdInputs === 0) {
349
- this.$emit("set-vars", this.template)
350
- }
351
- },
352
- setEvent(input) {
353
- const varList = input.nextSibling
354
-
355
- const currentInstance = this
356
-
357
- input.setAttribute("placeholder", `{{var_${this.lastVar}}}`);
358
- input.setAttribute("id", `{{var_${this.lastVar}}}`);
359
- input.addEventListener("input", (event) => {
360
- this.setVar(event, `var_${this.lastVar}`, true);
361
- if (varList) varList.classList.remove("visible")
362
- });
363
- input.addEventListener("focus", () => {
364
- input.classList.remove("invalid");
365
- input.classList.add("active");
366
- if (varList) {
367
- const handleNextFocus = () => {
368
- const allInputs = document.querySelectorAll(`.input-var-${currentInstance.identifier}`)
369
- let stop = false
370
- if (allInputs.length) {
371
- allInputs.forEach(elem => {
372
- if (!elem.value && !stop) {
373
- stop = true
374
- elem.focus()
375
- }
376
- })
377
- }
378
- if (!stop && this.$refs["template-single-button"]) this.$refs["template-single-button"].focus()
379
- }
380
-
381
- for (let i = 0; i < varList.children.length; i++) {
382
- varList.children[i].addEventListener("click", function () {
383
- input.value = `[${this.innerText}]`
384
- input.classList.add("active")
385
- input.dispatchEvent(new Event('input'))
386
- handleNextFocus()
387
- })
388
- }
389
-
390
- const containerGeral = document.querySelector(".tg-container")
391
- varList.classList.add("visible")
392
- const maxBottom = containerGeral.getBoundingClientRect().bottom,
393
- currentBottom = varList.getBoundingClientRect().bottom
394
- if (currentBottom > maxBottom) {
395
- varList.style.top = `-${varList.getBoundingClientRect().height}px`
396
- varList.style.borderTop = `1px solid currentColor`
397
- }
398
- }
399
- });
400
- input.addEventListener("blur", () => {
401
- if (input.value) {
402
- if (this.isValid(input.value)) {
403
- input.classList.add("active");
404
- } else {
405
- input.classList.add("invalid");
406
- }
407
- } else {
408
- input.classList.remove("invalid");
409
- input.classList.remove("active");
410
- }
411
- if (varList) varList.classList.remove("visible")
412
- });
413
- input.addEventListener("keydown", (event) => {
414
- if (event.keyCode == 13 && this.hasButton) this.$emit("click-trigger");
415
- });
416
- this.lastVar += 1;
417
- },
418
- toggleClass(index) {
419
- const elem = this.$refs[`float-container-${index}`]
420
- ? this.$refs[`float-container-${index}`][0]
421
- : false;
422
- if (elem) {
423
- if (elem.children[1] && elem.children[1].value) {
424
- elem.classList.add("active");
425
- } else {
426
- elem.classList.toggle("active");
427
- }
428
- }
429
- },
430
- isValid(textValue) {
431
- const regex = {
432
- htmlTags: /<\/?[\d\w\s=\-:\.\/\'\";]+>/gi,
433
- enter: /\n/g,
434
- consecutiveSpaces: /\s{3,}/g
435
- }
436
- const value = textValue ? textValue.trim("") : "";
437
- if (!value.length) return false
438
-
439
- let isValueValid = true
440
- for (let key in regex) {
441
- if (!isValueValid) return isValueValid
442
- if (regex[key].test(value)) isValueValid = false
443
- }
444
-
445
- return isValueValid
446
- },
447
- setImageVar(file) {
448
- this.$emit("set-file-var", file)
449
- if (!file) {
450
- this.headerFiles = []
451
- return this.$forceUpdate()
452
- } else {
453
- if (this.lastVar == 0) return this.$emit("set-vars", this.template)
454
- if (Object.keys(this.varValues).length) return this.$emit("set-vars", this.varValues)
455
- this.$nextTick(() => this.handleInitialFocus())
456
- }
457
- },
458
- setVar(event, key, notificar) {
459
- if (event && event.target) {
460
- key = event.target.id.replace(/[{}]+/g, '')
461
- const value = event.target.value ? event.target.value.trim("") : ""
462
- if (this.isValid(value)) {
463
- this.varValues[key] = value;
464
- } else {
465
- if (notificar)
466
- if (!document.querySelector(".toasted.toasted-primary.error"))
467
- this.$toasted.global.defaultError({
468
- msg: this.dictionary.template_msg_variavel_invalida,
469
- });
470
-
471
- delete this.varValues[key];
472
- }
473
- return this.$emit("set-vars", this.varValues);
474
- }
475
- },
476
- }
477
- };
478
- </script>
1
+ <template>
2
+ <div class="ts-container">
3
+ <div class="ts-content">
4
+ <div v-for="(component, cIndex) in template.components" :key="cIndex"
5
+ :class="`${component.type == 'footer' ? 'order-1 custom-box-shadow-bottom' : ''}${template.components.length == 1 ? 'custom-box-shadow-bottom' : ''}`">
6
+ <header v-if="component.type === 'header'"
7
+ :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''}`"
8
+ id="template_header">
9
+ <div v-for="(param, pIndex) in component.parameters" :key="pIndex">
10
+ <template v-if="param.type === 'text'">
11
+ <div v-if="regexVars.test(param.text)" v-html="param.text.replace(
12
+ regexVars,
13
+ `<div class='ts-content__var'>${htmlInputString}${varListValues}</div>`
14
+ )
15
+ "></div>
16
+ <p v-else v-html="param.text"></p>
17
+ </template>
18
+ <div v-else-if="param.type == 'image'" class="ts-image-type">
19
+ <input class="d-none" type="file" :ref="`ts-input-header-${pIndex}`" :accept="acceptedExtensions"
20
+ @change="fileUpload($event)" />
21
+ <template v-if="headerFiles[0]">
22
+ <fa-icon :icon="['fas', headerFiles[0].current_icon]" :class="`color-${headerFiles[0].current_color}`"
23
+ @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }" :content="headerFiles[0].image_preview ?
24
+ `<div class='custom-tooltip-image'>
25
+ <img src='${headerFiles[0].image_preview}' alt='${headerFiles[0].name}' />
26
+ </div>`
27
+ : headerFiles[0].name
28
+ " />
29
+ </template>
30
+ <fa-icon v-else class="select-image" :icon="['fas', 'image']"
31
+ @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }"
32
+ :content="`Selecionar anexo`" />
33
+ </div>
34
+ <div v-else-if="param.type == 'document'" class="ts-image-type">
35
+ <input class="d-none" type="file" :ref="`ts-input-header-${pIndex}`" :accept="acceptedExtensions"
36
+ @change="fileUpload($event)" />
37
+ <template v-if="headerFiles[0]">
38
+ <fa-icon :icon="['fas', headerFiles[0].current_icon]" :class="`color-${headerFiles[0].current_color}`"
39
+ @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }" :content="headerFiles[0].name
40
+ " />
41
+ </template>
42
+ <fa-icon v-else class="select-image" :icon="['fas', 'file-alt']"
43
+ @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }"
44
+ :content="`Selecionar anexo`" />
45
+ </div>
46
+ <div v-else-if="param.type == 'video'" class="ts-image-type">
47
+ <input class="d-none" type="file" :ref="`ts-input-header-${pIndex}`" :accept="acceptedExtensions"
48
+ @change="fileUpload($event)" />
49
+ <template v-if="headerFiles[0]">
50
+ <fa-icon :icon="['fas', headerFiles[0].current_icon]" :class="`color-${headerFiles[0].current_color}`"
51
+ @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }" :content="headerFiles[0].name
52
+ " />
53
+ </template>
54
+ <fa-icon v-else class="select-image" :icon="['fas', 'video']" style="font-size: 19.2px"
55
+ @click="triggerInputFile(`header-${pIndex}`)" v-tippy="{ placement: 'right' }"
56
+ :content="`Selecionar anexo`" />
57
+ </div>
58
+ </div>
59
+ </header>
60
+ <section v-if="component.type === 'body'"
61
+ :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''} ${!hasButtonOrFooterComponent ? 'custom-footer-style custom-box-shadow-bottom custom-border-gray' : ''}`"
62
+ id="template_body">
63
+ <div v-for="(param, pIndex) in component.parameters" :key="pIndex">
64
+ <template v-if="param.type === 'text'">
65
+ <div v-if="regexVars.test(param.text)" v-html="param.text.replace(
66
+ regexVars,
67
+ `<div class='ts-content__var'>${htmlInputString}${varListValues}</div>`
68
+ )
69
+ "></div>
70
+ <p v-else v-html="param.text"></p>
71
+ </template>
72
+ </div>
73
+ </section>
74
+ <section v-if="component.type == 'button'"
75
+ :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''} ${!hasFooterComponent ? 'custom-footer-style custom-box-shadow-bottom' : ''}`"
76
+ id="template_buttons">
77
+ <div v-for="(param, pIndex) in component.parameters" :key="pIndex" class="ts-button">
78
+ <p v-if="param.text" v-html="param.text"></p>
79
+ </div>
80
+ </section>
81
+ <footer v-if="component.type == 'footer'"
82
+ :class="`${template.components.length == 1 ? 'border-radius-5 custom-border-gray' : ''}`"
83
+ id="template_footer">
84
+ <div v-for="(param, pIndex) in component.parameters" :key="pIndex">
85
+ <small v-if="param.type == 'text'" v-html="param.text"> </small>
86
+ </div>
87
+ </footer>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="container-btns">
92
+ <div class="tg-btn" :class="{ 'small-btn': iconButton }" v-if="hasButton">
93
+ <button @click="$emit('click-trigger')" ref="template-single-button">
94
+ <template v-if="!iconButton">
95
+ <fa-icon :icon="['fas', 'paper-plane']" />
96
+ {{ dictionary.btn_contatar_clientes }}
97
+ </template>
98
+ <fa-icon v-else :icon="['fas', 'paper-plane']" />
99
+ </button>
100
+
101
+ </div>
102
+
103
+ <div class="tg-btn btn-red" :class="{ 'small-btn': iconButton }" v-if="hasSecondaryButton">
104
+ <button v-if="hasSecondaryButton" @click="$emit('dispatch-clients-with-bot')"
105
+ ref="template-single-button-secondary">
106
+ <template v-if="!iconButton">
107
+ <fa-icon :icon="['fas', 'paper-plane']" />
108
+ {{ dictionary.btn_contatar_clientes_com_bot }}
109
+ </template>
110
+ <fa-icon v-else :icon="['fas', 'paper-plane']" />
111
+ </button>
112
+ </div>
113
+ </div>
114
+
115
+ </div>
116
+ </template>
117
+
118
+ <style>
119
+ .btn-red button {
120
+ background-color: red;
121
+ }
122
+
123
+ .btn-red svg {
124
+ color: rgb(182, 0, 0);
125
+ }
126
+
127
+ .container-btns {
128
+ display: flex;
129
+ flex-direction: column;
130
+ justify-content: center;
131
+ align-items: center;
132
+ gap: 10px;
133
+ }
134
+ </style>
135
+
136
+ <script>
137
+ export default {
138
+ data() {
139
+ return {
140
+ varValues: {},
141
+ regexVars: /{{var_\d}}/g,
142
+ lastVar: 0,
143
+ htmlInputString: `<input type='text' class='input-var input-var-${this.identifier}' autocomplete='off' />`,
144
+ varListValues: "",
145
+ acceptedExtensions: "audio/aac, audio/mp4, audio/mpeg, audio/amr, audio/ogg, audio/ogx, text/plain, application/pdf, application/vnd.ms-powerpoint, application/msword, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.openxmlformats-officedocument.spreasheetml.sheet, image/jpeg, image/png, video/mp4, video/3gp, image/webp",
146
+ headerFiles: [],
147
+ };
148
+ },
149
+ props: {
150
+ template: {
151
+ type: Object,
152
+ required: true,
153
+ },
154
+ hasButton: {
155
+ type: Boolean,
156
+ required: false,
157
+ default: true,
158
+ },
159
+ hasSecondaryButton: {
160
+ type: Boolean,
161
+ required: false,
162
+ default: false
163
+ },
164
+ allVariables: {
165
+ type: Boolean,
166
+ required: false,
167
+ default: true
168
+ },
169
+ iconButton: {
170
+ type: Boolean,
171
+ required: false,
172
+ default: false
173
+ },
174
+ identifier: {
175
+ type: String,
176
+ required: true
177
+ },
178
+ dictionary: {
179
+ type: Object,
180
+ required: true
181
+ }
182
+ },
183
+ computed: {
184
+ hasFooterComponent() {
185
+ if (!this.template.components || !this.template.components.length) return false
186
+ const types = []
187
+ this.template.components.forEach(({ type }) => types.push(type))
188
+ if (types.includes('footer')) return true
189
+ return false
190
+ },
191
+ hasButtonOrFooterComponent() {
192
+ if (!this.template.components || !this.template.components.length) return false
193
+ const types = []
194
+ this.template.components.forEach(({ type }) => types.push(type))
195
+ if (types.includes('button')) return true
196
+ return this.hasFooterComponent
197
+ },
198
+ },
199
+ created() {
200
+ this.$root.$refs[`template-single-${this.identifier}`] = this
201
+ },
202
+ mounted() {
203
+ if (this.allVariables) this.getListOpts()
204
+ this.setInputs();
205
+ this.setImageVar(false)
206
+ },
207
+ updated() {
208
+ this.lastVar = 0;
209
+ this.setInputs();
210
+
211
+ const allInputs = document.querySelectorAll(`.input-var-${this.identifier}`);
212
+ allInputs.forEach(input => {
213
+ input.dispatchEvent(new Event('focus'))
214
+ input.dispatchEvent(new Event('blur'))
215
+ })
216
+ },
217
+ methods: {
218
+ handleInitialFocus() {
219
+ const allInputs = document.querySelectorAll(`.input-var-${this.identifier}`);
220
+ if (allInputs.length) allInputs[0].focus()
221
+ else if (this.$refs["template-single-button"]) this.$refs["template-single-button"].focus()
222
+ },
223
+ limparInputVar() {
224
+ const allInputs = document.querySelectorAll(`.input-var-${this.identifier}`);
225
+ allInputs.forEach((input) => {
226
+ input.focus();
227
+ input.value = "";
228
+ input.blur();
229
+ this.setVar({ target: input }, input.id.replace(/[{}]+/g, ''));
230
+ })
231
+ this.setImageVar(false)
232
+ },
233
+ getListOpts() {
234
+ this.varListValues =
235
+ `<ul class="ts-dropdown">
236
+ <li> nome </li>
237
+ ${this.allVariables ? '<li> telefone </li>' : ''}
238
+ </ul>`
239
+ },
240
+ fileUpload(event) {
241
+ try {
242
+ const allFiles = event.target.files ? event.target.files : event.dataTransfer.files ? event.dataTransfer.files : false
243
+ if (!allFiles || !allFiles.length) return this.setImageVar(false)
244
+ if (allFiles.length > 1) {
245
+ this.$toasted.global.defaultInfo({ msg: `Limite de 1 arquivo por vez` })
246
+ return this.setImageVar(false)
247
+ }
248
+ const file = allFiles[0]
249
+ const { type, name } = file
250
+ const validTypes = ['audio/aac', 'audio/mp4', 'audio/mpeg', 'audio/amr', 'audio/ogg', 'audio/ogx', 'text/plain', 'application/pdf', 'application/vnd.ms-powerpoint', 'application/msword', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.spreasheetml.sheet', 'image/jpeg', 'image/png', 'video/mp4', 'video/3gp', 'image/webp']
251
+ if (!validTypes.includes(type)) {
252
+ this.$toasted.global.defaultInfo({ msg: `(${type}) ${this.dictionary.msg_arquivo_invalido}` })
253
+ return this.setImageVar(false)
254
+ }
255
+ const returnArrType = str => {
256
+ const arr = []
257
+ validTypes.forEach(currentType => {
258
+ if (currentType.indexOf(str) > -1) arr.push(currentType)
259
+ })
260
+ return arr
261
+ }
262
+
263
+ const imageTypes = returnArrType('image/')
264
+ const docTypes = returnArrType('application/')
265
+ const audioTypes = returnArrType('audio/')
266
+ const videoTypes = returnArrType('video/')
267
+ const isPdf = type.indexOf('application/pdf') > -1
268
+
269
+ let sizeInBytes = 0
270
+ Array.from(allFiles).forEach(file => sizeInBytes += file.size)
271
+ const sizeInMb = parseFloat((sizeInBytes / (1024 * 1024)).toFixed(2))
272
+ if (sizeInMb == 0 && sizeInBytes == 0) {
273
+ this.$toasted.global.defaultInfo({ msg: this.dictionary.msg_arquivo_invalido })
274
+ return this.setImageVar(false)
275
+ }
276
+ const max = imageTypes.includes(type) ? 5 : 15
277
+ if (sizeInMb >= max) {
278
+ this.$toasted.global.defaultInfo({ msg: `Limite de ${max} MB por arquivo` })
279
+ return this.setImageVar(false)
280
+ }
281
+
282
+ const fileReader = new FileReader()
283
+ let current_icon = 'file-alt', current_color = 'blue', setValues = true
284
+ if (imageTypes.includes(type)) {
285
+ setValues = false
286
+ fileReader.readAsDataURL(file)
287
+ fileReader.onload = () => {
288
+ const image_preview = fileReader.result
289
+ current_icon = 'image'
290
+ current_color = 'purple'
291
+ this.headerFiles[0] = { file, image_preview, current_icon, current_color, name }
292
+ this.$forceUpdate()
293
+ this.setImageVar(file)
294
+ }
295
+ } else if (docTypes.includes(type) && isPdf) {
296
+ current_icon = 'file-pdf'
297
+ current_color = 'red'
298
+ } else if (audioTypes.includes(type)) {
299
+ current_icon = 'microphone'
300
+ current_color = 'black'
301
+ } else if (videoTypes.includes(type)) {
302
+ current_icon = 'video',
303
+ current_color = 'black'
304
+ }
305
+
306
+ if (setValues) {
307
+ this.headerFiles[0] = { file, current_icon, current_color, name }
308
+ this.$forceUpdate()
309
+ this.setImageVar(file)
310
+ }
311
+ } catch (error) {
312
+ console.error("Erro file upload: ", error)
313
+ }
314
+ },
315
+ triggerInputFile(origin) {
316
+ if (this.$refs[`ts-input-${origin}`]) {
317
+ const elem = this.$refs[`ts-input-${origin}`][0] ? this.$refs[`ts-input-${origin}`][0] : this.$refs[`ts-input-${origin}`]
318
+ elem && elem.click()
319
+ }
320
+ },
321
+ setInputs() {
322
+ let qtdInputs = 0
323
+ const header = document.querySelector("#template_header");
324
+ const body = document.querySelector("#template_body");
325
+ const footer = document.querySelector("#template_footer");
326
+
327
+ let hasText = false
328
+ if (header !== null) {
329
+ header.querySelectorAll(`.input-var-${this.identifier}`).forEach((input) => { this.setEvent(input) })
330
+ qtdInputs += header.querySelectorAll(`.input-var-${this.identifier}`).length
331
+ if (qtdInputs > 0 && header.innerText) hasText = true
332
+ }
333
+ if (this.lastVar === 0) this.lastVar += 1
334
+ if (body !== null) {
335
+ body.querySelectorAll(`.input-var-${this.identifier}`).forEach((input) => { this.setEvent(input) });
336
+ qtdInputs += body.querySelectorAll(`.input-var-${this.identifier}`).length
337
+ if (qtdInputs > 0 && body.innerText) hasText = true
338
+ }
339
+ if (footer !== null) {
340
+ footer.querySelectorAll(`.input-var-${this.identifier}`).forEach((input) => { this.setEvent(input) });
341
+ qtdInputs += footer.querySelectorAll(`.input-var-${this.identifier}`).length
342
+ if (qtdInputs > 0 && footer.innerText) hasText = true
343
+ }
344
+
345
+ if (qtdInputs === 1 && !hasText) {
346
+ document.querySelector(`.input-var-${this.identifier}`).parentElement.style.width = "100%"
347
+ document.querySelector(`.input-var-${this.identifier}`).style.width = "100%"
348
+ } else if (qtdInputs === 0) {
349
+ this.$emit("set-vars", this.template)
350
+ }
351
+ },
352
+ setEvent(input) {
353
+ const varList = input.nextSibling
354
+
355
+ const currentInstance = this
356
+
357
+ input.setAttribute("placeholder", `{{var_${this.lastVar}}}`);
358
+ input.setAttribute("id", `{{var_${this.lastVar}}}`);
359
+ input.addEventListener("input", (event) => {
360
+ this.setVar(event, `var_${this.lastVar}`, true);
361
+ if (varList) varList.classList.remove("visible")
362
+ });
363
+ input.addEventListener("focus", () => {
364
+ input.classList.remove("invalid");
365
+ input.classList.add("active");
366
+ if (varList) {
367
+ const handleNextFocus = () => {
368
+ const allInputs = document.querySelectorAll(`.input-var-${currentInstance.identifier}`)
369
+ let stop = false
370
+ if (allInputs.length) {
371
+ allInputs.forEach(elem => {
372
+ if (!elem.value && !stop) {
373
+ stop = true
374
+ elem.focus()
375
+ }
376
+ })
377
+ }
378
+ if (!stop && this.$refs["template-single-button"]) this.$refs["template-single-button"].focus()
379
+ }
380
+
381
+ for (let i = 0; i < varList.children.length; i++) {
382
+ varList.children[i].addEventListener("click", function () {
383
+ input.value = `[${this.innerText}]`
384
+ input.classList.add("active")
385
+ input.dispatchEvent(new Event('input'))
386
+ handleNextFocus()
387
+ })
388
+ }
389
+
390
+ const containerGeral = document.querySelector(".tg-container")
391
+ varList.classList.add("visible")
392
+ const maxBottom = containerGeral.getBoundingClientRect().bottom,
393
+ currentBottom = varList.getBoundingClientRect().bottom
394
+ if (currentBottom > maxBottom) {
395
+ varList.style.top = `-${varList.getBoundingClientRect().height}px`
396
+ varList.style.borderTop = `1px solid currentColor`
397
+ }
398
+ }
399
+ });
400
+ input.addEventListener("blur", () => {
401
+ if (input.value) {
402
+ if (this.isValid(input.value)) {
403
+ input.classList.add("active");
404
+ } else {
405
+ input.classList.add("invalid");
406
+ }
407
+ } else {
408
+ input.classList.remove("invalid");
409
+ input.classList.remove("active");
410
+ }
411
+ if (varList) varList.classList.remove("visible")
412
+ });
413
+ input.addEventListener("keydown", (event) => {
414
+ if (event.keyCode == 13 && this.hasButton) this.$emit("click-trigger");
415
+ });
416
+ this.lastVar += 1;
417
+ },
418
+ toggleClass(index) {
419
+ const elem = this.$refs[`float-container-${index}`]
420
+ ? this.$refs[`float-container-${index}`][0]
421
+ : false;
422
+ if (elem) {
423
+ if (elem.children[1] && elem.children[1].value) {
424
+ elem.classList.add("active");
425
+ } else {
426
+ elem.classList.toggle("active");
427
+ }
428
+ }
429
+ },
430
+ isValid(textValue) {
431
+ const regex = {
432
+ htmlTags: /<\/?[\d\w\s=\-:\.\/\'\";]+>/gi,
433
+ enter: /\n/g,
434
+ consecutiveSpaces: /\s{3,}/g
435
+ }
436
+ const value = textValue ? textValue.trim("") : "";
437
+ if (!value.length) return false
438
+
439
+ let isValueValid = true
440
+ for (let key in regex) {
441
+ if (!isValueValid) return isValueValid
442
+ if (regex[key].test(value)) isValueValid = false
443
+ }
444
+
445
+ return isValueValid
446
+ },
447
+ setImageVar(file) {
448
+ this.$emit("set-file-var", file)
449
+ if (!file) {
450
+ this.headerFiles = []
451
+ return this.$forceUpdate()
452
+ } else {
453
+ if (this.lastVar == 0) return this.$emit("set-vars", this.template)
454
+ if (Object.keys(this.varValues).length) return this.$emit("set-vars", this.varValues)
455
+ this.$nextTick(() => this.handleInitialFocus())
456
+ }
457
+ },
458
+ setVar(event, key, notificar) {
459
+ if (event && event.target) {
460
+ key = event.target.id.replace(/[{}]+/g, '')
461
+ const value = event.target.value ? event.target.value.trim("") : ""
462
+ if (this.isValid(value)) {
463
+ this.varValues[key] = value;
464
+ } else {
465
+ if (notificar)
466
+ if (!document.querySelector(".toasted.toasted-primary.error"))
467
+ this.$toasted.global.defaultError({
468
+ msg: this.dictionary.template_msg_variavel_invalida,
469
+ });
470
+
471
+ delete this.varValues[key];
472
+ }
473
+ return this.$emit("set-vars", this.varValues);
474
+ }
475
+ },
476
+ }
477
+ };
478
+ </script>