@nyaruka/temba-components 0.156.2 → 0.156.3

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.2",
3
+ "version": "0.156.3",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
@@ -583,34 +583,36 @@ export class Chat extends RapidElement {
583
583
  text-align: center;
584
584
  font-size: 11px;
585
585
  color: #8e8e93;
586
+ max-width: 100%;
587
+ overflow: hidden;
586
588
  }
587
589
 
588
590
  .event .webhook-event {
589
591
  display: inline-flex;
590
- align-items: center;
592
+ align-items: flex-start;
591
593
  gap: 6px;
594
+ max-width: 100%;
595
+ min-width: 0;
592
596
  }
593
597
 
594
- .event .webhook-event-log-link {
595
- all: unset;
596
- display: inline-flex;
597
- align-items: center;
598
- justify-content: center;
599
- color: #9ca3af;
600
- cursor: pointer;
601
- line-height: 1;
602
- border-radius: 4px;
603
- transition: color var(--animation-time, 200ms) ease;
598
+ .event .webhook-event-text {
599
+ overflow: hidden;
600
+ display: -webkit-box;
601
+ -webkit-line-clamp: 2;
602
+ -webkit-box-orient: vertical;
603
+ word-break: break-all;
604
+ min-width: 0;
605
+ padding: 4px 0;
604
606
  }
605
607
 
606
- .event .webhook-event-log-link:hover,
607
- .event .webhook-event-log-link:focus-visible {
608
- color: var(--color-primary-dark, #007aff);
608
+ .event .webhook-event-url {
609
+ color: inherit;
610
+ text-decoration: underline;
611
+ cursor: pointer;
609
612
  }
610
613
 
611
- .event .webhook-event-log-link:focus-visible {
612
- outline: 1px solid currentColor;
613
- outline-offset: 2px;
614
+ .event .webhook-event-url:hover {
615
+ text-decoration: none;
614
616
  }
615
617
 
616
618
  .event p {
@@ -389,7 +389,12 @@ export const renderResthookCalled = (event: any): TemplateResult | null => {
389
389
  };
390
390
 
391
391
  export const renderWebhookCalled = (event: any): TemplateResult | null => {
392
- return html`<div>Called <strong>${event.url}</strong></div>`;
392
+ const maxLen = 50;
393
+ const displayUrl =
394
+ event.url && event.url.length > maxLen
395
+ ? event.url.slice(0, maxLen) + '...'
396
+ : event.url;
397
+ return html`<div>Called <strong>${displayUrl}</strong></div>`;
393
398
  };
394
399
 
395
400
  export const renderServiceCalled = (event: any): TemplateResult | null => {
@@ -361,6 +361,9 @@ export class CanvasNode extends RapidElement {
361
361
 
362
362
  .router .body {
363
363
  padding: 0.75em;
364
+ }
365
+
366
+ .router .body > div {
364
367
  max-width: 180px;
365
368
  }
366
369
 
@@ -368,7 +371,32 @@ export class CanvasNode extends RapidElement {
368
371
  font-weight: 500;
369
372
  display: inline-block;
370
373
  }
371
-
374
+
375
+ .router {
376
+ display: flex;
377
+ flex-direction: column;
378
+ }
379
+
380
+ .rules-count {
381
+ position: absolute;
382
+ right: 4px;
383
+ top: 50%;
384
+ transform: translateY(-50%);
385
+ background: #fff8dc;
386
+ border-radius: 10px;
387
+ min-width: 18px;
388
+ height: 18px;
389
+ padding: 0 5px;
390
+ font-size: 11px;
391
+ font-weight: 600;
392
+ color: #333;
393
+ display: flex;
394
+ align-items: center;
395
+ justify-content: center;
396
+ line-height: 1;
397
+ box-sizing: border-box;
398
+ }
399
+
372
400
  .exit-wrapper {
373
401
  display: flex;
374
402
  justify-content: center;
@@ -1611,9 +1639,10 @@ export class CanvasNode extends RapidElement {
1611
1639
  ? ACTION_GROUP_METADATA[config.group]?.color ||
1612
1640
  SPLIT_GROUP_METADATA[config.group]?.color
1613
1641
  : '#aaaaaa';
1642
+ const untranslatedRules = this.getUntranslatedRulesCount();
1614
1643
  return html`<div
1615
1644
  class="cn-title ${isRemoving ? 'removing' : ''}"
1616
- style="background:${color}"
1645
+ style="background:${color}; position: relative;"
1617
1646
  >
1618
1647
  <div class="title-spacer"></div>
1619
1648
  <div class="name">
@@ -1630,6 +1659,9 @@ export class CanvasNode extends RapidElement {
1630
1659
  >
1631
1660
 
1632
1661
  </div>
1662
+ ${untranslatedRules > 0
1663
+ ? html`<div class="rules-count">${untranslatedRules}</div>`
1664
+ : null}
1633
1665
  </div>`;
1634
1666
  }
1635
1667
 
@@ -1752,6 +1784,21 @@ export class CanvasNode extends RapidElement {
1752
1784
  return result;
1753
1785
  }
1754
1786
 
1787
+ private getUntranslatedRulesCount(): number {
1788
+ if (!this.isTranslating || !this.ui?.config?.localizeRules) return 0;
1789
+ const cases = this.node?.router?.cases;
1790
+ if (!cases?.length) return 0;
1791
+
1792
+ const langLocalization =
1793
+ this.flowDefinition?.localization?.[this.languageCode] || {};
1794
+
1795
+ return cases.filter((c) => {
1796
+ if (!c.arguments?.length || !c.arguments.some((a) => a)) return false;
1797
+ const localized = langLocalization[c.uuid]?.arguments;
1798
+ return !Array.isArray(localized) || !localized.some((a: string) => a);
1799
+ }).length;
1800
+ }
1801
+
1755
1802
  private renderRouter(router: Router, ui: NodeUI) {
1756
1803
  const nodeConfig = NODE_CONFIG[ui.type];
1757
1804
  if (nodeConfig) {
@@ -705,6 +705,12 @@ export class Editor extends RapidElement {
705
705
  border: 1px solid #d7dce2;
706
706
  }
707
707
 
708
+ .language-pill.complete {
709
+ background: #d4f5e0;
710
+ color: #1a7f37;
711
+ --icon-color: #1a7f37;
712
+ }
713
+
708
714
  .language-pill-caret {
709
715
  margin-left: 1px;
710
716
  --icon-color: currentColor;
@@ -720,6 +726,10 @@ export class Editor extends RapidElement {
720
726
  white-space: nowrap;
721
727
  }
722
728
 
729
+ .language-pill.complete .language-percent {
730
+ color: #1a7f37;
731
+ }
732
+
723
733
  .toolbar-zoom-level {
724
734
  font-size: 12px;
725
735
  min-width: 40px;
@@ -6307,12 +6317,20 @@ export class Editor extends RapidElement {
6307
6317
  `;
6308
6318
  }
6309
6319
 
6320
+ const isComplete = option.percent === 100;
6321
+ const optionBg = isComplete ? '#d4f5e0' : '';
6322
+ const optionHoverBg = isComplete ? '#c0edce' : '';
6323
+ const optionRadius = isComplete ? 'border-radius:4px;' : '';
6324
+ const percentColor = isComplete ? 'color:#1a7f37;' : 'color:#5f6b7a;';
6325
+
6310
6326
  return html`
6311
6327
  <div
6312
- style="display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 10px;"
6328
+ style="display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 10px; ${optionBg ? `background:${optionBg};` : ''} ${optionRadius}"
6329
+ @mouseenter=${isComplete ? (e: MouseEvent) => { (e.currentTarget as HTMLElement).style.background = optionHoverBg; } : null}
6330
+ @mouseleave=${isComplete ? (e: MouseEvent) => { (e.currentTarget as HTMLElement).style.background = optionBg; } : null}
6313
6331
  >
6314
- <span>${option.name}</span>
6315
- <span style="font-size:11px; font-weight:600; color:#5f6b7a;"
6332
+ <span style="${isComplete ? 'color:#1a7f37;' : ''}">${option.name}</span>
6333
+ <span style="font-size:11px; font-weight:600; ${percentColor}"
6316
6334
  >${option.percent ?? 0}%</span
6317
6335
  >
6318
6336
  </div>
@@ -6407,7 +6425,7 @@ export class Editor extends RapidElement {
6407
6425
  'Change language',
6408
6426
  html`
6409
6427
  <button
6410
- class="language-pill ${isBaseSelected ? 'primary' : ''}"
6428
+ class="language-pill ${isBaseSelected ? 'primary' : percent === 100 ? 'complete' : ''}"
6411
6429
  id="language-btn"
6412
6430
  @click=${this.handleLanguageIconClick}
6413
6431
  aria-label="Change language"
@@ -6542,35 +6560,9 @@ export class Editor extends RapidElement {
6542
6560
  `;
6543
6561
  }
6544
6562
 
6545
- private renderToolbarTranslationTools(hasTranslations: boolean): TemplateResult {
6546
- const disableTranslationControls = Boolean(this.viewingRevision);
6547
- const autoTranslateLabel = this.autoTranslating
6548
- ? 'Stop auto translate'
6549
- : 'Auto translate';
6550
- return html`
6551
- <div class="toolbar-translation">
6552
- ${this.renderToolbarTip(
6553
- autoTranslateLabel,
6554
- html`
6555
- <button
6556
- class="toolbar-btn language-tool ${this.autoTranslating
6557
- ? 'active'
6558
- : ''}"
6559
- @click=${this.handleAutoTranslateClick}
6560
- ?disabled=${disableTranslationControls ||
6561
- (!this.autoTranslating && !hasTranslations)}
6562
- aria-label=${autoTranslateLabel}
6563
- >
6564
- <temba-icon
6565
- name=${this.autoTranslating ? 'progress_spinner' : Icon.ai}
6566
- size="0.9"
6567
- ?spin=${this.autoTranslating}
6568
- ></temba-icon>
6569
- </button>
6570
- `
6571
- )}
6572
- </div>
6573
- `;
6563
+ private renderToolbarTranslationTools(_hasTranslations: boolean): TemplateResult {
6564
+ // auto translate button hidden pending backend changes
6565
+ return html``;
6574
6566
  }
6575
6567
 
6576
6568
  /**
@@ -221,10 +221,23 @@ function getActionSearchTexts(action: Action): string[] {
221
221
  return texts;
222
222
  }
223
223
 
224
- function getTableMessageSearchTexts(action: SendMsg): string[] {
224
+ function getTableSearchTexts(action: Action): string[] {
225
225
  const texts: string[] = [];
226
- if (action.text) texts.push(action.text);
227
- if (action.quick_replies) texts.push(...action.quick_replies);
226
+ const config = ACTION_CONFIG[action.type];
227
+ if (!config?.localizable) return texts;
228
+ const a = action as Record<string, any>;
229
+ for (const key of config.localizable) {
230
+ const val = a[key];
231
+ if (typeof val === 'string' && val.trim()) {
232
+ texts.push(val);
233
+ } else if (Array.isArray(val)) {
234
+ for (const item of val) {
235
+ if (typeof item === 'string' && item.trim()) {
236
+ texts.push(item);
237
+ }
238
+ }
239
+ }
240
+ }
228
241
  return texts;
229
242
  }
230
243
 
@@ -729,20 +742,37 @@ export class FlowSearch extends LitElement {
729
742
  const nodeUI = this.definition._ui?.nodes[node.uuid];
730
743
  const nodeType = nodeUI?.type || 'execute_actions';
731
744
 
732
- // Message table rows: one row per send_msg action
745
+ // Message table rows: one row per action with localizable fields
733
746
  if (node.actions) {
734
747
  for (const action of node.actions) {
735
- if (action.type !== 'send_msg') {
748
+ const actionConfig = ACTION_CONFIG[action.type];
749
+ if (
750
+ action.type !== 'send_msg' &&
751
+ (!actionConfig?.localizable || actionConfig.localizable.length === 0)
752
+ ) {
736
753
  continue;
737
754
  }
738
755
 
739
- const actionConfig = ACTION_CONFIG[action.type];
740
- const searchAction = localizeAction(
756
+ // Search both original and localized texts, but only add one result per action
757
+ const originalTexts = getTableSearchTexts(action);
758
+ const localizedAction = localizeAction(
741
759
  action,
742
760
  langLocalization?.[action.uuid]
743
- ) as SendMsg;
744
- const texts = getTableMessageSearchTexts(searchAction);
745
- for (const text of texts) {
761
+ );
762
+ const localizedTexts = getTableSearchTexts(localizedAction);
763
+
764
+ // Deduplicate: combine both, originals first
765
+ const allTexts: string[] = [];
766
+ const seen = new Set<string>();
767
+ for (const text of [...originalTexts, ...localizedTexts]) {
768
+ if (!seen.has(text)) {
769
+ seen.add(text);
770
+ allTexts.push(text);
771
+ }
772
+ }
773
+
774
+ let found = false;
775
+ for (const text of allTexts) {
746
776
  const idx = text.toLowerCase().indexOf(query);
747
777
  if (idx !== -1) {
748
778
  results.push({
@@ -754,9 +784,11 @@ export class FlowSearch extends LitElement {
754
784
  matchStart: idx,
755
785
  matchLength: query.length
756
786
  });
787
+ found = true;
757
788
  break;
758
789
  }
759
790
  }
791
+ if (found) continue;
760
792
  }
761
793
  }
762
794