@nyaruka/temba-components 0.129.8 → 0.129.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 +37 -3
- package/demo/data/flows/sample-flow.json +186 -96
- package/demo/test-colorpicker.html +30 -0
- package/dist/temba-components.js +1126 -1111
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/excellent/helpers.js +2 -2
- package/out-tsc/src/excellent/helpers.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +25 -7
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +11 -1
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +133 -290
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/call_llm.js +56 -3
- package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
- package/out-tsc/src/flow/actions/call_webhook.js +1 -1
- package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
- package/out-tsc/src/flow/actions/open_ticket.js +65 -3
- package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +75 -0
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/config.js +4 -0
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +0 -65
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +63 -117
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/BaseListEditor.js +4 -3
- package/out-tsc/src/form/BaseListEditor.js.map +1 -1
- package/out-tsc/src/form/Checkbox.js +77 -24
- package/out-tsc/src/form/Checkbox.js.map +1 -1
- package/out-tsc/src/form/ColorPicker.js +28 -40
- package/out-tsc/src/form/ColorPicker.js.map +1 -1
- package/out-tsc/src/form/Completion.js +44 -53
- package/out-tsc/src/form/Completion.js.map +1 -1
- package/out-tsc/src/form/Compose.js +7 -8
- package/out-tsc/src/form/Compose.js.map +1 -1
- package/out-tsc/src/form/ContactSearch.js +3 -4
- package/out-tsc/src/form/ContactSearch.js.map +1 -1
- package/out-tsc/src/form/DatePicker.js +29 -36
- package/out-tsc/src/form/DatePicker.js.map +1 -1
- package/out-tsc/src/form/{FormField.js → FieldElement.js} +81 -53
- package/out-tsc/src/form/FieldElement.js.map +1 -0
- package/out-tsc/src/form/FieldRenderer.js +306 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -0
- package/out-tsc/src/form/ImagePicker.js +122 -126
- package/out-tsc/src/form/ImagePicker.js.map +1 -1
- package/out-tsc/src/form/KeyValueEditor.js +41 -37
- package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
- package/out-tsc/src/form/MessageEditor.js +55 -63
- package/out-tsc/src/form/MessageEditor.js.map +1 -1
- package/out-tsc/src/form/TembaSlider.js +3 -3
- package/out-tsc/src/form/TembaSlider.js.map +1 -1
- package/out-tsc/src/form/TemplateEditor.js +3 -3
- package/out-tsc/src/form/TemplateEditor.js.map +1 -1
- package/out-tsc/src/form/TextInput.js +23 -27
- package/out-tsc/src/form/TextInput.js.map +1 -1
- package/out-tsc/src/form/select/Select.js +57 -35
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/form/select/UserSelect.js +8 -9
- package/out-tsc/src/form/select/UserSelect.js.map +1 -1
- package/out-tsc/src/form/select/WorkspaceSelect.js +7 -8
- package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +62 -44
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/live/ContactFieldEditor.js.map +1 -1
- package/out-tsc/src/markdown.js +13 -11
- package/out-tsc/src/markdown.js.map +1 -1
- package/out-tsc/temba-modules.js +3 -2
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +2 -0
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +148 -0
- package/out-tsc/test/NodeHelper.js.map +1 -0
- package/out-tsc/test/actions/call_llm.test.js +103 -0
- package/out-tsc/test/actions/call_llm.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
- package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +150 -0
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
- package/out-tsc/test/temba-add-input-labels.test.js +70 -0
- package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
- package/out-tsc/test/temba-checkbox.test.js +16 -0
- package/out-tsc/test/temba-checkbox.test.js.map +1 -1
- package/out-tsc/test/temba-field-renderer.test.js +296 -0
- package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
- package/out-tsc/test/temba-integration-markdown.test.js +2 -4
- package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
- package/out-tsc/test/temba-markdown.test.js +1 -1
- package/out-tsc/test/temba-markdown.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +400 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +6 -3
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-slider.test.js +0 -1
- package/out-tsc/test/temba-slider.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +1 -1
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
- package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
- package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
- package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
- package/screenshots/truth/checkbox/checked.png +0 -0
- package/screenshots/truth/checkbox/default.png +0 -0
- package/screenshots/truth/colorpicker/default.png +0 -0
- package/screenshots/truth/colorpicker/focused.png +0 -0
- package/screenshots/truth/colorpicker/initialized.png +0 -0
- package/screenshots/truth/colorpicker/selected.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/send_msg.png +0 -0
- package/screenshots/truth/editor/set_contact_language.png +0 -0
- package/screenshots/truth/editor/set_contact_name.png +0 -0
- package/screenshots/truth/editor/set_run_result.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
- package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/context-comparison.png +0 -0
- package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
- package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
- package/screenshots/truth/field-renderer/select-multi.png +0 -0
- package/screenshots/truth/field-renderer/select-no-label.png +0 -0
- package/screenshots/truth/field-renderer/select-with-label.png +0 -0
- package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/text-no-label.png +0 -0
- package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
- package/screenshots/truth/field-renderer/text-with-label.png +0 -0
- package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
- package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
- package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/run-list/basic.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/src/events.ts +12 -6
- package/src/excellent/helpers.ts +2 -2
- package/src/flow/CanvasNode.ts +22 -1
- package/src/flow/Editor.ts +12 -1
- package/src/flow/NodeEditor.ts +186 -374
- package/src/flow/actions/add_input_labels.ts +45 -0
- package/src/flow/actions/call_llm.ts +57 -3
- package/src/flow/actions/call_webhook.ts +1 -1
- package/src/flow/actions/open_ticket.ts +74 -3
- package/src/flow/actions/set_run_result.ts +83 -0
- package/src/flow/config.ts +4 -0
- package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
- package/src/flow/nodes/split_by_ticket.ts +19 -0
- package/src/flow/nodes/wait_for_response.ts +28 -1
- package/src/flow/types.ts +26 -127
- package/src/form/ArrayEditor.ts +79 -139
- package/src/form/BaseListEditor.ts +4 -4
- package/src/form/Checkbox.ts +81 -24
- package/src/form/ColorPicker.ts +31 -43
- package/src/form/Completion.ts +49 -56
- package/src/form/Compose.ts +8 -8
- package/src/form/ContactSearch.ts +3 -4
- package/src/form/DatePicker.ts +32 -38
- package/src/form/{FormField.ts → FieldElement.ts} +108 -55
- package/src/form/FieldRenderer.ts +466 -0
- package/src/form/ImagePicker.ts +107 -110
- package/src/form/KeyValueEditor.ts +43 -39
- package/src/form/MessageEditor.ts +61 -67
- package/src/form/TembaSlider.ts +3 -3
- package/src/form/TemplateEditor.ts +3 -3
- package/src/form/TextInput.ts +26 -29
- package/src/form/select/Select.ts +63 -37
- package/src/form/select/UserSelect.ts +10 -11
- package/src/form/select/WorkspaceSelect.ts +9 -10
- package/src/live/ContactChat.ts +62 -47
- package/src/live/ContactFieldEditor.ts +2 -2
- package/src/markdown.ts +19 -11
- package/src/store/flow-definition.d.ts +5 -2
- package/static/api/labels.json +31 -0
- package/static/api/topics.json +24 -9
- package/static/api/users.json +35 -16
- package/static/css/temba-components.css +3 -3
- package/stress-test.js +18 -13
- package/temba-modules.ts +3 -2
- package/test/ActionHelper.ts +2 -0
- package/test/NodeHelper.ts +184 -0
- package/test/actions/call_llm.test.ts +137 -0
- package/test/nodes/README.md +78 -0
- package/test/nodes/split_by_llm_categorize.test.ts +698 -0
- package/test/nodes/split_by_random.test.ts +177 -0
- package/test/nodes/wait_for_digits.test.ts +176 -0
- package/test/nodes/wait_for_response.test.ts +206 -0
- package/test/temba-add-input-labels.test.ts +87 -0
- package/test/temba-checkbox.test.ts +26 -0
- package/test/temba-field-renderer.test.ts +482 -0
- package/test/temba-integration-markdown.test.ts +2 -4
- package/test/temba-markdown.test.ts +1 -1
- package/test/temba-node-editor.test.ts +496 -0
- package/test/temba-select.test.ts +6 -6
- package/test/temba-slider.test.ts +0 -1
- package/test/temba-webchat.test.ts +1 -1
- package/test-assets/contacts/history.json +7 -20
- package/test-assets/select/llms.json +18 -0
- package/web-dev-mock.mjs +96 -6
- package/web-dev-server.config.mjs +29 -7
- package/out-tsc/src/form/FormElement.js +0 -67
- package/out-tsc/src/form/FormElement.js.map +0 -1
- package/out-tsc/src/form/FormField.js.map +0 -1
- package/out-tsc/test/temba-formfield.test.js +0 -94
- package/out-tsc/test/temba-formfield.test.js.map +0 -1
- package/src/form/FormElement.ts +0 -69
- package/test/temba-flow-editor.test.ts.backup +0 -563
- package/test/temba-formfield.test.ts +0 -121
- package/test/temba-utils-index.test.ts.backup +0 -1737
package/web-dev-mock.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Client as MinioClient } from 'minio';
|
|
2
2
|
import busboy from 'busboy';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
import fs from 'fs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Generates FlowInfo dynamically from a FlowDefinition
|
|
@@ -271,10 +272,11 @@ function extractDependenciesFromAction(action, dependencyMap, resultMap, nodeUui
|
|
|
271
272
|
}
|
|
272
273
|
} else {
|
|
273
274
|
// Create new result
|
|
275
|
+
const categories = action.category ? [action.category] : ['All Responses'];
|
|
274
276
|
resultMap.set(action.name, {
|
|
275
|
-
key: action.name.toLowerCase(),
|
|
277
|
+
key: action.name.toLowerCase().replace(/[^a-z0-9_]/g, '_'),
|
|
276
278
|
name: action.name,
|
|
277
|
-
categories:
|
|
279
|
+
categories: categories,
|
|
278
280
|
node_uuids: [nodeUuid]
|
|
279
281
|
});
|
|
280
282
|
}
|
|
@@ -288,14 +290,20 @@ function extractDependenciesFromRouter(router, dependencyMap, resultMap, nodeUui
|
|
|
288
290
|
if (router.result_name && router.categories) {
|
|
289
291
|
const existingResult = resultMap.get(router.result_name);
|
|
290
292
|
if (existingResult) {
|
|
291
|
-
// Add this node to existing result
|
|
292
|
-
existingResult.node_uuids.
|
|
293
|
+
// Add this node to existing result if not already present
|
|
294
|
+
if (!existingResult.node_uuids.includes(nodeUuid)) {
|
|
295
|
+
existingResult.node_uuids.push(nodeUuid);
|
|
296
|
+
}
|
|
293
297
|
} else {
|
|
294
298
|
// Create new result
|
|
299
|
+
const categories = router.categories.length > 0
|
|
300
|
+
? router.categories.map((cat) => cat.name)
|
|
301
|
+
: ['All Responses'];
|
|
302
|
+
|
|
295
303
|
const result = {
|
|
296
|
-
key: router.result_name,
|
|
304
|
+
key: router.result_name.toLowerCase().replace(/[^a-z0-9_]/g, '_'),
|
|
297
305
|
name: router.result_name,
|
|
298
|
-
categories:
|
|
306
|
+
categories: categories,
|
|
299
307
|
node_uuids: [nodeUuid]
|
|
300
308
|
};
|
|
301
309
|
resultMap.set(router.result_name, result);
|
|
@@ -431,3 +439,85 @@ export function handleMinioUpload(context) {
|
|
|
431
439
|
}
|
|
432
440
|
});
|
|
433
441
|
}
|
|
442
|
+
|
|
443
|
+
// Handle label creation for the labels API
|
|
444
|
+
export function handleLabelCreation(context) {
|
|
445
|
+
return new Promise((resolve) => {
|
|
446
|
+
let body = '';
|
|
447
|
+
|
|
448
|
+
context.req.on('data', chunk => {
|
|
449
|
+
body += chunk.toString();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
context.req.on('end', () => {
|
|
453
|
+
try {
|
|
454
|
+
const requestData = JSON.parse(body);
|
|
455
|
+
const labelName = requestData.name || '';
|
|
456
|
+
|
|
457
|
+
if (!labelName.trim()) {
|
|
458
|
+
context.status = 400;
|
|
459
|
+
context.body = JSON.stringify({ error: 'Label name is required' });
|
|
460
|
+
resolve();
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Read existing labels file
|
|
465
|
+
const labelsPath = './static/api/labels.json';
|
|
466
|
+
let labelsData;
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
labelsData = JSON.parse(fs.readFileSync(labelsPath, 'utf-8'));
|
|
470
|
+
} catch (error) {
|
|
471
|
+
// If file doesn't exist, create basic structure
|
|
472
|
+
labelsData = {
|
|
473
|
+
next: null,
|
|
474
|
+
previous: null,
|
|
475
|
+
results: []
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check if label already exists
|
|
480
|
+
const existingLabel = labelsData.results.find(
|
|
481
|
+
label => label.name.toLowerCase() === labelName.trim().toLowerCase()
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
if (existingLabel) {
|
|
485
|
+
// Return existing label
|
|
486
|
+
context.contentType = 'application/json';
|
|
487
|
+
context.body = JSON.stringify(existingLabel);
|
|
488
|
+
resolve();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Create new label with UUID
|
|
493
|
+
const newLabel = {
|
|
494
|
+
uuid: uuidv4(),
|
|
495
|
+
name: labelName.trim(),
|
|
496
|
+
count: 0
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// Add to labels data
|
|
500
|
+
labelsData.results.push(newLabel);
|
|
501
|
+
|
|
502
|
+
// Write back to file
|
|
503
|
+
fs.writeFileSync(labelsPath, JSON.stringify(labelsData, null, 2));
|
|
504
|
+
|
|
505
|
+
// Return the new label
|
|
506
|
+
context.contentType = 'application/json';
|
|
507
|
+
context.body = JSON.stringify(newLabel);
|
|
508
|
+
|
|
509
|
+
console.log('📝 Label created:', newLabel);
|
|
510
|
+
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error('Label creation error:', error);
|
|
513
|
+
context.status = 500;
|
|
514
|
+
context.body = JSON.stringify({
|
|
515
|
+
error: 'Label creation failed',
|
|
516
|
+
details: error.message
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
resolve();
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
}
|
|
@@ -4,7 +4,7 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
|
|
6
6
|
// Import the shared flow info generator and Minio functionality
|
|
7
|
-
import { generateFlowInfo, handleMinioUpload } from './web-dev-mock.mjs';
|
|
7
|
+
import { generateFlowInfo, handleMinioUpload, handleLabelCreation } from './web-dev-mock.mjs';
|
|
8
8
|
|
|
9
9
|
const replacePlugin = fromRollup(replace);
|
|
10
10
|
|
|
@@ -32,6 +32,7 @@ export default {
|
|
|
32
32
|
// Map API endpoints to static files
|
|
33
33
|
const apiMappings = {
|
|
34
34
|
'/api/v2/groups.json': './static/api/groups.json',
|
|
35
|
+
'/api/v2/labels.json': './static/api/labels.json',
|
|
35
36
|
'/api/v2/fields.json': './static/api/fields.json',
|
|
36
37
|
'/api/v2/globals.json': './static/api/globals.json',
|
|
37
38
|
'/api/v2/completion.json': './static/mr/docs/en-us/editor.json',
|
|
@@ -61,6 +62,11 @@ export default {
|
|
|
61
62
|
if (context.request.method === 'POST' && context.path === '/api/v2/media.json') {
|
|
62
63
|
return handleMinioUpload(context);
|
|
63
64
|
}
|
|
65
|
+
|
|
66
|
+
// Handle label creation
|
|
67
|
+
if (context.request.method === 'POST' && context.path === '/api/v2/labels.json') {
|
|
68
|
+
return handleLabelCreation(context);
|
|
69
|
+
}
|
|
64
70
|
}
|
|
65
71
|
},
|
|
66
72
|
{
|
|
@@ -106,23 +112,39 @@ export default {
|
|
|
106
112
|
// Generate metadata similar to production
|
|
107
113
|
const metadata = generateFlowMetadata(flowDefinition);
|
|
108
114
|
|
|
109
|
-
|
|
115
|
+
const response = {
|
|
110
116
|
status: 'success',
|
|
111
117
|
saved_on: new Date().toISOString(),
|
|
112
118
|
revision: {
|
|
113
119
|
id: Math.floor(Math.random() * 1000) + 1,
|
|
114
120
|
user: {
|
|
115
|
-
email: '
|
|
116
|
-
name: '
|
|
121
|
+
email: 'admin1@textit.com',
|
|
122
|
+
name: 'Adam McAdmin'
|
|
117
123
|
},
|
|
118
124
|
created_on: new Date().toISOString(),
|
|
119
125
|
version: flowDefinition.spec_version || '14.3.0',
|
|
120
|
-
revision: flowDefinition.revision || 1
|
|
126
|
+
revision: (flowDefinition.revision || 0) + 1
|
|
127
|
+
},
|
|
128
|
+
info: {
|
|
129
|
+
counts: metadata.counts,
|
|
130
|
+
dependencies: metadata.dependencies,
|
|
131
|
+
locals: metadata.locals,
|
|
132
|
+
results: metadata.results,
|
|
133
|
+
parent_refs: [],
|
|
134
|
+
issues: []
|
|
121
135
|
},
|
|
122
|
-
info: metadata,
|
|
123
136
|
issues: [],
|
|
124
|
-
metadata:
|
|
137
|
+
metadata: {
|
|
138
|
+
counts: metadata.counts,
|
|
139
|
+
dependencies: metadata.dependencies,
|
|
140
|
+
locals: metadata.locals,
|
|
141
|
+
results: metadata.results,
|
|
142
|
+
parent_refs: [],
|
|
143
|
+
issues: []
|
|
144
|
+
}
|
|
125
145
|
};
|
|
146
|
+
|
|
147
|
+
context.body = JSON.stringify(response);
|
|
126
148
|
context.status = 200;
|
|
127
149
|
} else {
|
|
128
150
|
console.log(`No body received for flow ${uuid}.`);
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { __decorate } from "tslib";
|
|
2
|
-
import { RapidElement } from '../RapidElement';
|
|
3
|
-
import { property } from 'lit/decorators.js';
|
|
4
|
-
/**
|
|
5
|
-
* FormElement is a component that appends a hidden input (outside of
|
|
6
|
-
* its own shadow) with its value to be included in forms.
|
|
7
|
-
*/
|
|
8
|
-
export class FormElement extends RapidElement {
|
|
9
|
-
constructor() {
|
|
10
|
-
super();
|
|
11
|
-
this.name = '';
|
|
12
|
-
this.value = null;
|
|
13
|
-
this.inputRoot = this;
|
|
14
|
-
this.disabled = false;
|
|
15
|
-
this.internals = this.attachInternals();
|
|
16
|
-
}
|
|
17
|
-
updated(changedProperties) {
|
|
18
|
-
super.updated(changedProperties);
|
|
19
|
-
if (changedProperties.has('value')) {
|
|
20
|
-
this.internals.setFormValue(this.value);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
get form() {
|
|
24
|
-
return this.internals.form;
|
|
25
|
-
}
|
|
26
|
-
setValue(value) {
|
|
27
|
-
this.value = this.serializeValue(value);
|
|
28
|
-
}
|
|
29
|
-
getDeserializedValue() {
|
|
30
|
-
return JSON.parse(this.value);
|
|
31
|
-
}
|
|
32
|
-
serializeValue(value) {
|
|
33
|
-
return JSON.stringify(value);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
FormElement.formAssociated = true;
|
|
37
|
-
__decorate([
|
|
38
|
-
property({ type: String })
|
|
39
|
-
], FormElement.prototype, "name", void 0);
|
|
40
|
-
__decorate([
|
|
41
|
-
property({ type: String, attribute: 'help_text' })
|
|
42
|
-
], FormElement.prototype, "helpText", void 0);
|
|
43
|
-
__decorate([
|
|
44
|
-
property({ type: Boolean, attribute: 'help_always' })
|
|
45
|
-
], FormElement.prototype, "helpAlways", void 0);
|
|
46
|
-
__decorate([
|
|
47
|
-
property({ type: Boolean, attribute: 'widget_only' })
|
|
48
|
-
], FormElement.prototype, "widgetOnly", void 0);
|
|
49
|
-
__decorate([
|
|
50
|
-
property({ type: Boolean, attribute: 'hide_label' })
|
|
51
|
-
], FormElement.prototype, "hideLabel", void 0);
|
|
52
|
-
__decorate([
|
|
53
|
-
property({ type: String })
|
|
54
|
-
], FormElement.prototype, "label", void 0);
|
|
55
|
-
__decorate([
|
|
56
|
-
property({ type: Array })
|
|
57
|
-
], FormElement.prototype, "errors", void 0);
|
|
58
|
-
__decorate([
|
|
59
|
-
property({ type: String })
|
|
60
|
-
], FormElement.prototype, "value", void 0);
|
|
61
|
-
__decorate([
|
|
62
|
-
property({ attribute: false })
|
|
63
|
-
], FormElement.prototype, "inputRoot", void 0);
|
|
64
|
-
__decorate([
|
|
65
|
-
property({ type: Boolean })
|
|
66
|
-
], FormElement.prototype, "disabled", void 0);
|
|
67
|
-
//# sourceMappingURL=FormElement.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormElement.js","sourceRoot":"","sources":["../../../src/form/FormElement.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,YAAY;IAkC3C;QACE,KAAK,EAAE,CAAC;QAjCV,SAAI,GAAG,EAAE,CAAC;QAqBV,UAAK,GAAG,IAAI,CAAC;QAGb,cAAS,GAAgB,IAAI,CAAC;QAG9B,aAAQ,GAAG,KAAK,CAAC;QAOf,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;IAC1C,CAAC;IAEM,OAAO,CAAC,iBAAmC;QAChD,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACjC,IAAI,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC7B,CAAC;IAEM,QAAQ,CAAC,KAAU;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAEM,oBAAoB;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAEM,cAAc,CAAC,KAAU;QAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;;AA9BM,0BAAc,GAAG,IAAI,AAAP,CAAQ;AA5B7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCACjB;AAGV;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;6CAClC;AAGjB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;+CAClC;AAGpB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;+CAClC;AAGpB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;8CAClC;AAGnB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CACb;AAGd;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;2CACT;AAGjB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CACd;AAGb;IADC,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;8CACD;AAG9B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;6CACX","sourcesContent":["import { RapidElement } from '../RapidElement';\nimport { property } from 'lit/decorators.js';\n\n/**\n * FormElement is a component that appends a hidden input (outside of\n * its own shadow) with its value to be included in forms.\n */\nexport class FormElement extends RapidElement {\n @property({ type: String })\n name = '';\n\n @property({ type: String, attribute: 'help_text' })\n helpText: string;\n\n @property({ type: Boolean, attribute: 'help_always' })\n helpAlways: boolean;\n\n @property({ type: Boolean, attribute: 'widget_only' })\n widgetOnly: boolean;\n\n @property({ type: Boolean, attribute: 'hide_label' })\n hideLabel: boolean;\n\n @property({ type: String })\n label: string;\n\n @property({ type: Array })\n errors: string[];\n\n @property({ type: String })\n value = null;\n\n @property({ attribute: false })\n inputRoot: HTMLElement = this;\n\n @property({ type: Boolean })\n disabled = false;\n static formAssociated = true;\n\n protected internals: ElementInternals;\n\n constructor() {\n super();\n this.internals = this.attachInternals();\n }\n\n public updated(changedProperties: Map<string, any>) {\n super.updated(changedProperties);\n if (changedProperties.has('value')) {\n this.internals.setFormValue(this.value);\n }\n }\n\n get form() {\n return this.internals.form;\n }\n\n public setValue(value: any) {\n this.value = this.serializeValue(value);\n }\n\n public getDeserializedValue(): any {\n return JSON.parse(this.value);\n }\n\n public serializeValue(value: any): string {\n return JSON.stringify(value);\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormField.js","sourceRoot":"","sources":["../../../src/form/FormField.ts"],"names":[],"mappings":"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","sourcesContent":["import { TemplateResult, html, css, LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { renderMarkdown } from '../markdown';\n\n/**\n * A small wrapper to display labels and help text in a smartmin style.\n * This exists so we can display things consistently before restyling.\n */\nexport class FormField extends LitElement {\n static get styles() {\n return css`\n :host {\n font-family: var(--font-family);\n }\n\n label {\n margin-bottom: 5px;\n margin-left: 4px;\n display: block;\n font-weight: 400;\n font-size: var(--label-size);\n letter-spacing: 0.05em;\n line-height: normal;\n color: var(--color-label, #777);\n }\n\n .help-text {\n font-size: var(--help-text-size);\n line-height: normal;\n color: var(--color-text-help);\n margin-left: var(--help-text-margin-left);\n margin-top: -16px;\n opacity: 0;\n transition: opacity ease-in-out 100ms, margin-top ease-in-out 200ms;\n pointer-events: none;\n }\n\n .help-text.help-always {\n opacity: 1;\n margin-top: 6px;\n margin-left: var(--help-text-margin-left);\n }\n\n .field:focus-within .help-text {\n margin-top: 6px;\n opacity: 1;\n }\n\n .alert-error {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 1000;\n background: white;\n border: 1px solid var(--color-error);\n color: var(--color-error);\n padding: 8px 12px;\n margin: 2px 0 0 0;\n border-radius: var(--curvature);\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n font-size: 0.85em;\n line-height: 1.2;\n opacity: 0;\n visibility: hidden;\n transform: translateY(-12px);\n transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out,\n transform 0.2s ease-in-out;\n }\n\n .field:hover .alert-error {\n opacity: 1;\n visibility: visible;\n transform: translateY(2px);\n }\n\n /* Hide error popup when widget is focused */\n .field:focus-within .alert-error {\n opacity: 0;\n visibility: hidden;\n transform: translateY(-4px);\n }\n\n .field.has-error {\n position: relative;\n /* Set CSS custom properties that form components can use */\n --color-widget-border: var(--color-error);\n --widget-box-shadow-focused: var(\n --widget-box-shadow-focused-error,\n 0 0 0 3px rgba(255, 99, 71, 0.3)\n );\n --color-focus: var(--color-error);\n }\n\n .field.has-error .widget {\n border-radius: var(--curvature-widget);\n position: relative;\n }\n\n /* Force error styling with higher specificity */\n :host(.has-error) .field.has-error .widget .input-container,\n :host(.has-error) .field.has-error .widget .select-container,\n :host(.has-error) .field.has-error .widget .comp-container,\n :host(.has-error) .field.has-error .widget .checkbox-container,\n :host(.has-error) .field.has-error .widget .container,\n :host(.has-error) .field.has-error .widget .range-container,\n .field.has-error .widget .input-container,\n .field.has-error .widget .select-container,\n .field.has-error .widget .comp-container,\n .field.has-error .widget .checkbox-container,\n .field.has-error .widget .container,\n .field.has-error .widget .range-container {\n border-color: var(--color-error) !important;\n }\n\n /* When error field is focused, use error-colored focus ring */\n :host(.has-error) .field.has-error .widget .input-container:focus-within,\n :host(.has-error) .field.has-error .widget .select-container:focus-within,\n :host(.has-error) .field.has-error .widget .select-container.focused,\n :host(.has-error) .field.has-error .widget .comp-container:focus-within,\n :host(.has-error)\n .field.has-error\n .widget\n .checkbox-container:focus-within,\n :host(.has-error) .field.has-error .widget .container:focus-within,\n :host(.has-error) .field.has-error .widget .range-container:focus-within,\n .field.has-error .widget .input-container:focus-within,\n .field.has-error .widget .select-container:focus-within,\n .field.has-error .widget .select-container.focused,\n .field.has-error .widget .comp-container:focus-within,\n .field.has-error .widget .checkbox-container:focus-within,\n .field.has-error .widget .container:focus-within,\n .field.has-error .widget .range-container:focus-within {\n border-color: var(--color-error) !important;\n box-shadow: var(\n --widget-box-shadow-focused-error,\n 0 0 0 3px rgba(255, 99, 71, 0.3)\n ) !important;\n }\n\n .alert-error p {\n margin: 0;\n padding: 0;\n }\n\n .disabled {\n opacity: var(--disabled-opacity) !important;\n pointer-events: none !important;\n }\n `;\n }\n\n @property({ type: Boolean, attribute: 'hide_label' })\n hideLabel = false;\n\n @property({ type: Boolean, attribute: 'widget_only' })\n widgetOnly = false;\n\n @property({ type: Array, attribute: false })\n errors: string[] = [];\n\n @property({ type: Boolean })\n hideErrors = false;\n\n @property({ type: String, attribute: 'help_text' })\n helpText = '';\n\n @property({ type: Boolean, attribute: 'help_always' })\n helpAlways = true;\n\n @property({ type: String })\n label = '';\n\n @property({ type: String })\n name = '';\n\n @property({ type: Boolean })\n disabled = false;\n\n updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated(changedProperties);\n\n if (\n changedProperties.has('errors') ||\n changedProperties.has('hideErrors')\n ) {\n const hasErrors =\n !this.hideErrors && this.errors && this.errors.length > 0;\n this.classList.toggle('has-error', hasErrors);\n }\n }\n\n public render(): TemplateResult {\n const hasErrors = !this.hideErrors && this.errors && this.errors.length > 0;\n const errors = hasErrors\n ? this.errors.map((error: string) => {\n return html`\n <div class=\"alert-error\">${renderMarkdown(error)}</div>\n `;\n })\n : [];\n\n if (this.widgetOnly) {\n return html`\n <div class=\"${this.disabled ? 'disabled' : ''}\"><slot></slot></div>\n ${errors}\n `;\n }\n\n return html`\n <div\n class=\"field ${this.disabled ? 'disabled' : ''} ${hasErrors\n ? 'has-error'\n : ''}\"\n >\n ${!!this.name && !this.hideLabel && !!this.label\n ? html`\n <label class=\"control-label\" for=\"${this.name}\"\n >${this.label}</label\n >\n `\n : null}\n <div class=\"widget\">\n <slot></slot>\n ${errors}\n </div>\n ${this.helpText && this.helpText !== 'None'\n ? html`\n <div class=\"help-text ${this.helpAlways ? 'help-always' : null}\">\n ${this.helpText}\n </div>\n `\n : null}\n </div>\n `;\n }\n}\n"]}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { html, fixture, expect } from '@open-wc/testing';
|
|
2
|
-
import { assertScreenshot, getClip } from './utils.test';
|
|
3
|
-
describe('temba-field', () => {
|
|
4
|
-
it('renders field with plain text errors', async () => {
|
|
5
|
-
const formField = await fixture(html `
|
|
6
|
-
<temba-field
|
|
7
|
-
label="Test Field"
|
|
8
|
-
name="test"
|
|
9
|
-
.errors=${['This is a plain text error', 'Another error message']}
|
|
10
|
-
>
|
|
11
|
-
<input type="text" />
|
|
12
|
-
</temba-field>
|
|
13
|
-
`);
|
|
14
|
-
await formField.updateComplete;
|
|
15
|
-
// Check that errors are rendered
|
|
16
|
-
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
|
|
17
|
-
expect(errorElements.length).to.equal(2);
|
|
18
|
-
expect(errorElements[0].textContent.trim()).to.equal('This is a plain text error');
|
|
19
|
-
expect(errorElements[1].textContent.trim()).to.equal('Another error message');
|
|
20
|
-
await assertScreenshot('formfield/plain-text-errors', getClip(formField));
|
|
21
|
-
});
|
|
22
|
-
it('renders field with markdown errors', async () => {
|
|
23
|
-
const formField = await fixture(html `
|
|
24
|
-
<temba-field
|
|
25
|
-
label="Test Field"
|
|
26
|
-
name="test"
|
|
27
|
-
.errors=${[
|
|
28
|
-
'This is **bold** text',
|
|
29
|
-
'This has a [link](https://example.com)',
|
|
30
|
-
'This is *italic* and **bold** with a [link](https://example.com)'
|
|
31
|
-
]}
|
|
32
|
-
>
|
|
33
|
-
<input type="text" />
|
|
34
|
-
</temba-field>
|
|
35
|
-
`);
|
|
36
|
-
await formField.updateComplete;
|
|
37
|
-
// Check that errors are rendered
|
|
38
|
-
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
|
|
39
|
-
expect(errorElements.length).to.equal(3);
|
|
40
|
-
// First error should have bold text
|
|
41
|
-
const firstError = errorElements[0];
|
|
42
|
-
const boldElement = firstError.querySelector('strong');
|
|
43
|
-
expect(boldElement).to.not.be.null;
|
|
44
|
-
expect(boldElement.textContent).to.equal('bold');
|
|
45
|
-
// Second error should have a link
|
|
46
|
-
const secondError = errorElements[1];
|
|
47
|
-
const linkElement = secondError.querySelector('a');
|
|
48
|
-
expect(linkElement).to.not.be.null;
|
|
49
|
-
expect(linkElement.getAttribute('href')).to.equal('https://example.com');
|
|
50
|
-
expect(linkElement.textContent).to.equal('link');
|
|
51
|
-
// Third error should have both bold, italic, and link
|
|
52
|
-
const thirdError = errorElements[2];
|
|
53
|
-
const thirdBoldElement = thirdError.querySelector('strong');
|
|
54
|
-
const thirdItalicElement = thirdError.querySelector('em');
|
|
55
|
-
const thirdLinkElement = thirdError.querySelector('a');
|
|
56
|
-
expect(thirdBoldElement).to.not.be.null;
|
|
57
|
-
expect(thirdItalicElement).to.not.be.null;
|
|
58
|
-
expect(thirdLinkElement).to.not.be.null;
|
|
59
|
-
await assertScreenshot('formfield/markdown-errors', getClip(formField));
|
|
60
|
-
});
|
|
61
|
-
it('renders field without errors', async () => {
|
|
62
|
-
const formField = await fixture(html `
|
|
63
|
-
<temba-field label="Test Field" name="test">
|
|
64
|
-
<input type="text" />
|
|
65
|
-
</temba-field>
|
|
66
|
-
`);
|
|
67
|
-
await formField.updateComplete;
|
|
68
|
-
// Check that no errors are rendered
|
|
69
|
-
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
|
|
70
|
-
expect(errorElements.length).to.equal(0);
|
|
71
|
-
await assertScreenshot('formfield/no-errors', getClip(formField));
|
|
72
|
-
});
|
|
73
|
-
it('renders in widget-only mode with errors', async () => {
|
|
74
|
-
const formField = await fixture(html `
|
|
75
|
-
<temba-field
|
|
76
|
-
widget_only
|
|
77
|
-
.errors=${['Widget only **error** with [link](https://example.com)']}
|
|
78
|
-
>
|
|
79
|
-
<input type="text" />
|
|
80
|
-
</temba-field>
|
|
81
|
-
`);
|
|
82
|
-
await formField.updateComplete;
|
|
83
|
-
// Check that error is rendered in widget-only mode
|
|
84
|
-
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
|
|
85
|
-
expect(errorElements.length).to.equal(1);
|
|
86
|
-
const errorElement = errorElements[0];
|
|
87
|
-
const boldElement = errorElement.querySelector('strong');
|
|
88
|
-
const linkElement = errorElement.querySelector('a');
|
|
89
|
-
expect(boldElement).to.not.be.null;
|
|
90
|
-
expect(linkElement).to.not.be.null;
|
|
91
|
-
await assertScreenshot('formfield/widget-only-markdown-errors', getClip(formField));
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
//# sourceMappingURL=temba-formfield.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"temba-formfield.test.js","sourceRoot":"","sources":["../../test/temba-formfield.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, fixture, expect } from '@open-wc/testing';\nimport { FormField } from '../src/form/FormField';\nimport { assertScreenshot, getClip } from './utils.test';\n\ndescribe('temba-field', () => {\n it('renders field with plain text errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field\n label=\"Test Field\"\n name=\"test\"\n .errors=${['This is a plain text error', 'Another error message']}\n >\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that errors are rendered\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(2);\n expect(errorElements[0].textContent.trim()).to.equal(\n 'This is a plain text error'\n );\n expect(errorElements[1].textContent.trim()).to.equal(\n 'Another error message'\n );\n\n await assertScreenshot('formfield/plain-text-errors', getClip(formField));\n });\n\n it('renders field with markdown errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field\n label=\"Test Field\"\n name=\"test\"\n .errors=${[\n 'This is **bold** text',\n 'This has a [link](https://example.com)',\n 'This is *italic* and **bold** with a [link](https://example.com)'\n ]}\n >\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that errors are rendered\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(3);\n\n // First error should have bold text\n const firstError = errorElements[0];\n const boldElement = firstError.querySelector('strong');\n expect(boldElement).to.not.be.null;\n expect(boldElement.textContent).to.equal('bold');\n\n // Second error should have a link\n const secondError = errorElements[1];\n const linkElement = secondError.querySelector('a');\n expect(linkElement).to.not.be.null;\n expect(linkElement.getAttribute('href')).to.equal('https://example.com');\n expect(linkElement.textContent).to.equal('link');\n\n // Third error should have both bold, italic, and link\n const thirdError = errorElements[2];\n const thirdBoldElement = thirdError.querySelector('strong');\n const thirdItalicElement = thirdError.querySelector('em');\n const thirdLinkElement = thirdError.querySelector('a');\n expect(thirdBoldElement).to.not.be.null;\n expect(thirdItalicElement).to.not.be.null;\n expect(thirdLinkElement).to.not.be.null;\n\n await assertScreenshot('formfield/markdown-errors', getClip(formField));\n });\n\n it('renders field without errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field label=\"Test Field\" name=\"test\">\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that no errors are rendered\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(0);\n\n await assertScreenshot('formfield/no-errors', getClip(formField));\n });\n\n it('renders in widget-only mode with errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field\n widget_only\n .errors=${['Widget only **error** with [link](https://example.com)']}\n >\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that error is rendered in widget-only mode\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(1);\n\n const errorElement = errorElements[0];\n const boldElement = errorElement.querySelector('strong');\n const linkElement = errorElement.querySelector('a');\n expect(boldElement).to.not.be.null;\n expect(linkElement).to.not.be.null;\n\n await assertScreenshot(\n 'formfield/widget-only-markdown-errors',\n getClip(formField)\n );\n });\n});\n"]}
|
package/src/form/FormElement.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { RapidElement } from '../RapidElement';
|
|
2
|
-
import { property } from 'lit/decorators.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* FormElement is a component that appends a hidden input (outside of
|
|
6
|
-
* its own shadow) with its value to be included in forms.
|
|
7
|
-
*/
|
|
8
|
-
export class FormElement extends RapidElement {
|
|
9
|
-
@property({ type: String })
|
|
10
|
-
name = '';
|
|
11
|
-
|
|
12
|
-
@property({ type: String, attribute: 'help_text' })
|
|
13
|
-
helpText: string;
|
|
14
|
-
|
|
15
|
-
@property({ type: Boolean, attribute: 'help_always' })
|
|
16
|
-
helpAlways: boolean;
|
|
17
|
-
|
|
18
|
-
@property({ type: Boolean, attribute: 'widget_only' })
|
|
19
|
-
widgetOnly: boolean;
|
|
20
|
-
|
|
21
|
-
@property({ type: Boolean, attribute: 'hide_label' })
|
|
22
|
-
hideLabel: boolean;
|
|
23
|
-
|
|
24
|
-
@property({ type: String })
|
|
25
|
-
label: string;
|
|
26
|
-
|
|
27
|
-
@property({ type: Array })
|
|
28
|
-
errors: string[];
|
|
29
|
-
|
|
30
|
-
@property({ type: String })
|
|
31
|
-
value = null;
|
|
32
|
-
|
|
33
|
-
@property({ attribute: false })
|
|
34
|
-
inputRoot: HTMLElement = this;
|
|
35
|
-
|
|
36
|
-
@property({ type: Boolean })
|
|
37
|
-
disabled = false;
|
|
38
|
-
static formAssociated = true;
|
|
39
|
-
|
|
40
|
-
protected internals: ElementInternals;
|
|
41
|
-
|
|
42
|
-
constructor() {
|
|
43
|
-
super();
|
|
44
|
-
this.internals = this.attachInternals();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
public updated(changedProperties: Map<string, any>) {
|
|
48
|
-
super.updated(changedProperties);
|
|
49
|
-
if (changedProperties.has('value')) {
|
|
50
|
-
this.internals.setFormValue(this.value);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get form() {
|
|
55
|
-
return this.internals.form;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public setValue(value: any) {
|
|
59
|
-
this.value = this.serializeValue(value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public getDeserializedValue(): any {
|
|
63
|
-
return JSON.parse(this.value);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public serializeValue(value: any): string {
|
|
67
|
-
return JSON.stringify(value);
|
|
68
|
-
}
|
|
69
|
-
}
|