@nyaruka/temba-components 0.156.9 → 0.156.10
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 +11 -0
- package/dist/temba-components.js +568 -521
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Chat.ts +8 -8
- package/src/display/FloatingTab.ts +2 -2
- package/src/display/Options.ts +8 -2
- package/src/flow/CanvasMenu.ts +20 -25
- package/src/flow/CanvasNode.ts +16 -12
- package/src/flow/DragManager.ts +93 -33
- package/src/flow/Editor.ts +59 -54
- package/src/flow/EditorToolbar.ts +19 -20
- package/src/flow/FlowSearch.ts +9 -7
- package/src/flow/MessageTable.ts +181 -74
- package/src/flow/NodeEditor.ts +55 -72
- package/src/flow/RevisionsWindow.ts +2 -4
- package/src/flow/ZoomManager.ts +1 -2
- package/src/flow/actions/play_audio.ts +1 -28
- package/src/flow/actions/say_msg.ts +1 -40
- package/src/flow/actions/send_broadcast.ts +1 -2
- package/src/flow/actions/send_email.ts +5 -56
- package/src/flow/actions/send_msg.ts +10 -2
- package/src/flow/actions/start_session.ts +1 -2
- package/src/flow/categoryLocalization.ts +1 -5
- package/src/flow/categoryUtils.ts +139 -0
- package/src/flow/nodes/shared-rules.ts +6 -16
- package/src/flow/nodes/shared.ts +113 -6
- package/src/flow/nodes/split_by_airtime.ts +41 -63
- package/src/flow/nodes/split_by_contact_field.ts +8 -17
- package/src/flow/nodes/split_by_expression.ts +8 -17
- package/src/flow/nodes/split_by_groups.ts +34 -112
- package/src/flow/nodes/split_by_llm.ts +1 -7
- package/src/flow/nodes/split_by_llm_categorize.ts +27 -43
- package/src/flow/nodes/split_by_random.ts +39 -99
- package/src/flow/nodes/split_by_resthook.ts +5 -19
- package/src/flow/nodes/split_by_run_result.ts +8 -17
- package/src/flow/nodes/split_by_scheme.ts +39 -124
- package/src/flow/nodes/split_by_subflow.ts +1 -7
- package/src/flow/nodes/split_by_ticket.ts +1 -7
- package/src/flow/nodes/split_by_webhook.ts +2 -8
- package/src/flow/nodes/wait_for_audio.ts +1 -7
- package/src/flow/nodes/wait_for_dial.ts +2 -8
- package/src/flow/nodes/wait_for_digits.ts +5 -7
- package/src/flow/nodes/wait_for_menu.ts +5 -7
- package/src/flow/nodes/wait_for_response.ts +10 -18
- package/src/flow/types.ts +27 -0
- package/src/flow/utils.ts +111 -3
- package/src/form/Compose.ts +11 -4
- package/src/form/MessageEditor.ts +5 -3
- package/src/form/RichEditor.ts +3 -1
- package/src/form/TemplateEditor.ts +5 -1
- package/src/form/select/Select.ts +11 -9
- package/src/layout/AccordionSection.ts +9 -3
- package/src/layout/Modax.ts +1 -3
- package/src/live/ContactChat.ts +54 -46
- package/src/simulator/Simulator.ts +9 -3
- package/src/store/AppState.ts +1 -1
- package/src/utils.ts +21 -16
package/src/flow/MessageTable.ts
CHANGED
|
@@ -87,7 +87,9 @@ export class MessageTable extends RapidElement {
|
|
|
87
87
|
padding: 2px 16px 2px 26px;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
.message-table
|
|
90
|
+
.message-table
|
|
91
|
+
tr.localization-paired-row:not(.localization-paired-last)
|
|
92
|
+
td {
|
|
91
93
|
border-bottom: none;
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -278,11 +280,13 @@ export class MessageTable extends RapidElement {
|
|
|
278
280
|
transition: background 0.15s;
|
|
279
281
|
}
|
|
280
282
|
|
|
281
|
-
.translation-cell.category-translation-cell:hover
|
|
283
|
+
.translation-cell.category-translation-cell:hover
|
|
284
|
+
.category-translation-item {
|
|
282
285
|
background: #f5f8ff;
|
|
283
286
|
}
|
|
284
287
|
|
|
285
|
-
.translation-cell.category-translation-cell:hover
|
|
288
|
+
.translation-cell.category-translation-cell:hover
|
|
289
|
+
.category-translation-item.missing {
|
|
286
290
|
color: #9bb8df;
|
|
287
291
|
}
|
|
288
292
|
|
|
@@ -399,14 +403,24 @@ export class MessageTable extends RapidElement {
|
|
|
399
403
|
const nodeType = nodeUI?.type;
|
|
400
404
|
|
|
401
405
|
// Collect rules that have localized values or are flagged for localization
|
|
402
|
-
const langLocalization =
|
|
403
|
-
|
|
406
|
+
const langLocalization =
|
|
407
|
+
this.definition?.localization?.[this.languageCode] || {};
|
|
408
|
+
let rules: Array<{ uuid: string; type: string; arguments: string[] }> =
|
|
409
|
+
[];
|
|
404
410
|
if (node.router?.cases?.length) {
|
|
405
411
|
const hasLocalizeRules = nodeUI?.config?.localizeRules;
|
|
406
412
|
rules = node.router.cases
|
|
407
413
|
.filter((c) => c.arguments?.length > 0 && c.arguments.some((a) => a))
|
|
408
|
-
.filter(
|
|
409
|
-
|
|
414
|
+
.filter(
|
|
415
|
+
(c) =>
|
|
416
|
+
hasLocalizeRules ||
|
|
417
|
+
langLocalization[c.uuid]?.arguments?.some((a: string) => a)
|
|
418
|
+
)
|
|
419
|
+
.map((c) => ({
|
|
420
|
+
uuid: c.uuid,
|
|
421
|
+
type: c.type,
|
|
422
|
+
arguments: [...c.arguments]
|
|
423
|
+
}));
|
|
410
424
|
}
|
|
411
425
|
|
|
412
426
|
// Collect categories that have localized values or are flagged for localization
|
|
@@ -424,8 +438,8 @@ export class MessageTable extends RapidElement {
|
|
|
424
438
|
if (hasLocalizeCategories) {
|
|
425
439
|
categories = translatableCategories;
|
|
426
440
|
} else {
|
|
427
|
-
categories = translatableCategories.filter(
|
|
428
|
-
|
|
441
|
+
categories = translatableCategories.filter((cat) =>
|
|
442
|
+
langLocalization[cat.uuid]?.name?.some((n: string) => n)
|
|
429
443
|
);
|
|
430
444
|
}
|
|
431
445
|
}
|
|
@@ -492,9 +506,7 @@ export class MessageTable extends RapidElement {
|
|
|
492
506
|
}
|
|
493
507
|
|
|
494
508
|
return localization.quick_replies
|
|
495
|
-
.map((reply: unknown) =>
|
|
496
|
-
typeof reply === 'string' ? reply.trim() : ''
|
|
497
|
-
)
|
|
509
|
+
.map((reply: unknown) => (typeof reply === 'string' ? reply.trim() : ''))
|
|
498
510
|
.filter((reply: string) => reply.length > 0);
|
|
499
511
|
}
|
|
500
512
|
|
|
@@ -568,7 +580,10 @@ export class MessageTable extends RapidElement {
|
|
|
568
580
|
// Count string-type localizable fields (exclude arrays like attachments, quick_replies)
|
|
569
581
|
const textFields = config.localizable.filter((key) => {
|
|
570
582
|
const fieldConfig = config.form?.[key];
|
|
571
|
-
return
|
|
583
|
+
return (
|
|
584
|
+
fieldConfig &&
|
|
585
|
+
(fieldConfig.type === 'text' || fieldConfig.type === 'textarea')
|
|
586
|
+
);
|
|
572
587
|
});
|
|
573
588
|
return textFields.length > 1;
|
|
574
589
|
}
|
|
@@ -587,11 +602,15 @@ export class MessageTable extends RapidElement {
|
|
|
587
602
|
return config.localizable
|
|
588
603
|
.filter((key) => {
|
|
589
604
|
const fieldConfig = config.form?.[key];
|
|
590
|
-
return
|
|
605
|
+
return (
|
|
606
|
+
fieldConfig &&
|
|
607
|
+
(fieldConfig.type === 'text' || fieldConfig.type === 'textarea')
|
|
608
|
+
);
|
|
591
609
|
})
|
|
592
610
|
.map((key) => {
|
|
593
611
|
const fieldConfig = config.form![key];
|
|
594
|
-
const label =
|
|
612
|
+
const label =
|
|
613
|
+
typeof fieldConfig.label === 'string' ? fieldConfig.label : key;
|
|
595
614
|
return {
|
|
596
615
|
key,
|
|
597
616
|
label,
|
|
@@ -609,7 +628,8 @@ export class MessageTable extends RapidElement {
|
|
|
609
628
|
if (colonIdx < 0) return html``;
|
|
610
629
|
const contentType = att.substring(0, colonIdx);
|
|
611
630
|
const baseType = contentType.split('/')[0];
|
|
612
|
-
const iconName =
|
|
631
|
+
const iconName =
|
|
632
|
+
MessageTable.ATTACHMENT_ICONS[baseType] || Icon.attachment;
|
|
613
633
|
return html`<div class="attachment-icon">
|
|
614
634
|
<temba-icon name="${iconName}"></temba-icon>
|
|
615
635
|
</div>`;
|
|
@@ -628,10 +648,17 @@ export class MessageTable extends RapidElement {
|
|
|
628
648
|
if (config?.form) {
|
|
629
649
|
for (const key of localizable) {
|
|
630
650
|
const fc = config.form[key];
|
|
631
|
-
if (
|
|
651
|
+
if (
|
|
652
|
+
fc &&
|
|
653
|
+
(fc.type === 'text' ||
|
|
654
|
+
fc.type === 'textarea' ||
|
|
655
|
+
fc.type === 'message-editor')
|
|
656
|
+
) {
|
|
632
657
|
const text = action[key] || '';
|
|
633
658
|
if (text) {
|
|
634
|
-
parts.push(
|
|
659
|
+
parts.push(
|
|
660
|
+
renderHighlightedText(this.stripLeadingLineBreaks(text), true)
|
|
661
|
+
);
|
|
635
662
|
}
|
|
636
663
|
}
|
|
637
664
|
}
|
|
@@ -640,9 +667,15 @@ export class MessageTable extends RapidElement {
|
|
|
640
667
|
// Quick replies (send_msg)
|
|
641
668
|
const quickReplies: string[] = action.quick_replies || [];
|
|
642
669
|
if (quickReplies.length > 0) {
|
|
643
|
-
parts.push(
|
|
644
|
-
|
|
645
|
-
|
|
670
|
+
parts.push(
|
|
671
|
+
html`<div
|
|
672
|
+
class="quick-replies ${parts.length === 0 ? 'standalone' : ''}"
|
|
673
|
+
>
|
|
674
|
+
${quickReplies.map(
|
|
675
|
+
(reply) => html`<div class="quick-reply">${reply}</div>`
|
|
676
|
+
)}
|
|
677
|
+
</div>`
|
|
678
|
+
);
|
|
646
679
|
}
|
|
647
680
|
|
|
648
681
|
// Attachments
|
|
@@ -669,10 +702,20 @@ export class MessageTable extends RapidElement {
|
|
|
669
702
|
if (config?.form) {
|
|
670
703
|
for (const key of localizable) {
|
|
671
704
|
const fc = config.form[key];
|
|
672
|
-
if (
|
|
705
|
+
if (
|
|
706
|
+
fc &&
|
|
707
|
+
(fc.type === 'text' ||
|
|
708
|
+
fc.type === 'textarea' ||
|
|
709
|
+
fc.type === 'message-editor')
|
|
710
|
+
) {
|
|
673
711
|
const translatedText = this.getTranslatedField(action.uuid, key);
|
|
674
712
|
if (typeof translatedText === 'string') {
|
|
675
|
-
parts.push(
|
|
713
|
+
parts.push(
|
|
714
|
+
renderHighlightedText(
|
|
715
|
+
this.stripLeadingLineBreaks(translatedText),
|
|
716
|
+
true
|
|
717
|
+
)
|
|
718
|
+
);
|
|
676
719
|
}
|
|
677
720
|
}
|
|
678
721
|
}
|
|
@@ -681,13 +724,22 @@ export class MessageTable extends RapidElement {
|
|
|
681
724
|
// Translated quick replies
|
|
682
725
|
const translatedQuickReplies = this.getTranslatedQuickReplies(action.uuid);
|
|
683
726
|
if (translatedQuickReplies.length > 0) {
|
|
684
|
-
parts.push(
|
|
685
|
-
|
|
686
|
-
|
|
727
|
+
parts.push(
|
|
728
|
+
html`<div
|
|
729
|
+
class="quick-replies ${parts.length === 0 ? 'standalone' : ''}"
|
|
730
|
+
>
|
|
731
|
+
${translatedQuickReplies.map(
|
|
732
|
+
(reply) => html`<div class="quick-reply">${reply}</div>`
|
|
733
|
+
)}
|
|
734
|
+
</div>`
|
|
735
|
+
);
|
|
687
736
|
}
|
|
688
737
|
|
|
689
738
|
// Translated attachments
|
|
690
|
-
const translatedAttachments = this.getTranslatedArrayField(
|
|
739
|
+
const translatedAttachments = this.getTranslatedArrayField(
|
|
740
|
+
action.uuid,
|
|
741
|
+
'attachments'
|
|
742
|
+
);
|
|
691
743
|
if (translatedAttachments.length > 0) {
|
|
692
744
|
parts.push(this.renderAttachments(translatedAttachments));
|
|
693
745
|
}
|
|
@@ -739,9 +791,7 @@ export class MessageTable extends RapidElement {
|
|
|
739
791
|
});
|
|
740
792
|
}
|
|
741
793
|
|
|
742
|
-
private getGroupTranslations(
|
|
743
|
-
entry: LocalizationGroupEntry
|
|
744
|
-
): Array<{
|
|
794
|
+
private getGroupTranslations(entry: LocalizationGroupEntry): Array<{
|
|
745
795
|
original: string;
|
|
746
796
|
translated: string | null;
|
|
747
797
|
isRule: boolean;
|
|
@@ -795,7 +845,10 @@ export class MessageTable extends RapidElement {
|
|
|
795
845
|
}
|
|
796
846
|
|
|
797
847
|
private renderPairedRows(
|
|
798
|
-
items: Array<{
|
|
848
|
+
items: Array<{
|
|
849
|
+
original: TemplateResult;
|
|
850
|
+
translated: TemplateResult | null;
|
|
851
|
+
}>,
|
|
799
852
|
entry: TableEntry,
|
|
800
853
|
handleBaseClick: () => void,
|
|
801
854
|
handleTranslationClick: () => void,
|
|
@@ -805,11 +858,17 @@ export class MessageTable extends RapidElement {
|
|
|
805
858
|
${items.map(
|
|
806
859
|
(item, idx) => html`
|
|
807
860
|
<tr
|
|
808
|
-
class="category-row localization-paired-row ${idx === 0
|
|
861
|
+
class="category-row localization-paired-row ${idx === 0
|
|
862
|
+
? 'localization-paired-first'
|
|
863
|
+
: ''} ${idx === items.length - 1
|
|
864
|
+
? 'localization-paired-last'
|
|
865
|
+
: ''}"
|
|
809
866
|
style=${`--node-rail-color: ${this.getEntryRailColor(entry)};`}
|
|
810
867
|
data-node-uuid=${entry.node.uuid}
|
|
811
868
|
data-entry-kind=${entry.kind}
|
|
812
|
-
data-action-uuid=${entry.kind === 'message'
|
|
869
|
+
data-action-uuid=${entry.kind === 'message'
|
|
870
|
+
? entry.action.uuid
|
|
871
|
+
: ''}
|
|
813
872
|
>
|
|
814
873
|
<td>
|
|
815
874
|
<div
|
|
@@ -830,9 +889,16 @@ export class MessageTable extends RapidElement {
|
|
|
830
889
|
title="Click to edit translation"
|
|
831
890
|
>
|
|
832
891
|
<div
|
|
833
|
-
class="category-item category-translation-item ${item.translated !==
|
|
892
|
+
class="category-item category-translation-item ${item.translated !==
|
|
893
|
+
null
|
|
894
|
+
? ''
|
|
895
|
+
: 'missing'}"
|
|
834
896
|
>
|
|
835
|
-
<span
|
|
897
|
+
<span
|
|
898
|
+
>${item.translated !== null
|
|
899
|
+
? item.translated
|
|
900
|
+
: 'No translation'}</span
|
|
901
|
+
>
|
|
836
902
|
</div>
|
|
837
903
|
</div>
|
|
838
904
|
</td>`
|
|
@@ -880,32 +946,55 @@ export class MessageTable extends RapidElement {
|
|
|
880
946
|
const groupTranslations = this.getGroupTranslations(entry);
|
|
881
947
|
const items = groupTranslations.map((item) => ({
|
|
882
948
|
original: html`${item.isRule && item.operatorName
|
|
883
|
-
? html`<span class="rule-operator"
|
|
949
|
+
? html`<span class="rule-operator"
|
|
950
|
+
>${item.operatorName}</span
|
|
951
|
+
> `
|
|
884
952
|
: ''}${renderHighlightedText(item.original, true)}`,
|
|
885
|
-
translated:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
953
|
+
translated:
|
|
954
|
+
item.translated !== null
|
|
955
|
+
? html`${item.isRule && item.operatorName
|
|
956
|
+
? html`<span class="rule-operator"
|
|
957
|
+
>${item.operatorName}</span
|
|
958
|
+
> `
|
|
959
|
+
: ''}${renderHighlightedText(item.translated, true)}`
|
|
960
|
+
: null
|
|
890
961
|
}));
|
|
891
|
-
return this.renderPairedRows(
|
|
962
|
+
return this.renderPairedRows(
|
|
963
|
+
items,
|
|
964
|
+
entry,
|
|
965
|
+
handleBaseClick,
|
|
966
|
+
handleTranslationClickFn,
|
|
967
|
+
showTranslation
|
|
968
|
+
);
|
|
892
969
|
}
|
|
893
970
|
|
|
894
971
|
// Multi-field actions (e.g. send_email with subject + body) - paired rows
|
|
895
|
-
if (
|
|
896
|
-
entry.kind === 'message' &&
|
|
897
|
-
this.usesPairedRows(entry.action)
|
|
898
|
-
) {
|
|
972
|
+
if (entry.kind === 'message' && this.usesPairedRows(entry.action)) {
|
|
899
973
|
const fields = this.getPairedFields(entry.action);
|
|
900
974
|
const items = fields.map((f) => ({
|
|
901
975
|
original: html`${f.original
|
|
902
|
-
? renderHighlightedText(
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
:
|
|
976
|
+
? renderHighlightedText(
|
|
977
|
+
this.stripLeadingLineBreaks(f.original),
|
|
978
|
+
true
|
|
979
|
+
)
|
|
980
|
+
: html`<span style="color: #bbb; font-style: italic;"
|
|
981
|
+
>Empty</span
|
|
982
|
+
>`}`,
|
|
983
|
+
translated:
|
|
984
|
+
f.translated !== null
|
|
985
|
+
? html`${renderHighlightedText(
|
|
986
|
+
this.stripLeadingLineBreaks(f.translated),
|
|
987
|
+
true
|
|
988
|
+
)}`
|
|
989
|
+
: null
|
|
907
990
|
}));
|
|
908
|
-
return this.renderPairedRows(
|
|
991
|
+
return this.renderPairedRows(
|
|
992
|
+
items,
|
|
993
|
+
entry,
|
|
994
|
+
handleBaseClick,
|
|
995
|
+
handleTranslationClickFn,
|
|
996
|
+
showTranslation
|
|
997
|
+
);
|
|
909
998
|
}
|
|
910
999
|
|
|
911
1000
|
// Single-row entries (send_msg, send_broadcast, etc.)
|
|
@@ -913,9 +1002,9 @@ export class MessageTable extends RapidElement {
|
|
|
913
1002
|
entry.kind === 'message' &&
|
|
914
1003
|
showTranslation &&
|
|
915
1004
|
this.hasAnyTranslation(entry);
|
|
916
|
-
const translationCellClass = `translation-cell ${
|
|
917
|
-
? 'has-translation'
|
|
918
|
-
|
|
1005
|
+
const translationCellClass = `translation-cell ${
|
|
1006
|
+
hasTranslation ? 'has-translation' : 'missing-translation'
|
|
1007
|
+
}`;
|
|
919
1008
|
|
|
920
1009
|
return html`
|
|
921
1010
|
<tr
|
|
@@ -936,23 +1025,41 @@ export class MessageTable extends RapidElement {
|
|
|
936
1025
|
? this.renderOriginalContent(entry)
|
|
937
1026
|
: isGroupEntry
|
|
938
1027
|
? html`
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1028
|
+
<div class="category-stack category-stack-original">
|
|
1029
|
+
${entry.rules.map(
|
|
1030
|
+
(c) => html`
|
|
1031
|
+
<div
|
|
1032
|
+
class="category-item category-original-item"
|
|
1033
|
+
>
|
|
1034
|
+
<span
|
|
1035
|
+
><span class="rule-operator"
|
|
1036
|
+
>${getOperatorConfig(c.type)?.name ||
|
|
1037
|
+
c.type}</span
|
|
1038
|
+
>
|
|
1039
|
+
${renderHighlightedText(
|
|
1040
|
+
c.arguments.join(', '),
|
|
1041
|
+
true
|
|
1042
|
+
)}</span
|
|
1043
|
+
>
|
|
1044
|
+
</div>
|
|
1045
|
+
`
|
|
1046
|
+
)}
|
|
1047
|
+
${entry.categories.map(
|
|
1048
|
+
(category) => html`
|
|
1049
|
+
<div
|
|
1050
|
+
class="category-item category-original-item"
|
|
1051
|
+
>
|
|
1052
|
+
<span
|
|
1053
|
+
>${renderHighlightedText(
|
|
1054
|
+
category.name,
|
|
1055
|
+
true
|
|
1056
|
+
)}</span
|
|
1057
|
+
>
|
|
1058
|
+
</div>
|
|
1059
|
+
`
|
|
1060
|
+
)}
|
|
1061
|
+
</div>
|
|
1062
|
+
`
|
|
956
1063
|
: ''}
|
|
957
1064
|
</div>
|
|
958
1065
|
</td>
|
|
@@ -964,8 +1071,8 @@ export class MessageTable extends RapidElement {
|
|
|
964
1071
|
title="Click to edit translation"
|
|
965
1072
|
>
|
|
966
1073
|
${entry.kind === 'message'
|
|
967
|
-
|
|
968
|
-
|
|
1074
|
+
? this.renderTranslatedContent(entry)
|
|
1075
|
+
: 'No translation'}
|
|
969
1076
|
</div>
|
|
970
1077
|
</td>`
|
|
971
1078
|
: ''}
|
package/src/flow/NodeEditor.ts
CHANGED
|
@@ -21,8 +21,13 @@ import {
|
|
|
21
21
|
} from './types';
|
|
22
22
|
import { CustomEventType } from '../interfaces';
|
|
23
23
|
import { generateUUID } from '../utils';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
formatIssueMessage,
|
|
26
|
+
resolveToLocalizationFormData,
|
|
27
|
+
resolveFromLocalizationFormData
|
|
28
|
+
} from './utils';
|
|
25
29
|
import { getTranslatableCategoriesForNode } from './categoryLocalization';
|
|
30
|
+
import { collectReservedCategoryErrors } from './categoryUtils';
|
|
26
31
|
import { FieldRenderer } from '../form/FieldRenderer';
|
|
27
32
|
import { renderMarkdownInline } from '../markdown';
|
|
28
33
|
import {
|
|
@@ -587,21 +592,17 @@ export class NodeEditor extends RapidElement {
|
|
|
587
592
|
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
588
593
|
|
|
589
594
|
// Check if we're in localization mode
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
) {
|
|
595
|
+
const actionToLocalization = actionConfig
|
|
596
|
+
? resolveToLocalizationFormData(actionConfig)
|
|
597
|
+
: undefined;
|
|
598
|
+
if (this.isTranslating && actionToLocalization) {
|
|
595
599
|
// Get localized values for this action
|
|
596
600
|
const localization =
|
|
597
601
|
this.flowDefinition?.localization?.[this.languageCode]?.[
|
|
598
602
|
this.action.uuid
|
|
599
603
|
] || {};
|
|
600
604
|
|
|
601
|
-
this.formData =
|
|
602
|
-
this.action,
|
|
603
|
-
localization
|
|
604
|
-
);
|
|
605
|
+
this.formData = actionToLocalization(this.action, localization);
|
|
605
606
|
} else if (actionConfig?.toFormData) {
|
|
606
607
|
this.formData = actionConfig.toFormData(this.action);
|
|
607
608
|
} else {
|
|
@@ -618,19 +619,19 @@ export class NodeEditor extends RapidElement {
|
|
|
618
619
|
const nodeConfig = this.getNodeConfig();
|
|
619
620
|
|
|
620
621
|
// Check if we're in localization mode for a node with localizable categories
|
|
622
|
+
const nodeToLocalization = nodeConfig
|
|
623
|
+
? resolveToLocalizationFormData(nodeConfig)
|
|
624
|
+
: undefined;
|
|
621
625
|
if (
|
|
622
626
|
this.isTranslating &&
|
|
623
627
|
nodeConfig?.localizable === 'categories' &&
|
|
624
|
-
|
|
628
|
+
nodeToLocalization
|
|
625
629
|
) {
|
|
626
630
|
// Get localized values for this node's categories
|
|
627
631
|
const localization =
|
|
628
632
|
this.flowDefinition?.localization?.[this.languageCode] || {};
|
|
629
633
|
|
|
630
|
-
this.formData =
|
|
631
|
-
this.node,
|
|
632
|
-
localization
|
|
633
|
-
);
|
|
634
|
+
this.formData = nodeToLocalization(this.node, localization);
|
|
634
635
|
|
|
635
636
|
const translatableCategoryUuids = new Set(
|
|
636
637
|
getTranslatableCategoriesForNode(
|
|
@@ -648,8 +649,8 @@ export class NodeEditor extends RapidElement {
|
|
|
648
649
|
this.formData = {
|
|
649
650
|
...this.formData,
|
|
650
651
|
categories: Object.fromEntries(
|
|
651
|
-
Object.entries(this.formData.categories).filter(
|
|
652
|
-
translatableCategoryUuids.has(categoryUuid)
|
|
652
|
+
Object.entries(this.formData.categories).filter(
|
|
653
|
+
([categoryUuid]) => translatableCategoryUuids.has(categoryUuid)
|
|
653
654
|
)
|
|
654
655
|
)
|
|
655
656
|
};
|
|
@@ -690,9 +691,7 @@ export class NodeEditor extends RapidElement {
|
|
|
690
691
|
this.flowInfo?.results &&
|
|
691
692
|
!this.flowDefinition?._ui?.nodes?.[this.node.uuid]
|
|
692
693
|
) {
|
|
693
|
-
const existingNames = new Set(
|
|
694
|
-
this.flowInfo.results.map((r) => r.name)
|
|
695
|
-
);
|
|
694
|
+
const existingNames = new Set(this.flowInfo.results.map((r) => r.name));
|
|
696
695
|
let candidate = 'Result';
|
|
697
696
|
let i = 2;
|
|
698
697
|
while (existingNames.has(candidate)) {
|
|
@@ -906,12 +905,12 @@ export class NodeEditor extends RapidElement {
|
|
|
906
905
|
if (this.action) {
|
|
907
906
|
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
908
907
|
|
|
909
|
-
|
|
910
|
-
actionConfig
|
|
911
|
-
|
|
912
|
-
) {
|
|
908
|
+
const actionFromLocalization = actionConfig
|
|
909
|
+
? resolveFromLocalizationFormData(actionConfig)
|
|
910
|
+
: undefined;
|
|
911
|
+
if (actionFromLocalization) {
|
|
913
912
|
// Save to localization structure
|
|
914
|
-
const localizationData =
|
|
913
|
+
const localizationData = actionFromLocalization(
|
|
915
914
|
processedFormData,
|
|
916
915
|
this.action
|
|
917
916
|
);
|
|
@@ -933,11 +932,11 @@ export class NodeEditor extends RapidElement {
|
|
|
933
932
|
if (this.node) {
|
|
934
933
|
const nodeConfig = this.getNodeConfig();
|
|
935
934
|
|
|
936
|
-
|
|
937
|
-
nodeConfig
|
|
938
|
-
|
|
939
|
-
) {
|
|
940
|
-
const localizationData =
|
|
935
|
+
const nodeFromLocalization = nodeConfig
|
|
936
|
+
? resolveFromLocalizationFormData(nodeConfig)
|
|
937
|
+
: undefined;
|
|
938
|
+
if (nodeConfig?.localizable === 'categories' && nodeFromLocalization) {
|
|
939
|
+
const localizationData = nodeFromLocalization(
|
|
941
940
|
processedFormData,
|
|
942
941
|
this.node
|
|
943
942
|
);
|
|
@@ -1238,40 +1237,7 @@ export class NodeEditor extends RapidElement {
|
|
|
1238
1237
|
}
|
|
1239
1238
|
|
|
1240
1239
|
private validateCategoryNames(errors: { [key: string]: string }): void {
|
|
1241
|
-
|
|
1242
|
-
// Prevents use of reserved category names that have special meaning in the system
|
|
1243
|
-
// Define reserved category names (case-insensitive)
|
|
1244
|
-
const reservedNames = [
|
|
1245
|
-
'other',
|
|
1246
|
-
'failure',
|
|
1247
|
-
'success',
|
|
1248
|
-
'all responses',
|
|
1249
|
-
'no response'
|
|
1250
|
-
];
|
|
1251
|
-
|
|
1252
|
-
// Check all form fields for category arrays
|
|
1253
|
-
Object.entries(this.formData).forEach(([fieldName, value]) => {
|
|
1254
|
-
if (Array.isArray(value) && fieldName === 'categories') {
|
|
1255
|
-
const categories = value.filter(
|
|
1256
|
-
(item: any) => item?.name && item.name.trim() !== ''
|
|
1257
|
-
);
|
|
1258
|
-
|
|
1259
|
-
// Check for reserved names
|
|
1260
|
-
const reservedUsed = categories
|
|
1261
|
-
.filter((item: any) => {
|
|
1262
|
-
const lowerName = item.name.trim().toLowerCase();
|
|
1263
|
-
return reservedNames.includes(lowerName);
|
|
1264
|
-
})
|
|
1265
|
-
.map((item: any) => item.name.trim()); // Preserve original case
|
|
1266
|
-
|
|
1267
|
-
if (reservedUsed.length > 0) {
|
|
1268
|
-
errors[fieldName] =
|
|
1269
|
-
`Reserved category names cannot be used: ${reservedUsed.join(
|
|
1270
|
-
', '
|
|
1271
|
-
)}`;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
});
|
|
1240
|
+
Object.assign(errors, collectReservedCategoryErrors(this.formData));
|
|
1275
1241
|
}
|
|
1276
1242
|
|
|
1277
1243
|
private formDataToNode(formData: FormData = this.formData): Node {
|
|
@@ -1673,14 +1639,17 @@ export class NodeEditor extends RapidElement {
|
|
|
1673
1639
|
? html`
|
|
1674
1640
|
<div class="category-localization-row">
|
|
1675
1641
|
<div class="original-name">
|
|
1676
|
-
<span class="rule-operator"
|
|
1642
|
+
<span class="rule-operator"
|
|
1643
|
+
>${ruleData.operatorName}</span
|
|
1644
|
+
>
|
|
1677
1645
|
<span>${arg}</span>
|
|
1678
1646
|
</div>
|
|
1679
1647
|
<div class="localized-name">
|
|
1680
1648
|
<temba-textinput
|
|
1681
1649
|
name="rule-${caseUuid}-${i}"
|
|
1682
1650
|
placeholder=""
|
|
1683
|
-
value="${ruleData.localizedArguments?.[i] ||
|
|
1651
|
+
value="${ruleData.localizedArguments?.[i] ||
|
|
1652
|
+
''}"
|
|
1684
1653
|
@change=${(e: Event) =>
|
|
1685
1654
|
this.handleRuleLocalizationChange(
|
|
1686
1655
|
caseUuid,
|
|
@@ -1750,7 +1719,10 @@ export class NodeEditor extends RapidElement {
|
|
|
1750
1719
|
this.formData.rules = {};
|
|
1751
1720
|
}
|
|
1752
1721
|
if (!this.formData.rules[caseUuid]) {
|
|
1753
|
-
this.formData.rules[caseUuid] = {
|
|
1722
|
+
this.formData.rules[caseUuid] = {
|
|
1723
|
+
originalArguments: [],
|
|
1724
|
+
localizedArguments: []
|
|
1725
|
+
};
|
|
1754
1726
|
}
|
|
1755
1727
|
if (!this.formData.rules[caseUuid].localizedArguments) {
|
|
1756
1728
|
this.formData.rules[caseUuid].localizedArguments = [];
|
|
@@ -2200,9 +2172,15 @@ export class NodeEditor extends RapidElement {
|
|
|
2200
2172
|
: undefined}
|
|
2201
2173
|
>
|
|
2202
2174
|
<div class="form-group-info">
|
|
2203
|
-
<div class="form-group-title"
|
|
2204
|
-
|
|
2205
|
-
|
|
2175
|
+
<div class="form-group-title">
|
|
2176
|
+
${icon
|
|
2177
|
+
? html`<temba-icon
|
|
2178
|
+
name=${icon}
|
|
2179
|
+
size="1"
|
|
2180
|
+
style="margin-right:6px;color:#999;"
|
|
2181
|
+
></temba-icon>`
|
|
2182
|
+
: ''}${label}
|
|
2183
|
+
</div>
|
|
2206
2184
|
${helpText
|
|
2207
2185
|
? html`<div class="form-group-help">
|
|
2208
2186
|
${renderMarkdownInline(helpText)}
|
|
@@ -2248,7 +2226,9 @@ export class NodeEditor extends RapidElement {
|
|
|
2248
2226
|
style="${[
|
|
2249
2227
|
contentPadding ? `--group-content-padding: ${contentPadding}` : '',
|
|
2250
2228
|
gap ? `gap: ${gap}` : ''
|
|
2251
|
-
]
|
|
2229
|
+
]
|
|
2230
|
+
.filter(Boolean)
|
|
2231
|
+
.join(';')}"
|
|
2252
2232
|
>
|
|
2253
2233
|
${items.map((item) =>
|
|
2254
2234
|
this.renderLayoutItem(item, config, renderedFields)
|
|
@@ -2288,7 +2268,10 @@ export class NodeEditor extends RapidElement {
|
|
|
2288
2268
|
}
|
|
2289
2269
|
|
|
2290
2270
|
return html`
|
|
2291
|
-
<temba-accordion
|
|
2271
|
+
<temba-accordion
|
|
2272
|
+
?multi=${multi}
|
|
2273
|
+
@toggle=${this.handleAccordionSectionToggle}
|
|
2274
|
+
>
|
|
2292
2275
|
${visibleSections.map((section) => {
|
|
2293
2276
|
const { label, collapsed = true, getValueCount } = section;
|
|
2294
2277
|
const stateKey = `accordion:${label}`;
|
|
@@ -213,8 +213,7 @@ export class RevisionsWindow extends RapidElement {
|
|
|
213
213
|
|
|
214
214
|
private cancelRevisionView() {
|
|
215
215
|
const store = getStore().getState();
|
|
216
|
-
const preservedLanguageCode =
|
|
217
|
-
this.browseLanguageCode || store.languageCode;
|
|
216
|
+
const preservedLanguageCode = this.browseLanguageCode || store.languageCode;
|
|
218
217
|
|
|
219
218
|
if (this.preRevertState) {
|
|
220
219
|
const currentInfo = store.flowInfo;
|
|
@@ -246,8 +245,7 @@ export class RevisionsWindow extends RapidElement {
|
|
|
246
245
|
if (!this.viewingRevision || !this.preRevertState) return;
|
|
247
246
|
|
|
248
247
|
const store = getStore().getState();
|
|
249
|
-
const preservedLanguageCode =
|
|
250
|
-
this.browseLanguageCode || store.languageCode;
|
|
248
|
+
const preservedLanguageCode = this.browseLanguageCode || store.languageCode;
|
|
251
249
|
|
|
252
250
|
const definitionToSave = {
|
|
253
251
|
...store.flowDefinition,
|