@nyaruka/temba-components 0.159.2 → 0.159.4
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 +30 -2
- package/dist/temba-components.js +1495 -1223
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/rollup.components.mjs +1 -0
- package/src/display/Chat.ts +44 -6
- package/src/display/TembaUser.ts +21 -9
- package/src/flow/NodeEditor.ts +33 -13
- package/src/flow/actions/add_input_labels.ts +4 -1
- package/src/flow/actions/send_msg.ts +1 -0
- package/src/flow/actions/set_run_result.ts +1 -0
- package/src/flow/nodes/split_by_ticket.ts +1 -0
- package/src/form/Checkbox.ts +37 -11
- package/src/layout/PageHeader.ts +35 -13
- package/src/list/ContactList.ts +69 -19
- package/src/list/ContentList.ts +711 -271
- package/src/list/MsgList.ts +39 -14
- package/src/live/ContactChat.ts +3 -0
- package/src/live/ContactTimeline.ts +1 -1
- package/src/store/Store.ts +41 -1
- package/web-dev-server.config.mjs +11 -0
- package/web-test-runner.config.mjs +1 -0
package/src/list/MsgList.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { Msg } from '../interfaces';
|
|
|
8
8
|
* `msgs/msg_list.html` table. Reverse-chronological; the message
|
|
9
9
|
* cell carries the body text with its attachment thumbnails right
|
|
10
10
|
* after it and the flow / label pills pushed to the trailing edge,
|
|
11
|
-
* with a
|
|
11
|
+
* with a timedate timestamp closing the row.
|
|
12
12
|
*/
|
|
13
13
|
export class MsgList extends ContentList<Msg> {
|
|
14
14
|
static get styles() {
|
|
@@ -100,11 +100,13 @@ export class MsgList extends ContentList<Msg> {
|
|
|
100
100
|
this.searchPlaceholder = 'Search messages';
|
|
101
101
|
// Messages page 100 at a time, matching rapidpro's msg list.
|
|
102
102
|
this.pageSize = 100;
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
103
|
+
// Auto layout so the Contact / Sent columns size to their content
|
|
104
|
+
// (no stranded width on the short Sent value), while the Message
|
|
105
|
+
// column is a `grow` column — under auto layout it claims zero
|
|
106
|
+
// intrinsic width and ellipsis-truncates against the leftover space
|
|
107
|
+
// rather than stretching the table. minTableWidth lets the list
|
|
108
|
+
// scroll horizontally once the container is too narrow to keep the
|
|
109
|
+
// columns usable, rather than clipping anything.
|
|
108
110
|
this.minTableWidth = '640px';
|
|
109
111
|
this.columns = [
|
|
110
112
|
{
|
|
@@ -117,9 +119,7 @@ export class MsgList extends ContentList<Msg> {
|
|
|
117
119
|
{
|
|
118
120
|
key: 'created_on',
|
|
119
121
|
label: 'Sent',
|
|
120
|
-
|
|
121
|
-
align: 'right',
|
|
122
|
-
pinned: 'right'
|
|
122
|
+
align: 'right'
|
|
123
123
|
}
|
|
124
124
|
];
|
|
125
125
|
this.bulkActions = [
|
|
@@ -134,6 +134,14 @@ export class MsgList extends ContentList<Msg> {
|
|
|
134
134
|
];
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/** Rows navigate to the message's contact. Returning the href here
|
|
138
|
+
* also marks the row `clickable`, so it carries the pointer cursor on
|
|
139
|
+
* hover. */
|
|
140
|
+
protected getRowHref(item: Msg): string | null {
|
|
141
|
+
const uuid = item.contact?.uuid;
|
|
142
|
+
return uuid ? `/contact/read/${uuid}/` : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
137
145
|
protected renderCell(
|
|
138
146
|
item: Msg,
|
|
139
147
|
column: ContentListColumn
|
|
@@ -164,7 +172,7 @@ export class MsgList extends ContentList<Msg> {
|
|
|
164
172
|
`;
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
/** The sent cell —
|
|
175
|
+
/** The sent cell — timedate timestamp with an optional channel-log
|
|
168
176
|
* icon to its right. The icon is rendered when the server includes
|
|
169
177
|
* a logs_url on the row (permission- and retention-gated
|
|
170
178
|
* server-side). stopPropagation keeps the row's contact navigation
|
|
@@ -173,7 +181,7 @@ export class MsgList extends ContentList<Msg> {
|
|
|
173
181
|
if (!item.created_on) return '';
|
|
174
182
|
return html`
|
|
175
183
|
<div class="sent-cell">
|
|
176
|
-
<temba-date value=${item.created_on} display="
|
|
184
|
+
<temba-date value=${item.created_on} display="timedate"></temba-date>
|
|
177
185
|
${item.logs_url && this.isSafeHref(item.logs_url)
|
|
178
186
|
? html`
|
|
179
187
|
<a
|
|
@@ -220,20 +228,37 @@ export class MsgList extends ContentList<Msg> {
|
|
|
220
228
|
}
|
|
221
229
|
|
|
222
230
|
/** Flow + label pills for a row, pushed to the trailing edge of
|
|
223
|
-
* the message cell, or '' when the row carries none.
|
|
231
|
+
* the message cell, or '' when the row carries none. The flow pill
|
|
232
|
+
* opens its editor and each label pill opens that label's filtered
|
|
233
|
+
* message view — matching the rapidpro msg list. `clickable` gives
|
|
234
|
+
* the hover affordance; `goto` routes the click through the SPA and
|
|
235
|
+
* stops propagation so the row's own contact navigation doesn't also
|
|
236
|
+
* fire. */
|
|
224
237
|
private renderPills(item: Msg): TemplateResult | string {
|
|
225
238
|
const labels = item.labels || [];
|
|
226
239
|
if (!item.flow && !labels.length) return '';
|
|
227
240
|
return html`
|
|
228
241
|
<div class="cell-pills">
|
|
229
242
|
${item.flow
|
|
230
|
-
? html`<temba-label
|
|
243
|
+
? html`<temba-label
|
|
244
|
+
type="flow"
|
|
245
|
+
icon=${Icon.flow}
|
|
246
|
+
href="/flow/editor/${item.flow.uuid}/"
|
|
247
|
+
onclick="goto(event)"
|
|
248
|
+
clickable
|
|
231
249
|
>${item.flow.name}</temba-label
|
|
232
250
|
>`
|
|
233
251
|
: null}
|
|
234
252
|
${labels.map(
|
|
235
253
|
(l) => html`
|
|
236
|
-
<temba-label
|
|
254
|
+
<temba-label
|
|
255
|
+
type="label"
|
|
256
|
+
icon=${Icon.label}
|
|
257
|
+
href="/msg/filter/${l.uuid}/"
|
|
258
|
+
onclick="goto(event)"
|
|
259
|
+
clickable
|
|
260
|
+
>${l.name}</temba-label
|
|
261
|
+
>
|
|
237
262
|
`
|
|
238
263
|
)}
|
|
239
264
|
</div>
|
package/src/live/ContactChat.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
css,
|
|
4
4
|
html,
|
|
5
|
+
nothing,
|
|
5
6
|
PropertyValueMap,
|
|
6
7
|
PropertyValues,
|
|
7
8
|
TemplateResult
|
|
@@ -1436,6 +1437,8 @@ export class ContactChat extends ContactStoreElement {
|
|
|
1436
1437
|
@temba-scroll-threshold-bottom=${this.fetchNewerMessages}
|
|
1437
1438
|
@temba-fetch-complete=${this.fetchComplete}
|
|
1438
1439
|
avatar=${this.avatar}
|
|
1440
|
+
contactName=${this.currentContact?.name ?? nothing}
|
|
1441
|
+
contactUuid=${this.currentContact?.uuid ?? nothing}
|
|
1439
1442
|
agent
|
|
1440
1443
|
avatars
|
|
1441
1444
|
?hasFooter=${inFlow}
|
|
@@ -809,7 +809,7 @@ export class ContactTimeline extends EndpointMonitorElement {
|
|
|
809
809
|
<temba-icon name=${Icon.schedule} size="2"></temba-icon>
|
|
810
810
|
<div class="empty-title">${this.lang_empty}</div>
|
|
811
811
|
<div class="empty-help">${this.lang_empty_help}</div>
|
|
812
|
-
<a class="empty-link" href="/campaign/" onclick="goto(event)"
|
|
812
|
+
<a class="empty-link" href="/campaign/" onclick="goto(event, this)"
|
|
813
813
|
>${this.lang_campaigns_link}</a
|
|
814
814
|
>
|
|
815
815
|
</slot>
|
package/src/store/Store.ts
CHANGED
|
@@ -41,6 +41,25 @@ export const getStore = () => {
|
|
|
41
41
|
return document.querySelector('temba-store') as Store;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
declare const __TEMBA_DEV_SERVER__: boolean;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* True only when running against the temba-components dev server, which
|
|
48
|
+
* replaces `__TEMBA_DEV_SERVER__` with `true` at serve time. The production
|
|
49
|
+
* rollup build and the test runner replace it with `false`; for any other
|
|
50
|
+
* consumer the token is undefined and the try/catch falls back to `false`.
|
|
51
|
+
* We deliberately do not key off `process.env.NODE_ENV` — the published IIFE
|
|
52
|
+
* bundle (rollup.components.mjs) hardcodes that to 'development', so it can't
|
|
53
|
+
* distinguish the dev server from a production consumer.
|
|
54
|
+
*/
|
|
55
|
+
const isDevServer = (): boolean => {
|
|
56
|
+
try {
|
|
57
|
+
return __TEMBA_DEV_SERVER__ === true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
44
63
|
export class Store extends RapidElement {
|
|
45
64
|
public static get styles() {
|
|
46
65
|
return css`
|
|
@@ -172,7 +191,7 @@ export class Store extends RapidElement {
|
|
|
172
191
|
const fetches = [];
|
|
173
192
|
if (this.completionEndpoint) {
|
|
174
193
|
fetches.push(
|
|
175
|
-
getUrl(this.
|
|
194
|
+
getUrl(this.getCompletionEndpoint()).then((response) => {
|
|
176
195
|
this.schema = response.json['context'] as CompletionSchema;
|
|
177
196
|
this.fnOptions = response.json['functions'] as CompletionOption[];
|
|
178
197
|
})
|
|
@@ -338,6 +357,27 @@ export class Store extends RapidElement {
|
|
|
338
357
|
}
|
|
339
358
|
}
|
|
340
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Resolves the completion endpoint to fetch. When running against the
|
|
362
|
+
* temba-components dev server we override the configured endpoint so the
|
|
363
|
+
* editor serves our own editor.json (static/mr/docs/en-us/editor.json)
|
|
364
|
+
* rather than the host application's mailroom completions. The dev-server
|
|
365
|
+
* origin is derived from import.meta.url so this works even when the
|
|
366
|
+
* components are loaded cross-origin (e.g. rapidpro on :8001 with the
|
|
367
|
+
* components dev server on :3011).
|
|
368
|
+
*/
|
|
369
|
+
private getCompletionEndpoint(): string {
|
|
370
|
+
if (isDevServer()) {
|
|
371
|
+
try {
|
|
372
|
+
const origin = new URL(import.meta.url).origin;
|
|
373
|
+
return `${origin}/api/v2/completion.json`;
|
|
374
|
+
} catch {
|
|
375
|
+
// import.meta.url unavailable; fall back to the configured endpoint
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return this.completionEndpoint;
|
|
379
|
+
}
|
|
380
|
+
|
|
341
381
|
public getCompletionSchema(): CompletionSchema {
|
|
342
382
|
return this.schema;
|
|
343
383
|
}
|
|
@@ -295,8 +295,18 @@ export default {
|
|
|
295
295
|
// Permissive CORS so this dev server can be loaded as a cross-origin
|
|
296
296
|
// module source by a rapidpro instance running on a different localhost
|
|
297
297
|
// port (e.g. Nautilus/run-pair.sh launching rapidpro:8001 + components:3011).
|
|
298
|
+
// The Store fetches completion.json with custom headers (X-CSRFToken,
|
|
299
|
+
// X-Temba-Workspace, X-Requested-With), which makes it a non-simple
|
|
300
|
+
// cross-origin request, so we must also answer the preflight (OPTIONS)
|
|
301
|
+
// with the allowed methods/headers or the browser blocks it.
|
|
298
302
|
(ctx, next) => {
|
|
299
303
|
ctx.set('Access-Control-Allow-Origin', '*');
|
|
304
|
+
ctx.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
305
|
+
ctx.set('Access-Control-Allow-Headers', '*');
|
|
306
|
+
if (ctx.method === 'OPTIONS') {
|
|
307
|
+
ctx.status = 204;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
300
310
|
return next();
|
|
301
311
|
},
|
|
302
312
|
],
|
|
@@ -304,6 +314,7 @@ export default {
|
|
|
304
314
|
replacePlugin({
|
|
305
315
|
preventAssignment: true,
|
|
306
316
|
'process.env.NODE_ENV': JSON.stringify('development'),
|
|
317
|
+
'__TEMBA_DEV_SERVER__': JSON.stringify(true),
|
|
307
318
|
'__TEMBA_COMPONENTS_VERSION__': JSON.stringify(TEMBA_COMPONENTS_VERSION),
|
|
308
319
|
'process.env.MINIO_ENDPOINT': JSON.stringify('http://minio:9000'),
|
|
309
320
|
'process.env.MINIO_PUBLIC_ENDPOINT': JSON.stringify('http://localhost:9000'),
|
|
@@ -454,6 +454,7 @@ export default {
|
|
|
454
454
|
replacePlugin({
|
|
455
455
|
preventAssignment: true,
|
|
456
456
|
'process.env.NODE_ENV': JSON.stringify('test'),
|
|
457
|
+
__TEMBA_DEV_SERVER__: JSON.stringify(false),
|
|
457
458
|
__TEMBA_COMPONENTS_VERSION__: JSON.stringify(TEMBA_COMPONENTS_VERSION)
|
|
458
459
|
}),
|
|
459
460
|
{
|