@nyaruka/temba-components 0.156.10 → 0.156.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.156.10",
3
+ "version": "0.156.12",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "remarkable": "^2.0.1",
57
57
  "serialize-javascript": "^7.0.3",
58
58
  "tiny-lru": "^11.2.5",
59
- "uuid": "^13.0.0",
59
+ "uuid": "^14.0.0",
60
60
  "zustand": "^5.0.3"
61
61
  },
62
62
  "devDependencies": {
package/src/flow/types.ts CHANGED
@@ -36,7 +36,7 @@ export const CONTEXT_MENU_SHORTCUTS: Record<FlowType, ContextMenuShortcut[]> = {
36
36
  { type: 'wait_for_response', name: 'Wait for Response', icon: 'message' }
37
37
  ],
38
38
  [FlowTypes.VOICE]: [
39
- { type: 'say_msg', name: 'Say Message', icon: 'send' },
39
+ { type: 'say_msg', name: 'Say Message', icon: 'recording' },
40
40
  { type: 'wait_for_menu', name: 'Wait for Menu', icon: 'dots-grid' }
41
41
  ],
42
42
  [FlowTypes.BACKGROUND]: [
@@ -2,12 +2,12 @@ import { TemplateResult, html, css, PropertyValues, nothing } from 'lit';
2
2
  import { FieldElement } from './FieldElement';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { Attachment, CustomEventType, Language, Shortcut } from '../interfaces';
5
+ import { Icon } from '../Icons';
5
6
  import { DEFAULT_MEDIA_ENDPOINT } from '../utils';
6
7
  import { Select } from './select/Select';
7
8
  import { MessageEditor } from './MessageEditor';
8
9
  import { ShortcutList } from '../list/ShortcutList';
9
10
  import { setCaretOffset } from '../excellent/caret-utils';
10
- import { Icon } from '../Icons';
11
11
 
12
12
  export interface ComposeValue {
13
13
  text: string;
@@ -21,6 +21,7 @@ export interface ComposeValue {
21
21
  export class Compose extends FieldElement {
22
22
  static get styles() {
23
23
  return css`
24
+ ${super.styles}
24
25
  :host {
25
26
  border-top-right-radius: var(--curvature);
26
27
  border-top-left-radius: var(--curvature);
@@ -270,6 +271,65 @@ export class Compose extends FieldElement {
270
271
  );
271
272
  }
272
273
 
274
+ private hasTranslation(iso: string): boolean {
275
+ const entry = this.langValues?.[iso];
276
+ if (!entry) return false;
277
+ return !!(
278
+ (entry.text && entry.text.trim().length > 0) ||
279
+ (entry.attachments && entry.attachments.length > 0) ||
280
+ (entry.quick_replies && entry.quick_replies.length > 0)
281
+ );
282
+ }
283
+
284
+ private getSortedLanguages(): Language[] {
285
+ if (this.languages.length === 0) return [];
286
+ const [base, ...rest] = this.languages;
287
+ const sorted = [...rest].sort((a, b) => {
288
+ const aTranslated = this.hasTranslation(a.iso);
289
+ const bTranslated = this.hasTranslation(b.iso);
290
+ if (aTranslated !== bTranslated) {
291
+ return aTranslated ? -1 : 1;
292
+ }
293
+ return 0;
294
+ });
295
+ return [base, ...sorted];
296
+ }
297
+
298
+ private renderLanguageOption = (option: Language): TemplateResult => {
299
+ const isBase =
300
+ this.languages.length > 0 && option.iso === this.languages[0].iso;
301
+
302
+ const badgeBase =
303
+ 'display:inline-flex; align-items:center; border-radius:999px; font-size:10px; font-weight:700; line-height:1;';
304
+
305
+ let badge: TemplateResult;
306
+ if (isBase) {
307
+ badge = html`<span
308
+ style="${badgeBase} background:rgba(47, 63, 82, 0.12); color:#2f3f52; padding:3px 7px;"
309
+ >Original</span
310
+ >`;
311
+ } else if (this.hasTranslation(option.iso)) {
312
+ badge = html`<span
313
+ style="${badgeBase} background:rgba(26, 127, 55, 0.15); color:#1a7f37; padding:3px 7px;"
314
+ >Translated</span
315
+ >`;
316
+ } else {
317
+ badge = html`<span
318
+ style="${badgeBase} background:transparent; color:#8a94a1; border:1px solid #c2c8d0; padding:2px 6px;"
319
+ >Missing</span
320
+ >`;
321
+ }
322
+
323
+ return html`
324
+ <div
325
+ style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex:1; padding:2px 8px;"
326
+ >
327
+ <span>${option.name}</span>
328
+ ${badge}
329
+ </div>
330
+ `;
331
+ };
332
+
273
333
  public willUpdate(changed: PropertyValues): void {
274
334
  super.willUpdate(changed);
275
335
 
@@ -420,7 +480,17 @@ export class Compose extends FieldElement {
420
480
  if (editor) {
421
481
  const richEdit = editor.getRichEditor();
422
482
  if (richEdit) {
423
- richEdit.value = this.initialText;
483
+ const targetText = this.initialText;
484
+ const targetLanguage = this.currentLanguage;
485
+ richEdit.value = targetText;
486
+ const editable = richEdit.inputElement;
487
+ if (editable) {
488
+ window.setTimeout(() => {
489
+ if (this.currentLanguage === targetLanguage) {
490
+ setCaretOffset(editable, targetText.length);
491
+ }
492
+ }, 0);
493
+ }
424
494
  }
425
495
  }
426
496
  }
@@ -657,7 +727,8 @@ export class Compose extends FieldElement {
657
727
  @change=${this.handleLanguageChange}
658
728
  class="language"
659
729
  name="language"
660
- .staticOptions=${this.languages}
730
+ .staticOptions=${this.getSortedLanguages()}
731
+ .renderOption=${this.renderLanguageOption}
661
732
  valueKey="iso"
662
733
  >
663
734
  </temba-select>`
@@ -2272,7 +2272,7 @@ export class Select<T extends SelectOption> extends FieldElement {
2272
2272
  --icon-color: var(--color-text-dark);
2273
2273
  ${this.isMultiMode
2274
2274
  ? 'vertical-align: middle; background: #fff; border: 1px solid rgba(100,100,100,0.3); user-select: none; border-radius: 2px; align-items: center; flex-direction: row; flex-wrap: nowrap; margin: 2px 2px;'
2275
- : ''}
2275
+ : 'flex: 1; min-width: 0;'}
2276
2276
  ${index === this.selectedIndex
2277
2277
  ? 'background: rgba(100,100,100,0.3);'
2278
2278
  : ''}