@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.
@@ -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 duration timestamp closing the row.
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
- // Fixed layout so a long message ellipsis-truncates within its
104
- // column instead of stretching the table; minTableWidth lets the
105
- // list scroll horizontally once the container is too narrow to
106
- // keep the columns usable, rather than clipping anything.
107
- this.fixedLayout = true;
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
- width: '120px',
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 — duration timestamp with an optional channel-log
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="duration"></temba-date>
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 type="flow" icon=${Icon.flow}
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 type="label" icon=${Icon.label}>${l.name}</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>
@@ -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>
@@ -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.completionEndpoint).then((response) => {
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
  {