@capillarytech/creatives-library 8.0.329 → 8.0.330
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/constants/unified.js +0 -14
- package/package.json +1 -1
- package/services/api.js +0 -17
- package/services/tests/api.test.js +0 -85
- package/utils/commonUtils.js +0 -10
- package/utils/tests/commonUtil.test.js +0 -169
- package/v2Components/CapTagList/index.js +0 -10
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
- package/v2Components/CommonTestAndPreview/actions.js +0 -10
- package/v2Components/CommonTestAndPreview/constants.js +1 -53
- package/v2Components/CommonTestAndPreview/index.js +168 -1006
- package/v2Components/CommonTestAndPreview/messages.js +3 -147
- package/v2Components/CommonTestAndPreview/reducer.js +0 -10
- package/v2Components/CommonTestAndPreview/sagas.js +6 -15
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
- package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
- package/v2Components/FormBuilder/index.js +1 -7
- package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
- package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
- package/v2Containers/CreativesContainer/constants.js +0 -9
- package/v2Containers/CreativesContainer/index.js +93 -286
- package/v2Containers/CreativesContainer/index.scss +1 -51
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
- package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
- package/v2Containers/Rcs/constants.js +1 -34
- package/v2Containers/Rcs/index.js +884 -999
- package/v2Containers/Rcs/index.scss +6 -85
- package/v2Containers/Rcs/messages.js +1 -10
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
- package/v2Containers/Rcs/tests/index.test.js +38 -41
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +1 -379
- package/v2Containers/Rcs/utils.js +10 -358
- package/v2Containers/Sms/Create/index.js +38 -100
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +128 -609
- package/v2Containers/SmsTrai/Edit/messages.js +4 -9
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +2 -63
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +40 -90
- package/v2Containers/Templates/sagas.js +12 -57
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
- package/v2Containers/Templates/tests/sagas.test.js +123 -193
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
- package/v2Containers/TemplatesV2/index.js +23 -86
- package/v2Containers/Whatsapp/index.js +20 -3
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
- package/utils/templateVarUtils.js +0 -172
- package/utils/tests/templateVarUtils.test.js +0 -160
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
- package/v2Components/SmsFallback/constants.js +0 -73
- package/v2Components/SmsFallback/index.js +0 -955
- package/v2Components/SmsFallback/index.scss +0 -265
- package/v2Components/SmsFallback/messages.js +0 -78
- package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
- package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
- package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/index.js +0 -125
- package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
- package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
- package/v2Containers/SmsTrai/Edit/index.scss +0 -121
- package/v2Containers/Templates/TemplatesActionBar.js +0 -101
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getUnmappedDesc,
|
|
3
|
-
hasRcsVarTokens,
|
|
4
|
-
normalizeLibraryLoadedTitleDesc,
|
|
5
|
-
mergeRcsSmsFallBackContentFromDetails,
|
|
6
|
-
mergeRcsSmsFallbackVarMapLayers,
|
|
7
|
-
pickFirstSmsFallbackTemplateString,
|
|
8
|
-
syncCardVarMappedSemanticsFromSlots,
|
|
9
|
-
hasMeaningfulSmsFallbackShape,
|
|
10
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
11
|
-
} from '../rcsLibraryHydrationUtils';
|
|
12
|
-
import { rcsVarRegex } from '../constants';
|
|
13
|
-
|
|
14
|
-
describe('rcsLibraryHydrationUtils', () => {
|
|
15
|
-
describe('mergeRcsSmsFallBackContentFromDetails', () => {
|
|
16
|
-
it('returns empty object for missing details', () => {
|
|
17
|
-
expect(mergeRcsSmsFallBackContentFromDetails(null)).toEqual({});
|
|
18
|
-
expect(mergeRcsSmsFallBackContentFromDetails(undefined)).toEqual({});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('merges nested RCS smsFallBackContent with root mirror; nested wins on overlap', () => {
|
|
22
|
-
const details = {
|
|
23
|
-
versions: {
|
|
24
|
-
base: {
|
|
25
|
-
content: {
|
|
26
|
-
RCS: {
|
|
27
|
-
smsFallBackContent: {
|
|
28
|
-
smsTemplateName: 'OldName',
|
|
29
|
-
message: 'from nested',
|
|
30
|
-
smsContent: 'nested body',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
smsFallBackContent: {
|
|
37
|
-
message: 'from root',
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
const out = mergeRcsSmsFallBackContentFromDetails(details);
|
|
41
|
-
expect(out.message).toBe('from nested');
|
|
42
|
-
expect(out.smsContent).toBe('nested body');
|
|
43
|
-
expect(out.smsTemplateName).toBe('OldName');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('uses root when nested path is absent', () => {
|
|
47
|
-
const details = {
|
|
48
|
-
smsFallBackContent: { message: 'only root' },
|
|
49
|
-
};
|
|
50
|
-
expect(mergeRcsSmsFallBackContentFromDetails(details)).toEqual({ message: 'only root' });
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe('mergeRcsSmsFallbackVarMapLayers', () => {
|
|
55
|
-
it('merges API map with local so empty local object does not wipe API slots', () => {
|
|
56
|
-
const api = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'x' } };
|
|
57
|
-
const local = { rcsSmsFallbackVarMapped: {} };
|
|
58
|
-
expect(mergeRcsSmsFallbackVarMapLayers(api, local)).toEqual({ '{#a#}_0': 'x' });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('local slot values win on same key', () => {
|
|
62
|
-
const api = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'old' } };
|
|
63
|
-
const local = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'new' } };
|
|
64
|
-
expect(mergeRcsSmsFallbackVarMapLayers(api, local)).toEqual({ '{#a#}_0': 'new' });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('reads kebab-case var map from a layer when camel is absent', () => {
|
|
68
|
-
const api = { 'rcs-sms-fallback-var-mapped': { '{#b#}_1': 'y' } };
|
|
69
|
-
expect(mergeRcsSmsFallbackVarMapLayers(api, {})).toEqual({ '{#b#}_1': 'y' });
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('hasMeaningfulSmsFallbackShape', () => {
|
|
74
|
-
it('is false for empty / missing', () => {
|
|
75
|
-
expect(hasMeaningfulSmsFallbackShape(null)).toBe(false);
|
|
76
|
-
expect(hasMeaningfulSmsFallbackShape({})).toBe(false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('is true when template body or name or var map exists', () => {
|
|
80
|
-
expect(hasMeaningfulSmsFallbackShape({ message: 'hi' })).toBe(true);
|
|
81
|
-
expect(hasMeaningfulSmsFallbackShape({ smsTemplateName: 'T' })).toBe(true);
|
|
82
|
-
expect(hasMeaningfulSmsFallbackShape({ rcsSmsFallbackVarMapped: { '{#a#}_0': 'x' } })).toBe(true);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('getLibrarySmsFallbackApiBaselineFromTemplateData', () => {
|
|
87
|
-
it('merges root and nested; nested wins on overlap', () => {
|
|
88
|
-
const templateData = {
|
|
89
|
-
smsFallBackContent: { message: 'root' },
|
|
90
|
-
versions: {
|
|
91
|
-
base: {
|
|
92
|
-
content: {
|
|
93
|
-
RCS: {
|
|
94
|
-
smsFallBackContent: { message: 'nested', smsTemplateName: 'N' },
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
const out = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
101
|
-
expect(out.message).toBe('nested');
|
|
102
|
-
expect(out.smsTemplateName).toBe('N');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('pickFirstSmsFallbackTemplateString', () => {
|
|
107
|
-
it('prefers smsTemplateContent over resolved message so DLT tokens stay in the string', () => {
|
|
108
|
-
const sms = {
|
|
109
|
-
message: 'Hello user',
|
|
110
|
-
smsTemplateContent: 'Hello {#name#}',
|
|
111
|
-
};
|
|
112
|
-
expect(pickFirstSmsFallbackTemplateString(sms)).toBe('Hello {#name#}');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('skips whitespace-only templateContent and uses next field', () => {
|
|
116
|
-
expect(
|
|
117
|
-
pickFirstSmsFallbackTemplateString({
|
|
118
|
-
templateContent: ' ',
|
|
119
|
-
message: 'x',
|
|
120
|
-
}),
|
|
121
|
-
).toBe('x');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('getUnmappedDesc', () => {
|
|
126
|
-
it('returns empty string for falsy str', () => {
|
|
127
|
-
expect(getUnmappedDesc('', { a: '1' })).toBe('');
|
|
128
|
-
expect(getUnmappedDesc(null, { a: '1' })).toBe('');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('returns str when mapping is empty or missing', () => {
|
|
132
|
-
expect(getUnmappedDesc('hello', null)).toBe('hello');
|
|
133
|
-
expect(getUnmappedDesc('hello', {})).toBe('hello');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('replaces literal values with {{key}} placeholders', () => {
|
|
137
|
-
expect(getUnmappedDesc('Hi Alice', { user_name: 'Alice' })).toBe('Hi {{user_name}}');
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('skips mapping entries with empty or whitespace-only values', () => {
|
|
141
|
-
expect(getUnmappedDesc('x', { a: '', b: ' ' })).toBe('x');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('treats already-braced raw values without duplicating braced needle', () => {
|
|
145
|
-
expect(getUnmappedDesc('v {{user_id}}', { user_id: '{{user_id}}' })).toContain('{{user_id}}');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('adds braced needle when resolved value is not already in {{…}} form', () => {
|
|
149
|
-
const out = getUnmappedDesc('Hello Bob', { nick: 'Bob' });
|
|
150
|
-
expect(out).toBe('Hello {{nick}}');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('ignores undefined mapping values', () => {
|
|
154
|
-
expect(getUnmappedDesc('x', { a: undefined })).toBe('x');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('matches longer needles first to avoid partial replaces', () => {
|
|
158
|
-
const out = getUnmappedDesc('ab', { x: 'a', y: 'ab' });
|
|
159
|
-
expect(out).toBe('{{y}}');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('escapes regex metacharacters in resolved values', () => {
|
|
163
|
-
expect(getUnmappedDesc('price 9.99', { p: '9.99' })).toBe('price {{p}}');
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
describe('hasRcsVarTokens', () => {
|
|
168
|
-
it('is false for empty or non-matching strings', () => {
|
|
169
|
-
expect(hasRcsVarTokens('', rcsVarRegex)).toBe(false);
|
|
170
|
-
expect(hasRcsVarTokens('plain', rcsVarRegex)).toBe(false);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('is true when string contains {{word}} tokens', () => {
|
|
174
|
-
expect(hasRcsVarTokens('{{1}} hi', rcsVarRegex)).toBe(true);
|
|
175
|
-
expect(hasRcsVarTokens('{{user_id}}', rcsVarRegex)).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe('normalizeLibraryLoadedTitleDesc', () => {
|
|
180
|
-
const cardVarMappedAfterHydration = { user_id: '123' };
|
|
181
|
-
|
|
182
|
-
it('returns originals in full mode', () => {
|
|
183
|
-
const out = normalizeLibraryLoadedTitleDesc({
|
|
184
|
-
loadedTitle: 'Hello 123',
|
|
185
|
-
loadedDesc: 'Desc',
|
|
186
|
-
isFullMode: true,
|
|
187
|
-
cardVarMappedAfterHydration,
|
|
188
|
-
rcsVarRegex,
|
|
189
|
-
});
|
|
190
|
-
expect(out).toEqual({ normalizedTitle: 'Hello 123', normalizedDesc: 'Desc' });
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('unmaps when library mode, map present, and no mustache tokens', () => {
|
|
194
|
-
const out = normalizeLibraryLoadedTitleDesc({
|
|
195
|
-
loadedTitle: 'Hello 123',
|
|
196
|
-
loadedDesc: 'World 123',
|
|
197
|
-
isFullMode: false,
|
|
198
|
-
cardVarMappedAfterHydration,
|
|
199
|
-
rcsVarRegex,
|
|
200
|
-
});
|
|
201
|
-
expect(out.normalizedTitle).toBe('Hello {{user_id}}');
|
|
202
|
-
expect(out.normalizedDesc).toBe('World {{user_id}}');
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('keeps {{numeric}} slots when tokens already exist', () => {
|
|
206
|
-
const out = normalizeLibraryLoadedTitleDesc({
|
|
207
|
-
loadedTitle: 'Hi {{1}}',
|
|
208
|
-
loadedDesc: 'Body 123',
|
|
209
|
-
isFullMode: false,
|
|
210
|
-
cardVarMappedAfterHydration,
|
|
211
|
-
rcsVarRegex,
|
|
212
|
-
});
|
|
213
|
-
expect(out.normalizedTitle).toBe('Hi {{1}}');
|
|
214
|
-
expect(out.normalizedDesc).toBe('Body {{user_id}}');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('does not unmap when cardVarMappedAfterHydration is empty', () => {
|
|
218
|
-
const out = normalizeLibraryLoadedTitleDesc({
|
|
219
|
-
loadedTitle: 'Hello',
|
|
220
|
-
loadedDesc: 'D',
|
|
221
|
-
isFullMode: false,
|
|
222
|
-
cardVarMappedAfterHydration: {},
|
|
223
|
-
rcsVarRegex,
|
|
224
|
-
});
|
|
225
|
-
expect(out).toEqual({ normalizedTitle: 'Hello', normalizedDesc: 'D' });
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
describe('syncCardVarMappedSemanticsFromSlots', () => {
|
|
230
|
-
it('copies numeric slot values onto empty semantic keys (campaign round-trip)', () => {
|
|
231
|
-
const out = syncCardVarMappedSemanticsFromSlots(
|
|
232
|
-
{ 1: '[FirstName]', user_name: '', 2: 'x', other: '' },
|
|
233
|
-
'Hi {{user_name}}',
|
|
234
|
-
'More {{other}}',
|
|
235
|
-
rcsVarRegex,
|
|
236
|
-
);
|
|
237
|
-
expect(out.user_name).toBe('[FirstName]');
|
|
238
|
-
expect(out.other).toBe('x');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('does not overwrite non-empty semantic keys', () => {
|
|
242
|
-
const out = syncCardVarMappedSemanticsFromSlots(
|
|
243
|
-
{ 1: 'from-slot', user_name: 'kept' },
|
|
244
|
-
'Hello {{user_name}}',
|
|
245
|
-
'',
|
|
246
|
-
rcsVarRegex,
|
|
247
|
-
);
|
|
248
|
-
expect(out.user_name).toBe('kept');
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared SMS FormBuilder formData helpers for Sms/Create (and any embedded host).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {object} formData FormBuilder state (same shape as Sms/Create `this.state.formData`)
|
|
7
|
-
* @param {number} currentTab 1-based tab index
|
|
8
|
-
* @returns {string} Raw message body or ''
|
|
9
|
-
*/
|
|
10
|
-
export function getSmsMessageFromFormData(formData, currentTab) {
|
|
11
|
-
if (formData == null || typeof formData !== 'object') {
|
|
12
|
-
return '';
|
|
13
|
-
}
|
|
14
|
-
const tab = currentTab != null && currentTab > 0 ? currentTab : 1;
|
|
15
|
-
const currentTabData = formData[tab - 1];
|
|
16
|
-
if (currentTabData && typeof currentTabData === 'object') {
|
|
17
|
-
const versionedKey = tab > 1 ? `sms-editor${tab}` : 'sms-editor';
|
|
18
|
-
if (Object.prototype.hasOwnProperty.call(currentTabData, versionedKey)) {
|
|
19
|
-
const v = currentTabData[versionedKey];
|
|
20
|
-
// Key exists — commit to this version's value rather than falling through to base.
|
|
21
|
-
// Treat null/undefined as empty so a cleared version correctly reports as empty.
|
|
22
|
-
return (v != null && v !== '') ? String(v) : '';
|
|
23
|
-
}
|
|
24
|
-
if (currentTabData['sms-editor'] != null) {
|
|
25
|
-
return String(currentTabData['sms-editor']);
|
|
26
|
-
}
|
|
27
|
-
const activeTab = currentTabData.activeTab || 'base';
|
|
28
|
-
if (currentTabData[activeTab]?.['sms-editor'] != null) {
|
|
29
|
-
return String(currentTabData[activeTab]['sms-editor']);
|
|
30
|
-
}
|
|
31
|
-
if (currentTabData.base?.['sms-editor'] != null) {
|
|
32
|
-
return String(currentTabData.base['sms-editor']);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const rootBase = formData.base;
|
|
36
|
-
if (rootBase && typeof rootBase === 'object' && rootBase['sms-editor'] != null) {
|
|
37
|
-
return String(rootBase['sms-editor']);
|
|
38
|
-
}
|
|
39
|
-
return '';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {number} [tabCount] Total number of versions/tabs. When >1 all versions are checked.
|
|
44
|
-
* @returns {{ isTemplateNameEmpty: boolean, isMessageEmpty: boolean }}
|
|
45
|
-
*/
|
|
46
|
-
export function getSmsEmbeddedFooterValidity(formData, tabCount) {
|
|
47
|
-
const rawName = formData?.['template-name'];
|
|
48
|
-
const name = rawName != null && rawName !== '' ? String(rawName).trim() : '';
|
|
49
|
-
|
|
50
|
-
// Check ALL versions: if any version's message is empty, Done should be disabled.
|
|
51
|
-
// With a single version this is equivalent to the previous single-tab check.
|
|
52
|
-
const count = tabCount != null && tabCount > 1 ? tabCount : 1;
|
|
53
|
-
let isMessageEmpty = false;
|
|
54
|
-
for (let i = 1; i <= count; i++) {
|
|
55
|
-
const content = getSmsMessageFromFormData(formData, i);
|
|
56
|
-
const msg = content != null && content !== '' ? String(content).trim() : '';
|
|
57
|
-
if (!msg) {
|
|
58
|
-
isMessageEmpty = true;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
isTemplateNameEmpty: !name,
|
|
65
|
-
isMessageEmpty,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSmsMessageFromFormData,
|
|
3
|
-
getSmsEmbeddedFooterValidity,
|
|
4
|
-
} from '../smsFormDataHelpers';
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// getSmsMessageFromFormData
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
describe('getSmsMessageFromFormData', () => {
|
|
11
|
-
describe('null / invalid formData guard', () => {
|
|
12
|
-
it('returns empty string when formData is null', () => {
|
|
13
|
-
expect(getSmsMessageFromFormData(null, 1)).toBe('');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('returns empty string when formData is undefined', () => {
|
|
17
|
-
expect(getSmsMessageFromFormData(undefined, 1)).toBe('');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('returns empty string when formData is a string (not an object)', () => {
|
|
21
|
-
expect(getSmsMessageFromFormData('bad', 1)).toBe('');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('returns empty string when formData is a number', () => {
|
|
25
|
-
expect(getSmsMessageFromFormData(42, 1)).toBe('');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('currentTab normalisation', () => {
|
|
30
|
-
it('defaults to tab 1 when currentTab is null', () => {
|
|
31
|
-
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
32
|
-
expect(getSmsMessageFromFormData(formData, null)).toBe('hello');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('defaults to tab 1 when currentTab is undefined', () => {
|
|
36
|
-
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
37
|
-
expect(getSmsMessageFromFormData(formData, undefined)).toBe('hello');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('defaults to tab 1 when currentTab is 0', () => {
|
|
41
|
-
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
42
|
-
expect(getSmsMessageFromFormData(formData, 0)).toBe('hello');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('defaults to tab 1 when currentTab is negative', () => {
|
|
46
|
-
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
47
|
-
expect(getSmsMessageFromFormData(formData, -5)).toBe('hello');
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('versioned key lookup', () => {
|
|
52
|
-
it('uses "sms-editor" key for tab 1', () => {
|
|
53
|
-
const formData = { 0: { 'sms-editor': 'tab1 msg' } };
|
|
54
|
-
expect(getSmsMessageFromFormData(formData, 1)).toBe('tab1 msg');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('uses "sms-editor2" versioned key for tab 2', () => {
|
|
58
|
-
const formData = { 1: { 'sms-editor2': 'tab2 msg', 'sms-editor': 'fallback' } };
|
|
59
|
-
expect(getSmsMessageFromFormData(formData, 2)).toBe('tab2 msg');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('uses "sms-editor3" versioned key for tab 3', () => {
|
|
63
|
-
const formData = { 2: { 'sms-editor3': 'tab3 msg' } };
|
|
64
|
-
expect(getSmsMessageFromFormData(formData, 3)).toBe('tab3 msg');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('returns empty string when versioned key exists but is null (cleared version)', () => {
|
|
68
|
-
const formData = { 1: { 'sms-editor2': null } };
|
|
69
|
-
expect(getSmsMessageFromFormData(formData, 2)).toBe('');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('returns empty string when versioned key exists but is empty string', () => {
|
|
73
|
-
const formData = { 1: { 'sms-editor2': '' } };
|
|
74
|
-
expect(getSmsMessageFromFormData(formData, 2)).toBe('');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('coerces a numeric value in versioned key to string', () => {
|
|
78
|
-
const formData = { 1: { 'sms-editor2': 12345 } };
|
|
79
|
-
expect(getSmsMessageFromFormData(formData, 2)).toBe('12345');
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('"sms-editor" flat key fallback (versioned key absent)', () => {
|
|
84
|
-
it('falls back to "sms-editor" when versioned key is absent for tab 2', () => {
|
|
85
|
-
const formData = { 1: { 'sms-editor': 'flat fallback' } };
|
|
86
|
-
expect(getSmsMessageFromFormData(formData, 2)).toBe('flat fallback');
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('activeTab nested fallback', () => {
|
|
91
|
-
it('falls back to activeTab["sms-editor"] when neither versioned key nor flat "sms-editor" is present', () => {
|
|
92
|
-
// The versioned key must be absent entirely for the activeTab fallback to be reached.
|
|
93
|
-
// (If 'sms-editor' exists but is null, the function commits to '' without falling through.)
|
|
94
|
-
const formData = {
|
|
95
|
-
0: {
|
|
96
|
-
activeTab: 'variant1',
|
|
97
|
-
variant1: { 'sms-editor': 'variant msg' },
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
expect(getSmsMessageFromFormData(formData, 1)).toBe('variant msg');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('falls back to base["sms-editor"] when activeTab entry lacks "sms-editor" and flat key is absent', () => {
|
|
104
|
-
const formData = {
|
|
105
|
-
0: {
|
|
106
|
-
activeTab: 'variant1',
|
|
107
|
-
variant1: {},
|
|
108
|
-
base: { 'sms-editor': 'base msg' },
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
expect(getSmsMessageFromFormData(formData, 1)).toBe('base msg');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('defaults activeTab to "base" when activeTab prop is not set and flat key is absent', () => {
|
|
115
|
-
const formData = {
|
|
116
|
-
0: {
|
|
117
|
-
base: { 'sms-editor': 'default base msg' },
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
expect(getSmsMessageFromFormData(formData, 1)).toBe('default base msg');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('root formData.base fallback', () => {
|
|
125
|
-
it('falls back to formData.base["sms-editor"] when tab slot is missing', () => {
|
|
126
|
-
const formData = {
|
|
127
|
-
base: { 'sms-editor': 'root base msg' },
|
|
128
|
-
};
|
|
129
|
-
expect(getSmsMessageFromFormData(formData, 1)).toBe('root base msg');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('returns empty string when even root base has no "sms-editor"', () => {
|
|
133
|
-
const formData = { base: {} };
|
|
134
|
-
expect(getSmsMessageFromFormData(formData, 1)).toBe('');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('returns empty string when formData has no relevant keys at all', () => {
|
|
138
|
-
expect(getSmsMessageFromFormData({}, 1)).toBe('');
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
// getSmsEmbeddedFooterValidity
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
describe('getSmsEmbeddedFooterValidity', () => {
|
|
148
|
-
describe('isTemplateNameEmpty', () => {
|
|
149
|
-
it('is true when template-name is absent', () => {
|
|
150
|
-
const formData = { 0: { 'sms-editor': 'msg' } };
|
|
151
|
-
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
152
|
-
expect(isTemplateNameEmpty).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('is true when template-name is empty string', () => {
|
|
156
|
-
const formData = { 'template-name': '', 0: { 'sms-editor': 'msg' } };
|
|
157
|
-
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
158
|
-
expect(isTemplateNameEmpty).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('is true when template-name is whitespace only', () => {
|
|
162
|
-
const formData = { 'template-name': ' ', 0: { 'sms-editor': 'msg' } };
|
|
163
|
-
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
164
|
-
expect(isTemplateNameEmpty).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('is false when template-name is a non-empty string', () => {
|
|
168
|
-
const formData = { 'template-name': 'My Template', 0: { 'sms-editor': 'msg' } };
|
|
169
|
-
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
170
|
-
expect(isTemplateNameEmpty).toBe(false);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe('isMessageEmpty — single tab', () => {
|
|
175
|
-
it('is true when message is empty string', () => {
|
|
176
|
-
const formData = { 'template-name': 'T', 0: { 'sms-editor': '' } };
|
|
177
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
178
|
-
expect(isMessageEmpty).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('is true when message is whitespace only', () => {
|
|
182
|
-
const formData = { 'template-name': 'T', 0: { 'sms-editor': ' ' } };
|
|
183
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
184
|
-
expect(isMessageEmpty).toBe(true);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('is true when no sms-editor key at all', () => {
|
|
188
|
-
const formData = { 'template-name': 'T', 0: {} };
|
|
189
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
190
|
-
expect(isMessageEmpty).toBe(true);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('is false when message is non-empty', () => {
|
|
194
|
-
const formData = { 'template-name': 'T', 0: { 'sms-editor': 'Hello world' } };
|
|
195
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
196
|
-
expect(isMessageEmpty).toBe(false);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe('isMessageEmpty — multiple tabs', () => {
|
|
201
|
-
it('is false when all tabs have non-empty messages', () => {
|
|
202
|
-
const formData = {
|
|
203
|
-
'template-name': 'T',
|
|
204
|
-
0: { 'sms-editor': 'Tab 1 message' },
|
|
205
|
-
1: { 'sms-editor2': 'Tab 2 message' },
|
|
206
|
-
2: { 'sms-editor3': 'Tab 3 message' },
|
|
207
|
-
};
|
|
208
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 3);
|
|
209
|
-
expect(isMessageEmpty).toBe(false);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('is true when the second tab has an empty message', () => {
|
|
213
|
-
const formData = {
|
|
214
|
-
'template-name': 'T',
|
|
215
|
-
0: { 'sms-editor': 'Tab 1 message' },
|
|
216
|
-
1: { 'sms-editor2': '' },
|
|
217
|
-
};
|
|
218
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 2);
|
|
219
|
-
expect(isMessageEmpty).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('is true when the third tab has no message', () => {
|
|
223
|
-
const formData = {
|
|
224
|
-
'template-name': 'T',
|
|
225
|
-
0: { 'sms-editor': 'msg1' },
|
|
226
|
-
1: { 'sms-editor2': 'msg2' },
|
|
227
|
-
2: {},
|
|
228
|
-
};
|
|
229
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 3);
|
|
230
|
-
expect(isMessageEmpty).toBe(true);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('treats tabCount=1 as single-tab check even when not explicitly provided', () => {
|
|
234
|
-
const formData = { 'template-name': 'T', 0: { 'sms-editor': 'msg' } };
|
|
235
|
-
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData);
|
|
236
|
-
expect(isMessageEmpty).toBe(false);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe('both fields together', () => {
|
|
241
|
-
it('returns both empty when template name absent and message empty', () => {
|
|
242
|
-
const formData = { 0: { 'sms-editor': '' } };
|
|
243
|
-
const result = getSmsEmbeddedFooterValidity(formData, 1);
|
|
244
|
-
expect(result).toEqual({ isTemplateNameEmpty: true, isMessageEmpty: true });
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('returns both non-empty when template name and message are present', () => {
|
|
248
|
-
const formData = { 'template-name': 'My Template', 0: { 'sms-editor': 'Hello!' } };
|
|
249
|
-
const result = getSmsEmbeddedFooterValidity(formData, 1);
|
|
250
|
-
expect(result).toEqual({ isTemplateNameEmpty: false, isMessageEmpty: false });
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
@import '~@capillarytech/cap-ui-library/styles/_variables';
|
|
2
|
-
|
|
3
|
-
.sms-trai-segmented-editor {
|
|
4
|
-
.ant-input {
|
|
5
|
-
margin-bottom: 0.125rem;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
* DLT SMS fallback edit only: mirror RCS rich-card message box (`.cap-rcs-creatives` + default
|
|
11
|
-
* VarSegment `rcs-edit-template-message-input` / `rcs-edit-template-message-split`).
|
|
12
|
-
*/
|
|
13
|
-
.sms-trai-edit-rcs-fallback {
|
|
14
|
-
width: 100%;
|
|
15
|
-
|
|
16
|
-
.rcs_text_area_wrapper {
|
|
17
|
-
position: relative;
|
|
18
|
-
width: 100%;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input {
|
|
22
|
-
background-color: $CAP_G10;
|
|
23
|
-
padding: $CAP_SPACE_12 $CAP_SPACE_16;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-split {
|
|
27
|
-
margin-bottom: $CAP_SPACE_08;
|
|
28
|
-
overflow: hidden;
|
|
29
|
-
text-overflow: ellipsis;
|
|
30
|
-
color: $FONT_COLOR_03;
|
|
31
|
-
font-weight: 500;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input .ant-input,
|
|
35
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input textarea.ant-input {
|
|
36
|
-
width: 100%;
|
|
37
|
-
box-sizing: border-box;
|
|
38
|
-
min-height: 2.5rem;
|
|
39
|
-
padding-top: $CAP_SPACE_08;
|
|
40
|
-
padding-bottom: $CAP_SPACE_08;
|
|
41
|
-
border-color: $CAP_G07;
|
|
42
|
-
box-shadow: none;
|
|
43
|
-
overflow: hidden;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input .ant-input:focus,
|
|
47
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input .ant-input:active,
|
|
48
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input textarea.ant-input:focus,
|
|
49
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input textarea.ant-input:active {
|
|
50
|
-
border-color: $CAP_G07;
|
|
51
|
-
box-shadow: none;
|
|
52
|
-
outline: none;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/*
|
|
56
|
-
* DLT per-slot hint: long selector (under CapSpin + message row) for specificity without !important.
|
|
57
|
-
*/
|
|
58
|
-
.rcs_text_area_wrapper .rcs-edit-template-message-input .var-segment-message-editor__var-slot {
|
|
59
|
-
display: flex;
|
|
60
|
-
flex-direction: column;
|
|
61
|
-
align-items: stretch;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.rcs_text_area_wrapper
|
|
65
|
-
.rcs-edit-template-message-input
|
|
66
|
-
.var-segment-message-editor__var-slot
|
|
67
|
-
span.sms-trai-rcs-fallback-var-hint {
|
|
68
|
-
display: block;
|
|
69
|
-
box-sizing: border-box;
|
|
70
|
-
flex-shrink: 0;
|
|
71
|
-
margin-top: $CAP_SPACE_04;
|
|
72
|
-
margin-left: auto;
|
|
73
|
-
margin-right: 0;
|
|
74
|
-
width: fit-content;
|
|
75
|
-
max-width: 100%;
|
|
76
|
-
text-align: right;
|
|
77
|
-
color: $FONT_COLOR_02;
|
|
78
|
-
font-weight: 400;
|
|
79
|
-
font-size: 0.75rem;
|
|
80
|
-
line-height: 1rem;
|
|
81
|
-
opacity: 0.92;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Footer “1 SMS (n characters)” — match RCS message typography, not muted gray */
|
|
85
|
-
.rcs-character-count,
|
|
86
|
-
.rcs-character-count--compact {
|
|
87
|
-
color: $FONT_COLOR_03;
|
|
88
|
-
font-weight: 500;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.rcs-character-count {
|
|
93
|
-
display: block;
|
|
94
|
-
margin-top: $CAP_SPACE_16;
|
|
95
|
-
margin-bottom: $CAP_SPACE_04;
|
|
96
|
-
text-align: right;
|
|
97
|
-
color: $FONT_COLOR_02;
|
|
98
|
-
width: 100%;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.rcs-character-count--compact {
|
|
102
|
-
font-size: 0.75rem;
|
|
103
|
-
line-height: 1rem;
|
|
104
|
-
font-weight: 400;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* Non–RCS fallback: segmented editor shell + character count row (was inline styles on CapRow) */
|
|
108
|
-
.sms-trai-editor-segment-row {
|
|
109
|
-
background-color: $CAP_G10;
|
|
110
|
-
padding: $CAP_SPACE_16;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.sms-trai-length-row {
|
|
114
|
-
display: flex;
|
|
115
|
-
justify-content: flex-end;
|
|
116
|
-
margin-top: $CAP_SPACE_04;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.sms-trai-edit-bottom-spacer {
|
|
120
|
-
margin-bottom: 6.25rem;
|
|
121
|
-
}
|