vue-intergrall-plugins 0.0.505 → 0.0.510

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