@nyaruka/temba-components 0.108.7 → 0.109.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 +19 -2
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +602 -455
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/charcount/CharCount.js +4 -5
- package/out-tsc/src/charcount/CharCount.js.map +1 -1
- package/out-tsc/src/completion/Completion.js +27 -16
- package/out-tsc/src/completion/Completion.js.map +1 -1
- package/out-tsc/src/compose/Compose.js +259 -95
- package/out-tsc/src/compose/Compose.js.map +1 -1
- package/out-tsc/src/contacts/ContactChat.js +18 -16
- package/out-tsc/src/contacts/ContactChat.js.map +1 -1
- package/out-tsc/src/contacts/ContactTickets.js +1 -1
- package/out-tsc/src/contacts/ContactTickets.js.map +1 -1
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/ShortcutList.js +125 -0
- package/out-tsc/src/list/ShortcutList.js.map +1 -0
- package/out-tsc/src/list/TembaList.js +8 -5
- package/out-tsc/src/list/TembaList.js.map +1 -1
- package/out-tsc/src/options/Options.js +46 -35
- package/out-tsc/src/options/Options.js.map +1 -1
- package/out-tsc/src/select/Select.js +1 -1
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/store/Store.js +18 -3
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/tabpane/Tab.js +2 -0
- package/out-tsc/src/tabpane/Tab.js.map +1 -1
- package/out-tsc/src/tabpane/TabPane.js +27 -5
- package/out-tsc/src/tabpane/TabPane.js.map +1 -1
- package/out-tsc/src/textinput/TextInput.js +7 -2
- package/out-tsc/src/textinput/TextInput.js.map +1 -1
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +2 -2
- package/out-tsc/src/vectoricon/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-compose.test.js +26 -18
- package/out-tsc/test/temba-compose.test.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +27 -18
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/compose/attachments-and-send-button.png +0 -0
- package/screenshots/truth/compose/attachments-no-send-button.png +0 -0
- package/screenshots/truth/compose/attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/attachments-with-all-files.png +0 -0
- package/screenshots/truth/compose/attachments-with-failure-files.png +0 -0
- package/screenshots/truth/compose/attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/attachments-with-success-files.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-no-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-attachments-no-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-no-counter-and-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-no-counter-no-send-button.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-failure-files.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-spaces.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-and-url.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-no-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-failure-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-click-send.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-hit-enter.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text-no-spaces.png +0 -0
- package/screenshots/truth/compose/chatbox-with-text.png +0 -0
- package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
- package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
- package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
- package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
- package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
- package/screenshots/truth/contacts/contact-active-default.png +0 -0
- package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
- package/screenshots/truth/counter/summary.png +0 -0
- package/screenshots/truth/counter/text.png +0 -0
- package/screenshots/truth/counter/unicode-variables.png +0 -0
- package/screenshots/truth/counter/unicode.png +0 -0
- package/screenshots/truth/counter/variable.png +0 -0
- package/src/charcount/CharCount.ts +4 -5
- package/src/completion/Completion.ts +33 -19
- package/src/compose/Compose.ts +289 -96
- package/src/contacts/ContactChat.ts +18 -16
- package/src/contacts/ContactTickets.ts +1 -1
- package/src/interfaces.ts +7 -0
- package/src/list/ShortcutList.ts +137 -0
- package/src/list/TembaList.ts +9 -6
- package/src/options/Options.ts +53 -44
- package/src/select/Select.ts +1 -1
- package/src/store/Store.ts +23 -4
- package/src/tabpane/Tab.ts +2 -0
- package/src/tabpane/TabPane.ts +28 -5
- package/src/textinput/TextInput.ts +9 -3
- package/src/utils/index.ts +8 -2
- package/src/vectoricon/index.ts +2 -2
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/zap-fast.svg +1 -0
- package/static/svg/work/used/zap-fast.svg +3 -0
- package/temba-modules.ts +2 -0
- package/test/temba-compose.test.ts +28 -35
- package/test/temba-contact-chat.test.ts +28 -37
- package/test-assets/store/shortcuts.json +14 -0
- package/static/svg/work/traced/message-dots-circle.svg +0 -1
- package/static/svg/work/used/message-dots-circle.svg +0 -3
|
@@ -47,15 +47,14 @@ export class CharCount extends RapidElement {
|
|
|
47
47
|
overflow: hidden;
|
|
48
48
|
opacity: 0.3;
|
|
49
49
|
transform: scale(0.7);
|
|
50
|
-
box-shadow: var(--shadow);
|
|
50
|
+
box-shadow: var(--dropdown-shadow);
|
|
51
51
|
transition: transform cubic-bezier(0.71, 0.18, 0.61, 1.33)
|
|
52
52
|
var(--transition-speed);
|
|
53
53
|
visibility: hidden;
|
|
54
|
-
margin-top: var(--temba-charcount-summary-margin-top);
|
|
55
|
-
right: var(--temba-charcount-summary-right);
|
|
56
|
-
bottom: var(--temba-charcount-summary-bottom);
|
|
57
54
|
text-align: left;
|
|
58
|
-
position:
|
|
55
|
+
position: fixed;
|
|
56
|
+
margin-left: -190px;
|
|
57
|
+
margin-top: 20px;
|
|
59
58
|
z-index: 1000;
|
|
60
59
|
}
|
|
61
60
|
|
|
@@ -116,6 +116,9 @@ export class Completion extends FormElement {
|
|
|
116
116
|
@property({ type: Boolean })
|
|
117
117
|
gsm: boolean;
|
|
118
118
|
|
|
119
|
+
@property({ type: Boolean })
|
|
120
|
+
disableCompletion: boolean;
|
|
121
|
+
|
|
119
122
|
@property({ type: String })
|
|
120
123
|
counter: string;
|
|
121
124
|
|
|
@@ -140,30 +143,32 @@ export class Completion extends FormElement {
|
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
private handleKeyUp(evt: KeyboardEvent) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
if (this.disableCompletion) {
|
|
147
|
+
// if we have options, ignore keys that are meant for them
|
|
148
|
+
if (this.options && this.options.length > 0) {
|
|
149
|
+
if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown') {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (evt.ctrlKey) {
|
|
154
|
+
if (evt.key === 'n' || evt.key === 'p') {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
148
158
|
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
if (
|
|
160
|
+
evt.key === 'Enter' ||
|
|
161
|
+
evt.key === 'Escape' ||
|
|
162
|
+
evt.key === 'Tab' ||
|
|
163
|
+
evt.key.startsWith('Control')
|
|
164
|
+
) {
|
|
165
|
+
evt.stopPropagation();
|
|
166
|
+
evt.preventDefault();
|
|
151
167
|
return;
|
|
152
168
|
}
|
|
153
|
-
}
|
|
154
169
|
|
|
155
|
-
|
|
156
|
-
evt.key === 'Enter' ||
|
|
157
|
-
evt.key === 'Escape' ||
|
|
158
|
-
evt.key === 'Tab' ||
|
|
159
|
-
evt.key.startsWith('Control')
|
|
160
|
-
) {
|
|
161
|
-
evt.stopPropagation();
|
|
162
|
-
evt.preventDefault();
|
|
163
|
-
return;
|
|
170
|
+
this.executeQuery(evt.currentTarget as TextInput);
|
|
164
171
|
}
|
|
165
|
-
|
|
166
|
-
this.executeQuery(evt.currentTarget as TextInput);
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
|
|
@@ -172,10 +177,15 @@ export class Completion extends FormElement {
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
private executeQuery(ele: TextInput) {
|
|
180
|
+
if (this.disableCompletion) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
const store: Store = document.querySelector('temba-store');
|
|
176
185
|
if (!ele.inputElement) {
|
|
177
186
|
return;
|
|
178
187
|
}
|
|
188
|
+
|
|
179
189
|
const result = executeCompletionQuery(
|
|
180
190
|
ele.inputElement,
|
|
181
191
|
store,
|
|
@@ -232,6 +242,10 @@ export class Completion extends FormElement {
|
|
|
232
242
|
}
|
|
233
243
|
}
|
|
234
244
|
|
|
245
|
+
public getTextInput(): TextInput {
|
|
246
|
+
return this.textInputElement;
|
|
247
|
+
}
|
|
248
|
+
|
|
235
249
|
public click() {
|
|
236
250
|
super.click();
|
|
237
251
|
const input = this.shadowRoot.querySelector('temba-textinput') as TextInput;
|
package/src/compose/Compose.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { TemplateResult, html, css } from 'lit';
|
|
2
2
|
import { FormElement } from '../FormElement';
|
|
3
3
|
import { property } from 'lit/decorators.js';
|
|
4
|
-
import { Attachment, CustomEventType, Language } from '../interfaces';
|
|
4
|
+
import { Attachment, CustomEventType, Language, Shortcut } from '../interfaces';
|
|
5
5
|
import { DEFAULT_MEDIA_ENDPOINT, getClasses } from '../utils';
|
|
6
6
|
import { Completion } from '../completion/Completion';
|
|
7
7
|
import { Select } from '../select/Select';
|
|
8
8
|
import { TabPane } from '../tabpane/TabPane';
|
|
9
9
|
import { MediaPicker } from '../mediapicker/MediaPicker';
|
|
10
10
|
import { Tab } from '../tabpane/Tab';
|
|
11
|
+
import { TextInput } from '../textinput/TextInput';
|
|
12
|
+
import { ShortcutList } from '../list/ShortcutList';
|
|
11
13
|
|
|
12
14
|
export class Compose extends FormElement {
|
|
13
15
|
static get styles() {
|
|
14
16
|
return css`
|
|
15
17
|
:host {
|
|
16
|
-
--textarea-min-height: var(--textarea-min-height, 4em);
|
|
17
18
|
overflow: hidden;
|
|
18
19
|
border-top-right-radius: var(--curvature);
|
|
19
20
|
border-top-left-radius: var(--curvature);
|
|
@@ -32,13 +33,15 @@ export class Compose extends FormElement {
|
|
|
32
33
|
flex-direction: column;
|
|
33
34
|
justify-content: space-between;
|
|
34
35
|
position: relative;
|
|
35
|
-
|
|
36
|
-
border-radius: var(--curvature-widget);
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
border-radius: var(--compose-curvature, var(--curvature-widget));
|
|
37
38
|
background: var(--color-widget-bg);
|
|
38
39
|
border: var(--compose-border, 1px solid var(--color-widget-border));
|
|
39
40
|
transition: all ease-in-out var(--transition-speed);
|
|
40
41
|
box-shadow: var(--compose-shadow, var(--widget-box-shadow));
|
|
41
42
|
caret-color: var(--input-caret);
|
|
43
|
+
--color-widget-bg-focused: transparent;
|
|
44
|
+
--color-widget-bg: transparent;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
.chatbox {
|
|
@@ -49,7 +52,10 @@ export class Compose extends FormElement {
|
|
|
49
52
|
);
|
|
50
53
|
|
|
51
54
|
--widget-box-shadow: none;
|
|
52
|
-
|
|
55
|
+
display: block;
|
|
56
|
+
flex-grow: 1;
|
|
57
|
+
--widget-box-shadow-focused: none;
|
|
58
|
+
--temba-textinput-padding: 1em 1em;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
.actions {
|
|
@@ -58,9 +64,6 @@ export class Compose extends FormElement {
|
|
|
58
64
|
align-items: center;
|
|
59
65
|
padding: 0em;
|
|
60
66
|
background: #f9f9f9;
|
|
61
|
-
border-bottom-left-radius: var(--curvature);
|
|
62
|
-
border-bottom-right-radius: var(--curvature);
|
|
63
|
-
border-top: solid 1px var(--color-widget-border);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
.actions-right {
|
|
@@ -86,6 +89,7 @@ export class Compose extends FormElement {
|
|
|
86
89
|
.send-error {
|
|
87
90
|
color: rgba(250, 0, 0, 0.75);
|
|
88
91
|
font-size: var(--help-text-size);
|
|
92
|
+
padding: 0.5em;
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
.language {
|
|
@@ -98,12 +102,19 @@ export class Compose extends FormElement {
|
|
|
98
102
|
display: flex;
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
.gutter {
|
|
106
|
+
align-items: center;
|
|
107
|
+
display: flex;
|
|
108
|
+
margin: 0.5em;
|
|
109
|
+
}
|
|
110
|
+
|
|
101
111
|
#send-button {
|
|
102
112
|
margin: 0.3em;
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
temba-tabs {
|
|
106
116
|
--focused-tab-color: #f4f4f4;
|
|
117
|
+
min-height: var(--compose-min-height, 13.5em);
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
.quick-replies {
|
|
@@ -111,14 +122,47 @@ export class Compose extends FormElement {
|
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
.optins {
|
|
114
|
-
|
|
125
|
+
margin: 0.8em;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.templates {
|
|
129
|
+
margin: 0.8em;
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
.attachments {
|
|
133
|
+
min-height: 5em;
|
|
134
|
+
padding: 0.2em;
|
|
135
|
+
align-items: center;
|
|
136
|
+
display: flex;
|
|
137
|
+
background: #f9f9f9;
|
|
138
|
+
border-radius: var(--curvature);
|
|
139
|
+
margin: 0.6em;
|
|
140
|
+
margin-bottom: 0em;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.pane-bottom {
|
|
144
|
+
border: 0px solid red;
|
|
145
|
+
--color-placeholder: rgba(0, 0, 0, 0.2);
|
|
146
|
+
flex-grow: 99;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.shortcut-wrapper {
|
|
150
|
+
max-height: var(--shortcuts-height, 12em);
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: row;
|
|
153
|
+
align-items: stretch;
|
|
154
|
+
--options-block-shadow: none;
|
|
155
|
+
--curvature-widget: 0px;
|
|
156
|
+
--color-options-bg: #fff;
|
|
157
|
+
border-bottom: 1px solid var(--color-widget-border);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
temba-shortcuts {
|
|
161
|
+
flex-grow: 1;
|
|
118
162
|
}
|
|
119
163
|
|
|
120
|
-
|
|
121
|
-
|
|
164
|
+
.quick-replies {
|
|
165
|
+
background: #f9f9f9;
|
|
122
166
|
}
|
|
123
167
|
`;
|
|
124
168
|
}
|
|
@@ -239,6 +283,12 @@ export class Compose extends FormElement {
|
|
|
239
283
|
@property({ type: Object })
|
|
240
284
|
currentTab: Tab;
|
|
241
285
|
|
|
286
|
+
@property({ type: Boolean })
|
|
287
|
+
hasPendingText = false;
|
|
288
|
+
|
|
289
|
+
@property({ type: Object })
|
|
290
|
+
activeShortcut: Shortcut;
|
|
291
|
+
|
|
242
292
|
public constructor() {
|
|
243
293
|
super();
|
|
244
294
|
}
|
|
@@ -253,6 +303,14 @@ export class Compose extends FormElement {
|
|
|
253
303
|
private handleTabChanged() {
|
|
254
304
|
const tabs = this.shadowRoot.querySelector('temba-tabs') as TabPane;
|
|
255
305
|
this.currentTab = tabs.getCurrentTab();
|
|
306
|
+
|
|
307
|
+
if (this.currentTab && this.currentTab.name === 'Shortcuts') {
|
|
308
|
+
const shortcuts = this.shadowRoot.querySelector(
|
|
309
|
+
'temba-shortcuts'
|
|
310
|
+
) as ShortcutList;
|
|
311
|
+
shortcuts.filter = '';
|
|
312
|
+
}
|
|
313
|
+
this.setFocusOnChatbox();
|
|
256
314
|
}
|
|
257
315
|
|
|
258
316
|
public firstUpdated(changes: Map<string, any>): void {
|
|
@@ -354,14 +412,15 @@ export class Compose extends FormElement {
|
|
|
354
412
|
if (completion) {
|
|
355
413
|
window.setTimeout(() => {
|
|
356
414
|
completion.focus();
|
|
357
|
-
// this.resetTabs();
|
|
358
415
|
}, 0);
|
|
359
416
|
}
|
|
360
417
|
}
|
|
361
418
|
}
|
|
362
419
|
|
|
363
420
|
public reset(): void {
|
|
364
|
-
|
|
421
|
+
const completion = this.shadowRoot.querySelector('.chatbox') as Completion;
|
|
422
|
+
completion.textInputElement.value = '';
|
|
423
|
+
completion.value = '';
|
|
365
424
|
this.initialText = '';
|
|
366
425
|
this.currentText = '';
|
|
367
426
|
this.currentQuickReplies = [];
|
|
@@ -380,8 +439,28 @@ export class Compose extends FormElement {
|
|
|
380
439
|
}
|
|
381
440
|
|
|
382
441
|
private handleChatboxChange(evt: Event) {
|
|
383
|
-
const chatbox = evt.target as
|
|
384
|
-
|
|
442
|
+
const chatbox = evt.target as Completion;
|
|
443
|
+
const inputElement = chatbox.getTextInput().inputElement;
|
|
444
|
+
|
|
445
|
+
this.currentText = inputElement.value;
|
|
446
|
+
this.hasPendingText = inputElement.value.length > 0;
|
|
447
|
+
|
|
448
|
+
// is the last character a / and is it at the beginning of the line
|
|
449
|
+
const cursor = inputElement.selectionStart;
|
|
450
|
+
const text = inputElement.value;
|
|
451
|
+
const lineStart = text.lastIndexOf('\n', cursor - 1) + 1;
|
|
452
|
+
const line = text.substring(lineStart, cursor);
|
|
453
|
+
|
|
454
|
+
if (line.startsWith('/')) {
|
|
455
|
+
// switch to the shortcuts tab
|
|
456
|
+
const tabs = this.shadowRoot.querySelector('temba-tabs') as TabPane;
|
|
457
|
+
tabs.focusTab('Shortcuts');
|
|
458
|
+
|
|
459
|
+
const shortcuts = this.shadowRoot.querySelector(
|
|
460
|
+
'temba-shortcuts'
|
|
461
|
+
) as ShortcutList;
|
|
462
|
+
shortcuts.filter = line.substring(1);
|
|
463
|
+
}
|
|
385
464
|
}
|
|
386
465
|
|
|
387
466
|
public toggleButton() {
|
|
@@ -406,16 +485,68 @@ export class Compose extends FormElement {
|
|
|
406
485
|
this.handleSend();
|
|
407
486
|
}
|
|
408
487
|
|
|
409
|
-
private
|
|
410
|
-
|
|
488
|
+
private getCurrentLine(): { text: string; index: number } {
|
|
489
|
+
const chatbox = this.shadowRoot.querySelector('.chatbox') as Completion;
|
|
490
|
+
|
|
491
|
+
const cursor = chatbox.getTextInput().inputElement.selectionStart - 1;
|
|
492
|
+
const text = chatbox.value;
|
|
493
|
+
const start = text.substring(0, cursor).lastIndexOf('\n') + 1;
|
|
494
|
+
|
|
495
|
+
let end = chatbox.value.indexOf('\n', start);
|
|
496
|
+
if (end === -1) {
|
|
497
|
+
end = chatbox.value.length;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return { text: chatbox.value.substring(start, end), index: start };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private handleKeyDown(evt: KeyboardEvent) {
|
|
504
|
+
const tabs = this.shadowRoot.querySelector('temba-tabs') as TabPane;
|
|
505
|
+
const num = parseInt(evt.key);
|
|
506
|
+
if (
|
|
507
|
+
!Number.isNaN(num) &&
|
|
508
|
+
num > 0 &&
|
|
509
|
+
evt.ctrlKey &&
|
|
510
|
+
evt.metaKey &&
|
|
511
|
+
num <= tabs.tabs.length
|
|
512
|
+
) {
|
|
513
|
+
tabs.index = num - 1;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// if they type / as the first character in a line, switch to the shortcut
|
|
517
|
+
if (evt.key === '/' && this.currentTab.name !== 'Shortcuts') {
|
|
518
|
+
const line = this.getCurrentLine();
|
|
519
|
+
const text = line.text.trim();
|
|
520
|
+
if (text.trim().length === 1) {
|
|
521
|
+
evt.preventDefault();
|
|
522
|
+
tabs.index = tabs.tabs.findIndex((tab) => tab.name === 'Shortcuts');
|
|
523
|
+
}
|
|
524
|
+
} else if (evt.key === 'Backspace') {
|
|
525
|
+
const line = this.getCurrentLine();
|
|
526
|
+
const text = line.text;
|
|
527
|
+
if (text === '/') {
|
|
528
|
+
tabs.index = tabs.tabs.findIndex((tab) => tab.name === 'Reply');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (this.currentTab.name === 'Shortcuts') {
|
|
411
533
|
if (evt.key === 'Enter' && !evt.shiftKey) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (this.button) {
|
|
539
|
+
if (evt.key === 'Enter') {
|
|
540
|
+
if (!evt.shiftKey) {
|
|
541
|
+
evt.preventDefault();
|
|
542
|
+
if (this.completion) {
|
|
543
|
+
const chat = evt.target as Completion;
|
|
544
|
+
if (!chat.hasVisibleOptions()) {
|
|
545
|
+
this.handleSend();
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
415
548
|
this.handleSend();
|
|
416
549
|
}
|
|
417
|
-
} else {
|
|
418
|
-
this.handleSend();
|
|
419
550
|
}
|
|
420
551
|
}
|
|
421
552
|
}
|
|
@@ -435,7 +566,7 @@ export class Compose extends FormElement {
|
|
|
435
566
|
}
|
|
436
567
|
|
|
437
568
|
public resetTabs() {
|
|
438
|
-
(this.shadowRoot.querySelector('temba-tabs') as TabPane).index =
|
|
569
|
+
(this.shadowRoot.querySelector('temba-tabs') as TabPane).index = 0;
|
|
439
570
|
}
|
|
440
571
|
|
|
441
572
|
public render(): TemplateResult {
|
|
@@ -463,43 +594,12 @@ export class Compose extends FormElement {
|
|
|
463
594
|
</temba-select>`
|
|
464
595
|
: null}
|
|
465
596
|
<div class="container">
|
|
466
|
-
${this.chatbox ? html`${this.getChatbox()}` : null}
|
|
467
597
|
<div class="items actions">${this.getActions()}</div>
|
|
468
598
|
</div>
|
|
469
599
|
</temba-field>
|
|
470
600
|
`;
|
|
471
601
|
}
|
|
472
602
|
|
|
473
|
-
private getChatbox(): TemplateResult {
|
|
474
|
-
if (this.completion) {
|
|
475
|
-
return html`<temba-completion
|
|
476
|
-
class="chatbox"
|
|
477
|
-
.value=${this.initialText}
|
|
478
|
-
gsm
|
|
479
|
-
textarea
|
|
480
|
-
?autogrow=${this.autogrow}
|
|
481
|
-
maxlength=${this.maxLength}
|
|
482
|
-
@change=${this.handleChatboxChange}
|
|
483
|
-
@keydown=${this.handleSendEnter}
|
|
484
|
-
placeholder="Write something here"
|
|
485
|
-
>
|
|
486
|
-
</temba-completion>`;
|
|
487
|
-
} else {
|
|
488
|
-
return html`<temba-textinput
|
|
489
|
-
class="chatbox"
|
|
490
|
-
gsm
|
|
491
|
-
textarea
|
|
492
|
-
?autogrow=${this.autogrow}
|
|
493
|
-
maxlength=${this.maxLength}
|
|
494
|
-
.value=${this.initialText}
|
|
495
|
-
@change=${this.handleChatboxChange}
|
|
496
|
-
@keydown=${this.handleSendEnter}
|
|
497
|
-
placeholder="Write something here"
|
|
498
|
-
>
|
|
499
|
-
</temba-textinput>`;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
603
|
private handleTemplateChanged(evt: CustomEvent) {
|
|
504
604
|
this.currentTemplate = evt.detail.template;
|
|
505
605
|
this.locale = evt.detail.translation?.locale;
|
|
@@ -510,6 +610,48 @@ export class Compose extends FormElement {
|
|
|
510
610
|
this.variables = [...evt.detail.variables];
|
|
511
611
|
}
|
|
512
612
|
|
|
613
|
+
public getTextInput(): TextInput {
|
|
614
|
+
return (
|
|
615
|
+
this.shadowRoot.querySelector('.chatbox') as Completion
|
|
616
|
+
).getTextInput();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
public handleShortcutSelection(event: CustomEvent) {
|
|
620
|
+
this.activeShortcut = event.detail.selected;
|
|
621
|
+
const line = this.getCurrentLine();
|
|
622
|
+
const chatbox = this.getTextInput();
|
|
623
|
+
|
|
624
|
+
const originalText = chatbox.value;
|
|
625
|
+
|
|
626
|
+
if (line.text.startsWith('/')) {
|
|
627
|
+
const newText =
|
|
628
|
+
originalText.substring(0, line.index) +
|
|
629
|
+
this.activeShortcut.text +
|
|
630
|
+
originalText.substring(line.index + line.text.length);
|
|
631
|
+
|
|
632
|
+
chatbox.updateValue(newText);
|
|
633
|
+
|
|
634
|
+
// set our cursor to the end of the shortcut
|
|
635
|
+
const cursor = line.index + this.activeShortcut.text.length;
|
|
636
|
+
chatbox.inputElement.setSelectionRange(cursor, cursor);
|
|
637
|
+
} else {
|
|
638
|
+
// add the text where the cursor is
|
|
639
|
+
const cursor = chatbox.inputElement.selectionStart;
|
|
640
|
+
const newText =
|
|
641
|
+
originalText.substring(0, cursor) +
|
|
642
|
+
this.activeShortcut.text +
|
|
643
|
+
originalText.substring(cursor);
|
|
644
|
+
chatbox.updateValue(newText);
|
|
645
|
+
|
|
646
|
+
// set the cursor to the end of the shortcut text
|
|
647
|
+
const newCursor = cursor + this.activeShortcut.text.length;
|
|
648
|
+
chatbox.inputElement.setSelectionRange(newCursor, newCursor);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const tabs = this.shadowRoot.querySelector('temba-tabs') as TabPane;
|
|
652
|
+
tabs.index = tabs.tabs.findIndex((tab) => tab.name === 'Reply');
|
|
653
|
+
}
|
|
654
|
+
|
|
513
655
|
private getActions(): TemplateResult {
|
|
514
656
|
const showOptins = this.optIns && this.isBaseLanguage();
|
|
515
657
|
const showTemplates = this.templates && this.isBaseLanguage();
|
|
@@ -517,17 +659,22 @@ export class Compose extends FormElement {
|
|
|
517
659
|
<temba-tabs
|
|
518
660
|
embedded
|
|
519
661
|
focusedname
|
|
520
|
-
|
|
521
|
-
unselect
|
|
662
|
+
index="0"
|
|
522
663
|
@temba-context-changed=${this.handleTabChanged}
|
|
523
664
|
refresh="${(this.currentAttachments || []).length}|${this.index}|${this
|
|
524
665
|
.currentQuickReplies.length}|${showOptins}|${this
|
|
525
666
|
.currentOptin}|${showTemplates}|${this.currentTemplate}"
|
|
526
667
|
>
|
|
668
|
+
<temba-tab
|
|
669
|
+
name="Reply"
|
|
670
|
+
icon="message"
|
|
671
|
+
selectionBackground="#fff"
|
|
672
|
+
></temba-tab>
|
|
527
673
|
${this.attachments
|
|
528
674
|
? html`<temba-tab
|
|
529
675
|
name="Attachments"
|
|
530
676
|
icon="attachment"
|
|
677
|
+
selectionBackground="#fff"
|
|
531
678
|
.count=${(this.currentAttachments || []).length}
|
|
532
679
|
>
|
|
533
680
|
<div class="items attachments">
|
|
@@ -544,9 +691,11 @@ export class Compose extends FormElement {
|
|
|
544
691
|
? html`<temba-tab
|
|
545
692
|
name="Quick Replies"
|
|
546
693
|
icon="quick_replies"
|
|
694
|
+
selectionBackground="#fff"
|
|
547
695
|
.count=${this.currentQuickReplies.length}
|
|
548
696
|
>
|
|
549
697
|
<temba-select
|
|
698
|
+
class="quick-replies"
|
|
550
699
|
@change=${this.handleQuickReplyChange}
|
|
551
700
|
.values=${this.currentQuickReplies}
|
|
552
701
|
class="quick-replies"
|
|
@@ -558,48 +707,90 @@ export class Compose extends FormElement {
|
|
|
558
707
|
></temba-select>
|
|
559
708
|
</temba-tab>`
|
|
560
709
|
: null}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
710
|
+
${showOptins
|
|
711
|
+
? html`<temba-tab
|
|
712
|
+
name="Opt-in"
|
|
713
|
+
icon="channel_fba"
|
|
714
|
+
selectionBackground="#fff"
|
|
715
|
+
?hidden=${!showOptins}
|
|
716
|
+
?checked=${this.currentOptin.length > 0}
|
|
717
|
+
>
|
|
718
|
+
<temba-select
|
|
719
|
+
@change=${this.handleOptInChange}
|
|
720
|
+
.values=${this.currentOptin}
|
|
721
|
+
endpoint="${this.optinEndpoint}"
|
|
722
|
+
class="optins"
|
|
723
|
+
searchable
|
|
724
|
+
clearable
|
|
725
|
+
placeholder="Select an opt-in to use for Facebook (optional)"
|
|
726
|
+
></temba-select>
|
|
727
|
+
</temba-tab>`
|
|
728
|
+
: null}
|
|
729
|
+
${showTemplates
|
|
730
|
+
? html`<temba-tab
|
|
731
|
+
name="Template"
|
|
732
|
+
icon="channel_wa"
|
|
733
|
+
selectionBackground="#fff"
|
|
734
|
+
?alert=${this.errors &&
|
|
735
|
+
this.errors.find((error) => error.includes('template'))}
|
|
736
|
+
?hidden=${!showTemplates}
|
|
737
|
+
?checked=${this.currentTemplate}
|
|
738
|
+
>
|
|
739
|
+
<temba-template-editor
|
|
740
|
+
class="templates"
|
|
741
|
+
@temba-context-changed=${this.handleTemplateChanged}
|
|
742
|
+
@temba-content-changed=${this.handleTemplateVariablesChanged}
|
|
743
|
+
template=${this.template}
|
|
744
|
+
variables=${JSON.stringify(this.variables)}
|
|
745
|
+
url=${this.templateEndpoint}
|
|
746
|
+
lang=${this.currentLanguage}
|
|
747
|
+
>
|
|
748
|
+
</temba-template-editor>
|
|
749
|
+
</temba-tab>`
|
|
750
|
+
: null}
|
|
577
751
|
|
|
578
|
-
|
|
579
|
-
name="
|
|
580
|
-
icon="
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
lang=${this.currentLanguage}
|
|
593
|
-
>
|
|
594
|
-
</temba-template-editor>
|
|
752
|
+
<!--temba-tab
|
|
753
|
+
name="Note"
|
|
754
|
+
icon="notes"
|
|
755
|
+
activityColor="#ffbd00"
|
|
756
|
+
selectionBackground="#fff9c2"
|
|
757
|
+
borderColor="#ebdf6f"
|
|
758
|
+
></temba-tab-->
|
|
759
|
+
|
|
760
|
+
<temba-tab name="Shortcuts" icon="shortcut" selectionBackground="#fff">
|
|
761
|
+
<div class="shortcut-wrapper">
|
|
762
|
+
<temba-shortcuts
|
|
763
|
+
@temba-selection=${this.handleShortcutSelection}
|
|
764
|
+
></temba-shortcuts>
|
|
765
|
+
</div>
|
|
595
766
|
</temba-tab>
|
|
596
767
|
|
|
597
768
|
<div slot="tab-right" class="top-right">
|
|
769
|
+
${this.counter ? this.getCounter() : null}
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
<div
|
|
773
|
+
slot="pane-bottom"
|
|
774
|
+
class="pane-bottom ${this.hasPendingText ? 'pending' : ''}"
|
|
775
|
+
>
|
|
776
|
+
${this.chatbox
|
|
777
|
+
? html`<temba-completion
|
|
778
|
+
class="chatbox"
|
|
779
|
+
.value=${this.initialText}
|
|
780
|
+
gsm
|
|
781
|
+
textarea
|
|
782
|
+
?disableCompletion=${!this.completion}
|
|
783
|
+
?autogrow=${this.autogrow}
|
|
784
|
+
maxlength=${this.maxLength}
|
|
785
|
+
@change=${this.handleChatboxChange}
|
|
786
|
+
@keydown=${this.handleKeyDown}
|
|
787
|
+
placeholder="Write something here"
|
|
788
|
+
>
|
|
789
|
+
</temba-completion>`
|
|
790
|
+
: null}
|
|
598
791
|
${this.buttonError
|
|
599
792
|
? html`<div class="send-error">${this.buttonError}</div>`
|
|
600
793
|
: null}
|
|
601
|
-
${this.counter ? this.getCounter() : null}
|
|
602
|
-
${this.button ? this.getButton() : null}
|
|
603
794
|
</div>
|
|
604
795
|
</temba-tabs>
|
|
605
796
|
`;
|
|
@@ -612,11 +803,13 @@ export class Compose extends FormElement {
|
|
|
612
803
|
}
|
|
613
804
|
|
|
614
805
|
private getButton(): TemplateResult {
|
|
615
|
-
return html
|
|
616
|
-
|
|
617
|
-
|
|
806
|
+
return html`<temba-icon
|
|
807
|
+
tabindex="1"
|
|
808
|
+
class="send-icon"
|
|
809
|
+
name="send"
|
|
810
|
+
size="1"
|
|
811
|
+
clickable
|
|
618
812
|
@click=${this.handleSendClick}
|
|
619
|
-
|
|
620
|
-
></temba-button>`;
|
|
813
|
+
></temba-icon>`;
|
|
621
814
|
}
|
|
622
815
|
}
|