@nyaruka/temba-components 0.71.4 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -1
- package/demo/index.html +6 -3
- package/dist/{beb1070a.js → c6d002ae.js} +400 -270
- package/dist/index.js +400 -270
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/dist/templates/components-body.html +1 -1
- package/dist/templates/components-head.html +1 -1
- package/out-tsc/src/RapidElement.js +18 -25
- package/out-tsc/src/RapidElement.js.map +1 -1
- package/out-tsc/src/select/Select.js +20 -7
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/templates/TemplateEditor.js +308 -0
- package/out-tsc/src/templates/TemplateEditor.js.map +1 -0
- package/out-tsc/src/textinput/TextInput.js +3 -1
- package/out-tsc/src/textinput/TextInput.js.map +1 -1
- package/out-tsc/src/utils/index.js +18 -0
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-slider.test.js +1 -1
- package/out-tsc/test/temba-slider.test.js.map +1 -1
- package/out-tsc/test/temba-template-editor.test.js +53 -0
- package/out-tsc/test/temba-template-editor.test.js.map +1 -0
- package/package.json +1 -1
- package/screenshots/truth/templates/default.png +0 -0
- package/screenshots/truth/templates/french.png +0 -0
- package/src/RapidElement.ts +19 -40
- package/src/select/Select.ts +21 -7
- package/src/templates/TemplateEditor.ts +348 -0
- package/src/textinput/TextInput.ts +3 -1
- package/src/utils/index.ts +21 -0
- package/static/api/templates.json +250 -0
- package/temba-modules.ts +2 -0
- package/test/temba-slider.test.ts +1 -1
- package/test/temba-template-editor.test.ts +69 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { property } from 'lit/decorators.js';
|
|
2
|
+
import { FormElement } from '../FormElement';
|
|
3
|
+
import { TemplateResult, html, css, PropertyValueMap } from 'lit';
|
|
4
|
+
import { CustomEventType } from '../interfaces';
|
|
5
|
+
|
|
6
|
+
const KEY_HEADER = 'header';
|
|
7
|
+
const KEY_BODY = 'body';
|
|
8
|
+
const KEY_FOOTER = 'footer';
|
|
9
|
+
const KEY_BUTTONS = 'buttons';
|
|
10
|
+
|
|
11
|
+
interface Component {
|
|
12
|
+
content: string;
|
|
13
|
+
params: { type: string }[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Translation {
|
|
17
|
+
locale: string;
|
|
18
|
+
status: string;
|
|
19
|
+
channel: { uuid: string; name: string };
|
|
20
|
+
components: { [key: string]: Component };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Template {
|
|
24
|
+
created_on: string;
|
|
25
|
+
modified_on: string;
|
|
26
|
+
name: string;
|
|
27
|
+
translations: Translation[];
|
|
28
|
+
uuid: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class TemplateEditor extends FormElement {
|
|
32
|
+
static get styles() {
|
|
33
|
+
return css`
|
|
34
|
+
.component {
|
|
35
|
+
background: #fff;
|
|
36
|
+
border: 1px solid var(--color-widget-border);
|
|
37
|
+
border-radius: var(--curvature);
|
|
38
|
+
padding: 1em;
|
|
39
|
+
margin-bottom: 0.5em;
|
|
40
|
+
}
|
|
41
|
+
.picker {
|
|
42
|
+
margin-bottom: 0.5em;
|
|
43
|
+
display: block;
|
|
44
|
+
}
|
|
45
|
+
.param {
|
|
46
|
+
display: flex;
|
|
47
|
+
margin-bottom: 0.5em;
|
|
48
|
+
align-items: center;
|
|
49
|
+
}
|
|
50
|
+
label {
|
|
51
|
+
margin-right: 0.5em;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.content span {
|
|
55
|
+
margin-right: 0.25em;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.error-message {
|
|
59
|
+
padding-left: 0.5em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.variable {
|
|
63
|
+
display: inline-block;
|
|
64
|
+
margin: 0.25em 0em;
|
|
65
|
+
margin-right: 0.25em;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.button-wrapper {
|
|
69
|
+
margin-top: 1em;
|
|
70
|
+
background: #f9f9f9;
|
|
71
|
+
border-radius: var(--curvature);
|
|
72
|
+
padding: 0.5em;
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.button-header {
|
|
78
|
+
font-weight: normal;
|
|
79
|
+
margin-left: 0.25em;
|
|
80
|
+
margin-bottom: -0.5em;
|
|
81
|
+
font-size: 0.9em;
|
|
82
|
+
color: #777;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.buttons {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.button {
|
|
93
|
+
background: #fff;
|
|
94
|
+
padding: 0.3em 1em;
|
|
95
|
+
border: 1px solid #e6e6e6;
|
|
96
|
+
border-radius: var(--curvature);
|
|
97
|
+
min-height: 23px;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
margin-right: 0.5em;
|
|
101
|
+
margin-top: 0.5em;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
temba-textinput,
|
|
105
|
+
temba-completion {
|
|
106
|
+
--temba-textinput-padding: 5px 5px;
|
|
107
|
+
--temba-textinput-font-size: 0.9em;
|
|
108
|
+
line-height: initial;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.template {
|
|
112
|
+
background: #fff;
|
|
113
|
+
border-radius: var(--curvature);
|
|
114
|
+
border: 1px solid var(--color-widget-border);
|
|
115
|
+
padding: 1em;
|
|
116
|
+
line-height: 2.2em;
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@property({ type: String })
|
|
122
|
+
url: string;
|
|
123
|
+
|
|
124
|
+
// initial template uuid
|
|
125
|
+
@property({ type: String })
|
|
126
|
+
template: string;
|
|
127
|
+
|
|
128
|
+
@property({ type: Object })
|
|
129
|
+
selectedTemplate: Template;
|
|
130
|
+
|
|
131
|
+
@property({ type: String })
|
|
132
|
+
lang = 'eng-US';
|
|
133
|
+
|
|
134
|
+
// component key to array of strings for variables
|
|
135
|
+
@property({ type: Object })
|
|
136
|
+
params: { [key: string]: string[] };
|
|
137
|
+
|
|
138
|
+
@property({ type: Object, attribute: false })
|
|
139
|
+
translation: Translation;
|
|
140
|
+
|
|
141
|
+
@property({ type: Boolean })
|
|
142
|
+
translating: boolean;
|
|
143
|
+
|
|
144
|
+
buttonKeys = [];
|
|
145
|
+
contentKeys = [];
|
|
146
|
+
otherKeys = [];
|
|
147
|
+
|
|
148
|
+
public firstUpdated(
|
|
149
|
+
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
150
|
+
): void {
|
|
151
|
+
super.firstUpdated(changes);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public updated(changedProperties: Map<string, any>): void {
|
|
155
|
+
super.updated(changedProperties);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private handleTemplateChanged(event: CustomEvent) {
|
|
159
|
+
this.selectedTemplate = (event.target as any).values[0] as Template;
|
|
160
|
+
const [lang, loc] = this.lang.split('-');
|
|
161
|
+
|
|
162
|
+
const newParams = {};
|
|
163
|
+
if (this.selectedTemplate) {
|
|
164
|
+
this.selectedTemplate.translations.forEach(translation => {
|
|
165
|
+
if (
|
|
166
|
+
translation.locale === this.lang ||
|
|
167
|
+
(!loc && translation.locale.split('-')[0] === lang)
|
|
168
|
+
) {
|
|
169
|
+
this.translation = translation;
|
|
170
|
+
this.buttonKeys = [];
|
|
171
|
+
this.contentKeys = [];
|
|
172
|
+
this.otherKeys = [];
|
|
173
|
+
const keys = Object.keys(translation.components);
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
if (key.startsWith(KEY_BUTTONS)) {
|
|
176
|
+
this.buttonKeys.push(key);
|
|
177
|
+
} else if (
|
|
178
|
+
key === KEY_HEADER ||
|
|
179
|
+
key === KEY_BODY ||
|
|
180
|
+
key === KEY_FOOTER
|
|
181
|
+
) {
|
|
182
|
+
this.contentKeys.push(key);
|
|
183
|
+
} else {
|
|
184
|
+
this.otherKeys.push(key);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const compParams = translation.components[key].params || [];
|
|
188
|
+
if (compParams.length > 0) {
|
|
189
|
+
// create an array for the length of params
|
|
190
|
+
newParams[key] = new Array(compParams.length).fill('');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this.buttonKeys.sort();
|
|
194
|
+
|
|
195
|
+
// if we are looking at the same template copy our params on top
|
|
196
|
+
if (this.template === this.selectedTemplate.uuid) {
|
|
197
|
+
for (const key of Object.keys(this.params || {})) {
|
|
198
|
+
if (newParams[key]) {
|
|
199
|
+
for (let i = 0; i < this.params[key].length; i++) {
|
|
200
|
+
newParams[key][i] = this.params[key][i];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
this.translation = null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.params = newParams;
|
|
212
|
+
this.fireCustomEvent(CustomEventType.ContextChanged, {
|
|
213
|
+
template: this.selectedTemplate,
|
|
214
|
+
translation: this.translation,
|
|
215
|
+
params: this.params,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private handleVariableChanged(event: CustomEvent) {
|
|
220
|
+
const target = event.target as HTMLInputElement;
|
|
221
|
+
const key = target.getAttribute('key');
|
|
222
|
+
const index = parseInt(target.getAttribute('index'));
|
|
223
|
+
this.params[key][index - 1] = target.value;
|
|
224
|
+
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
225
|
+
template: this.selectedTemplate,
|
|
226
|
+
translation: this.translation,
|
|
227
|
+
params: this.params,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private renderVariables(key: string, component: Component) {
|
|
232
|
+
const parts = component.content.split(/{{(\d+)}}/g);
|
|
233
|
+
if (parts.length > 0) {
|
|
234
|
+
const variables = parts.map((part, index) => {
|
|
235
|
+
const keyIndex = Math.round(index / 2);
|
|
236
|
+
if (index % 2 === 0) {
|
|
237
|
+
return html`<span class="text">${part}</span>`;
|
|
238
|
+
}
|
|
239
|
+
return html`<temba-completion
|
|
240
|
+
class="variable"
|
|
241
|
+
type="text"
|
|
242
|
+
value=${this.params[key][keyIndex - 1]}
|
|
243
|
+
@change=${this.handleVariableChanged}
|
|
244
|
+
key="${key}"
|
|
245
|
+
index="${keyIndex}}"
|
|
246
|
+
placeholder="variable.."
|
|
247
|
+
></temba-completion>`;
|
|
248
|
+
});
|
|
249
|
+
return html`<div class="content">${variables}</div>`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private renderComponent(key: string, component: Component) {
|
|
254
|
+
return html` <div class="component">
|
|
255
|
+
<div>${key}</div>
|
|
256
|
+
${this.renderVariables(key, component)}
|
|
257
|
+
</div>`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public renderContent(components: {
|
|
261
|
+
[key: string]: Component;
|
|
262
|
+
}): TemplateResult {
|
|
263
|
+
let header = null;
|
|
264
|
+
let body = null;
|
|
265
|
+
let footer = null;
|
|
266
|
+
|
|
267
|
+
if (components[KEY_HEADER]) {
|
|
268
|
+
header = html`<div class="header">
|
|
269
|
+
${this.renderVariables(KEY_HEADER, components[KEY_HEADER])}
|
|
270
|
+
</div>`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (components[KEY_BODY]) {
|
|
274
|
+
body = html`<div class="body">
|
|
275
|
+
${this.renderVariables(KEY_BODY, components[KEY_BODY])}
|
|
276
|
+
</div>`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (components[KEY_FOOTER]) {
|
|
280
|
+
footer = html`<div class="footer">
|
|
281
|
+
${this.renderVariables(KEY_FOOTER, components[KEY_FOOTER])}
|
|
282
|
+
</div>`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (header || body || footer) {
|
|
286
|
+
return html`<div class="content">${header}${body}${footer}</div>`;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public renderButtons(components): TemplateResult {
|
|
292
|
+
if (this.buttonKeys.length > 0) {
|
|
293
|
+
const buttons = this.buttonKeys.map(key => {
|
|
294
|
+
const component = components[key];
|
|
295
|
+
return html`<div class="button">
|
|
296
|
+
${this.renderVariables(key, component)}
|
|
297
|
+
</div>`;
|
|
298
|
+
});
|
|
299
|
+
return html`<div class="button-wrapper">
|
|
300
|
+
<div class="button-header">Template Buttons</div>
|
|
301
|
+
<div class="buttons">${buttons}</div>
|
|
302
|
+
</div>`;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
public render(): TemplateResult {
|
|
307
|
+
let content = null;
|
|
308
|
+
let buttons = null;
|
|
309
|
+
let otherComponents = null;
|
|
310
|
+
if (this.translation) {
|
|
311
|
+
content = this.renderContent(this.translation.components);
|
|
312
|
+
buttons = this.renderButtons(this.translation.components);
|
|
313
|
+
otherComponents = this.otherKeys.map(key => {
|
|
314
|
+
const component = this.translation.components[key];
|
|
315
|
+
return this.renderComponent(key, component);
|
|
316
|
+
});
|
|
317
|
+
} else {
|
|
318
|
+
otherComponents = html`<div class="error-message">
|
|
319
|
+
No approved translation was found for current language.
|
|
320
|
+
</div>`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return html`
|
|
324
|
+
<div>
|
|
325
|
+
<temba-select
|
|
326
|
+
searchable
|
|
327
|
+
?clearable=${!this.translating}
|
|
328
|
+
?disabled=${this.translating}
|
|
329
|
+
valuekey="uuid"
|
|
330
|
+
class="picker"
|
|
331
|
+
value="${this.template}"
|
|
332
|
+
endpoint=${this.url}
|
|
333
|
+
shouldExclude=${template => template.status !== 'approved'}
|
|
334
|
+
placeholder="Select a template"
|
|
335
|
+
@temba-content-changed=${this.swallowEvent}
|
|
336
|
+
@change=${this.handleTemplateChanged}
|
|
337
|
+
>
|
|
338
|
+
</temba-select>
|
|
339
|
+
|
|
340
|
+
${this.template
|
|
341
|
+
? html` <div class="template">
|
|
342
|
+
${content} ${buttons} ${otherComponents}
|
|
343
|
+
</div>`
|
|
344
|
+
: null}
|
|
345
|
+
</div>
|
|
346
|
+
`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -183,7 +183,9 @@ export class TextInput extends FormElement {
|
|
|
183
183
|
super.updated(changes);
|
|
184
184
|
|
|
185
185
|
if (changes.has('value')) {
|
|
186
|
-
|
|
186
|
+
if (changes.get('value') !== undefined) {
|
|
187
|
+
this.fireEvent('change');
|
|
188
|
+
}
|
|
187
189
|
|
|
188
190
|
if (this.textarea && this.autogrow) {
|
|
189
191
|
const autogrow = this.shadowRoot.querySelector(
|
package/src/utils/index.ts
CHANGED
|
@@ -27,6 +27,27 @@ export interface ResultsPage {
|
|
|
27
27
|
results: any[];
|
|
28
28
|
next: string;
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
export enum Color {
|
|
32
|
+
BLUE = 'color:#5078b5;',
|
|
33
|
+
GREEN = 'color:#62bd6a;',
|
|
34
|
+
RED = 'color:#e36049;',
|
|
35
|
+
PURPLE = 'color:#a626a4;',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const log = (message: string | object, styling = '', details = []) => {
|
|
39
|
+
if (styling === '') {
|
|
40
|
+
console.log(message);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof message === 'object') {
|
|
45
|
+
console.log('%c' + JSON.stringify(message, null, 2), styling);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log('%c' + message, styling, ...details);
|
|
49
|
+
};
|
|
50
|
+
|
|
30
51
|
/** Get the value for a named cookie */
|
|
31
52
|
export const getHTTPCookie = (name: string): string => {
|
|
32
53
|
for (const cookie of document.cookie.split(';')) {
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
{
|
|
2
|
+
"next": null,
|
|
3
|
+
"previous": null,
|
|
4
|
+
"results": [
|
|
5
|
+
{
|
|
6
|
+
"uuid": "4b6d0ce7-ada8-4a88-82f3-7fa775d9671a",
|
|
7
|
+
"name": "Approved",
|
|
8
|
+
"translations": [
|
|
9
|
+
{
|
|
10
|
+
"channel": {
|
|
11
|
+
"uuid": "4da11872-9299-4649-8672-78257d7b72d0",
|
|
12
|
+
"name": "WhatsApp"
|
|
13
|
+
},
|
|
14
|
+
"namespace": "",
|
|
15
|
+
"locale": "eng-US",
|
|
16
|
+
"status": "approved",
|
|
17
|
+
"components": {
|
|
18
|
+
"body": {
|
|
19
|
+
"content": "Hi bob {{1}} from {{2}}",
|
|
20
|
+
"params": [
|
|
21
|
+
{
|
|
22
|
+
"type": "text"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "text"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"buttons.0": {
|
|
30
|
+
"content": "Yes",
|
|
31
|
+
"params": []
|
|
32
|
+
},
|
|
33
|
+
"buttons.1": {
|
|
34
|
+
"content": "No {{1}}",
|
|
35
|
+
"params": [
|
|
36
|
+
{
|
|
37
|
+
"type": "text"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"channel": {
|
|
45
|
+
"uuid": "4da11872-9299-4649-8672-78257d7b72d0",
|
|
46
|
+
"name": "WhatsApp"
|
|
47
|
+
},
|
|
48
|
+
"namespace": "",
|
|
49
|
+
"locale": "fra-FR",
|
|
50
|
+
"status": "approved",
|
|
51
|
+
"components": [
|
|
52
|
+
{
|
|
53
|
+
"body": {
|
|
54
|
+
"content": "bon jour bob {{1}} from {{2}}",
|
|
55
|
+
"params": [
|
|
56
|
+
{
|
|
57
|
+
"type": "text"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"type": "text"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"buttons.0": {
|
|
65
|
+
"content": "Yes",
|
|
66
|
+
"params": []
|
|
67
|
+
},
|
|
68
|
+
"buttons.1": {
|
|
69
|
+
"content": "No {{1}}",
|
|
70
|
+
"params": [
|
|
71
|
+
{
|
|
72
|
+
"type": "text"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"created_on": "2023-09-08T22:38:22.703997Z",
|
|
81
|
+
"modified_on": "2023-09-08T22:38:22.707271Z"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"uuid": "7b6d0ce7-ada8-4a88-82f3-7fa775d9671a",
|
|
85
|
+
"name": "Testing",
|
|
86
|
+
"translations": [
|
|
87
|
+
{
|
|
88
|
+
"channel": {
|
|
89
|
+
"uuid": "4da11872-9299-4649-8672-78257d7b72d0",
|
|
90
|
+
"name": "WhatsApp"
|
|
91
|
+
},
|
|
92
|
+
"namespace": "",
|
|
93
|
+
"locale": "eng-US",
|
|
94
|
+
"status": "approved",
|
|
95
|
+
"components": {
|
|
96
|
+
"body": {
|
|
97
|
+
"content": "Hi bob {{1}} from {{2}}",
|
|
98
|
+
"params": [
|
|
99
|
+
{
|
|
100
|
+
"type": "text"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"type": "text"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"buttons.0": {
|
|
108
|
+
"content": "Yes",
|
|
109
|
+
"params": []
|
|
110
|
+
},
|
|
111
|
+
"buttons.1": {
|
|
112
|
+
"content": "No {{1}}",
|
|
113
|
+
"params": [
|
|
114
|
+
{
|
|
115
|
+
"type": "text"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"channel": {
|
|
123
|
+
"uuid": "4da11872-9299-4649-8672-78257d7b72d0",
|
|
124
|
+
"name": "WhatsApp"
|
|
125
|
+
},
|
|
126
|
+
"namespace": "",
|
|
127
|
+
"locale": "fra-FR",
|
|
128
|
+
"status": "pending",
|
|
129
|
+
"components": [
|
|
130
|
+
{
|
|
131
|
+
"body": {
|
|
132
|
+
"content": "bon jour bob {{1}} from {{2}}",
|
|
133
|
+
"params": [
|
|
134
|
+
{
|
|
135
|
+
"type": "text"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"type": "text"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
"buttons.0": {
|
|
143
|
+
"content": "Yes",
|
|
144
|
+
"params": []
|
|
145
|
+
},
|
|
146
|
+
"buttons.1": {
|
|
147
|
+
"content": "No {{1}}",
|
|
148
|
+
"params": [
|
|
149
|
+
{
|
|
150
|
+
"type": "text"
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"created_on": "2023-09-08T22:38:22.703997Z",
|
|
159
|
+
"modified_on": "2023-09-08T22:38:22.707271Z"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"uuid": "580b124f-32cb-4003-b9e5-9eb783e29101",
|
|
163
|
+
"name": "Car Warranty",
|
|
164
|
+
"translations": [
|
|
165
|
+
{
|
|
166
|
+
"channel": {
|
|
167
|
+
"uuid": "4da11872-9299-4649-8672-78257d7b72d0",
|
|
168
|
+
"name": "WhatsApp"
|
|
169
|
+
},
|
|
170
|
+
"namespace": "",
|
|
171
|
+
"locale": "eng-US",
|
|
172
|
+
"status": "approved",
|
|
173
|
+
"components": {
|
|
174
|
+
"body": {
|
|
175
|
+
"content": "Hi there, we are trying to reach {{1}} about their car warranty. It turns out you only have until {{2}} to decide whether to extend it further. Please contact us at {{3}} at your earliest convenience to discuss your options. Are you still interested?",
|
|
176
|
+
"params": [
|
|
177
|
+
{
|
|
178
|
+
"type": "text"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"type": "text"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"type": "text"
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"buttons.0": {
|
|
189
|
+
"content": "Yes",
|
|
190
|
+
"params": []
|
|
191
|
+
},
|
|
192
|
+
"buttons.1": {
|
|
193
|
+
"content": "No"
|
|
194
|
+
},
|
|
195
|
+
"buttons.2": {
|
|
196
|
+
"content": "Wait until {{1}}",
|
|
197
|
+
"params": [
|
|
198
|
+
{
|
|
199
|
+
"type": "text"
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"channel": {
|
|
207
|
+
"uuid": "4da11872-9299-4649-8672-78257d7b72d0",
|
|
208
|
+
"name": "WhatsApp"
|
|
209
|
+
},
|
|
210
|
+
"namespace": "",
|
|
211
|
+
"locale": "fra-FR",
|
|
212
|
+
"status": "pending",
|
|
213
|
+
"components": {
|
|
214
|
+
"body": {
|
|
215
|
+
"content": "Bonjour, nous essayons d'atteindre {{1}} à propos de leur garantie automobile. Il s'avère que tu n'as que jusqu'à {{2}} pour décider s'il convient de le prolonger davantage. Veuillez nous contacter au {{3}} dès que possible pour discuter de vos options. Êtes vous toujours intéressé?",
|
|
216
|
+
"params": [
|
|
217
|
+
{
|
|
218
|
+
"type": "text"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"type": "text"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"type": "text"
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
"buttons.0": {
|
|
229
|
+
"content": "Oui",
|
|
230
|
+
"params": []
|
|
231
|
+
},
|
|
232
|
+
"buttons.1": {
|
|
233
|
+
"content": "No"
|
|
234
|
+
},
|
|
235
|
+
"buttons.2": {
|
|
236
|
+
"content": "Attendre jusqu'à {{1}}",
|
|
237
|
+
"params": [
|
|
238
|
+
{
|
|
239
|
+
"type": "text"
|
|
240
|
+
}
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
"created_on": "2023-09-08T22:38:22.710140Z",
|
|
247
|
+
"modified_on": "2023-09-08T22:38:22.710141Z"
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
package/temba-modules.ts
CHANGED
|
@@ -54,6 +54,7 @@ import { WebChat } from './src/webchat/WebChat';
|
|
|
54
54
|
import { ImagePicker } from './src/imagepicker/ImagePicker';
|
|
55
55
|
import { Mask } from './src/mask/Mask';
|
|
56
56
|
import { TembaUser } from './src/user/TembaUser';
|
|
57
|
+
import { TemplateEditor } from './src/templates/TemplateEditor';
|
|
57
58
|
|
|
58
59
|
export function addCustomElement(name: string, comp: any) {
|
|
59
60
|
if (!window.customElements.get(name)) {
|
|
@@ -118,3 +119,4 @@ addCustomElement('temba-webchat', WebChat);
|
|
|
118
119
|
addCustomElement('temba-image-picker', ImagePicker);
|
|
119
120
|
addCustomElement('temba-mask', Mask);
|
|
120
121
|
addCustomElement('temba-user', TembaUser);
|
|
122
|
+
addCustomElement('temba-template-editor', TemplateEditor);
|
|
@@ -6,7 +6,7 @@ import { assertScreenshot, getClip, showMouse } from './utils.test';
|
|
|
6
6
|
const createSlider = async (def: TemplateResult) => {
|
|
7
7
|
const parentNode = document.createElement('div');
|
|
8
8
|
parentNode.setAttribute('style', 'width: 200px;');
|
|
9
|
-
return
|
|
9
|
+
return await fixture<TembaSlider>(def, { parentNode });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
describe('temba-slider', () => {
|