@flowfuse/node-red-dashboard 1.16.0 → 1.16.1-312bbd4-202409201219.0

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.
Files changed (79) hide show
  1. package/nodes/config/ui_base.html +21 -1
  2. package/nodes/config/ui_base.js +39 -31
  3. package/nodes/utils/index.js +46 -1
  4. package/nodes/widgets/locales/en-US/ui_gauge.html +47 -1
  5. package/nodes/widgets/locales/en-US/ui_gauge.json +2 -1
  6. package/nodes/widgets/locales/en-US/ui_number_input.html +21 -10
  7. package/nodes/widgets/locales/en-US/ui_switch.html +13 -0
  8. package/nodes/widgets/locales/en-US/ui_table.html +8 -3
  9. package/nodes/widgets/locales/en-US/ui_table.json +4 -1
  10. package/nodes/widgets/ui_gauge.html +251 -232
  11. package/nodes/widgets/ui_gauge.js +57 -1
  12. package/nodes/widgets/ui_number_input.html +21 -2
  13. package/nodes/widgets/ui_number_input.js +33 -0
  14. package/nodes/widgets/ui_switch.html +16 -2
  15. package/nodes/widgets/ui_switch.js +4 -0
  16. package/nodes/widgets/ui_table.html +13 -1
  17. package/nodes/widgets/ui_table.js +21 -3
  18. package/package.json +5 -4
  19. package/dist/apple-touch-icon.png +0 -0
  20. package/dist/assets/Tableau10-B-NsZVaP.js +0 -1
  21. package/dist/assets/array-BKyUJesY.js +0 -1
  22. package/dist/assets/blockDiagram-9f4a6865-UPqjSqDj.js +0 -118
  23. package/dist/assets/c4Diagram-ae766693-kv7lK6ga.js +0 -10
  24. package/dist/assets/channel-mXvjgtXb.js +0 -1
  25. package/dist/assets/classDiagram-fb54d2a0-RmrjaeHD.js +0 -2
  26. package/dist/assets/classDiagram-v2-a2b738ad-zuuu4ECc.js +0 -2
  27. package/dist/assets/clone-BqlsRKdV.js +0 -1
  28. package/dist/assets/createText-ca0c5216-BXZBL5BT.js +0 -7
  29. package/dist/assets/edges-066a5561-C1-KGOXF.js +0 -4
  30. package/dist/assets/erDiagram-09d1c15f-ClSg9c0s.js +0 -51
  31. package/dist/assets/flowDb-c1833063-Cxm3oDr8.js +0 -10
  32. package/dist/assets/flowDiagram-b222e15a-Dqzt33WO.js +0 -4
  33. package/dist/assets/flowDiagram-v2-13329dc7-C7Ci1xPs.js +0 -1
  34. package/dist/assets/flowchart-elk-definition-ae0efee6-BXKaZ8So.js +0 -139
  35. package/dist/assets/ganttDiagram-b62c793e-BIe_Z918.js +0 -257
  36. package/dist/assets/gitGraphDiagram-942e62fe-B075kJ0d.js +0 -70
  37. package/dist/assets/graph-Dz9-FpiS.js +0 -1
  38. package/dist/assets/index-01f381cb-CgXS4jQ7.js +0 -1
  39. package/dist/assets/index-ByRewN2V.css +0 -13
  40. package/dist/assets/index-D_uZCxvr.js +0 -245
  41. package/dist/assets/infoDiagram-94cd232f-BGm0VWz1.js +0 -7
  42. package/dist/assets/init-Gi6I4Gst.js +0 -1
  43. package/dist/assets/journeyDiagram-6625b456-DsIUrj6-.js +0 -139
  44. package/dist/assets/katex-CvgdMzdh.js +0 -261
  45. package/dist/assets/layout-DcQzOJ4r.js +0 -1
  46. package/dist/assets/line-C728eder.js +0 -1
  47. package/dist/assets/linear-BlKmEocs.js +0 -1
  48. package/dist/assets/logo-DIAzbBuw.png +0 -0
  49. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  50. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  51. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  52. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  53. package/dist/assets/mindmap-definition-307c710a-DUg6o2Zr.js +0 -110
  54. package/dist/assets/ordinal-Cboi1Yqb.js +0 -1
  55. package/dist/assets/pieDiagram-bb1d19e5-B3Hd_cIK.js +0 -35
  56. package/dist/assets/quadrantDiagram-c759a472-C6yNrwDB.js +0 -7
  57. package/dist/assets/requirementDiagram-87253d64-BH-7FzoY.js +0 -52
  58. package/dist/assets/sankeyDiagram-707fac0f-BEB9auiO.js +0 -8
  59. package/dist/assets/sequenceDiagram-6894f283-CksgleqZ.js +0 -122
  60. package/dist/assets/stateDiagram-5dee940d-B6F6qEPH.js +0 -1
  61. package/dist/assets/stateDiagram-v2-1992cada-C_gj0AcC.js +0 -1
  62. package/dist/assets/styles-0784dbeb-CwYxdnVK.js +0 -207
  63. package/dist/assets/styles-483fbfea-Bp-0538M.js +0 -116
  64. package/dist/assets/styles-b83b31c9-C8CyZU2q.js +0 -160
  65. package/dist/assets/svgDrawCommon-5e1cfd1d-2p_DeREG.js +0 -1
  66. package/dist/assets/timeline-definition-bf702344-Ctui7jsb.js +0 -61
  67. package/dist/assets/workbox-window.prod.es5-D5gOYdM7.js +0 -2
  68. package/dist/assets/xychartDiagram-f11f50a6-Duus9YYx.js +0 -7
  69. package/dist/favicon.ico +0 -0
  70. package/dist/favicon.svg +0 -482
  71. package/dist/index.html +0 -22
  72. package/dist/logo-512x512.png +0 -0
  73. package/dist/logo.svg +0 -482
  74. package/dist/manifest.webmanifest +0 -1
  75. package/dist/maskable-icon-512x512.png +0 -0
  76. package/dist/pwa-192x192.png +0 -0
  77. package/dist/pwa-512x512.png +0 -0
  78. package/dist/pwa-64x64.png +0 -0
  79. package/dist/sw.js +0 -2
@@ -235,6 +235,26 @@
235
235
  display: flex;
236
236
  flex-direction: column;
237
237
  }
238
+ div.nrdb2-sb-widget-list-container ol.nrdb2-sb-widget-list.red-ui-editableList-list.ui-sortable {
239
+ min-height: 20px; /* IMPORTANT! So that user when last element is dragged out it doesnt collapse. */
240
+ }
241
+ div.nrdb2-sb-widget-list-container ol.nrdb2-sb-widget-list.red-ui-editableList-list.ui-sortable:empty {
242
+ border: 1px dashed #c4c4c4;
243
+ padding: 8px; /* should be 9px to match the other entries but border width is 2px */
244
+ height: 20px; /* To make it visible */
245
+ text-align: center;
246
+ border-left-width: 0;
247
+ border-right-width: 0;
248
+ background-color: #f9f9f9;
249
+ }
250
+ div.nrdb2-sb-widget-list-container ol.nrdb2-sb-widget-list.red-ui-editableList-list.ui-sortable:empty::before {
251
+ content: "empty";
252
+ color: #d2d2d2;
253
+ font-style: italic;
254
+ }
255
+ ol.nrdb2-sb-group-list li:last-child ol.nrdb2-sb-widget-list.red-ui-editableList-list.ui-sortable:empty {
256
+ border-bottom-width: 0px;
257
+ }
238
258
  </style>
239
259
 
240
260
 
@@ -1043,7 +1063,7 @@
1043
1063
  addItem: function (container, i, /** @type {DashboardItem} */ widget) {
1044
1064
  const titleRow = $('<div>', { class: 'nrdb2-sb-list-header nrdb2-sb-widgets-list-header' }).appendTo(container)
1045
1065
  $('<i class="nrdb2-sb-list-handle nrdb2-sb-widget-list-handle fa fa-bars"></i>').appendTo(titleRow)
1046
-
1066
+
1047
1067
  // Set the icon
1048
1068
  const ico = $('<i>', { class: 'nrdb2-sb-icon nrdb2-sb-widget-icon' }).appendTo(titleRow)
1049
1069
  let widgetIcon = RED.utils.getNodeIcon(widget.node?._def, widget.node) || 'fa-question'
@@ -1,10 +1,9 @@
1
- const fs = require('fs')
2
1
  const path = require('path')
3
2
 
4
3
  const v = require('../../package.json').version
5
4
  const datastore = require('../store/data.js')
6
5
  const statestore = require('../store/state.js')
7
- const { appendTopic, addConnectionCredentials } = require('../utils/index.js')
6
+ const { appendTopic, addConnectionCredentials, getThirdPartyWidgets } = require('../utils/index.js')
8
7
 
9
8
  // from: https://stackoverflow.com/a/28592528/3016654
10
9
  function join (...paths) {
@@ -90,36 +89,8 @@ module.exports = function (RED) {
90
89
  /**
91
90
  * Load in third party widgets
92
91
  */
93
- let packagePath, packageJson
94
- if (RED.settings?.userDir) {
95
- packagePath = path.join(RED.settings.userDir, 'package.json')
96
- packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
97
- } else {
98
- node.log('Cannot import third party widgets. No access to Node-RED package.json')
99
- }
100
92
 
101
- if (packageJson && packageJson.dependencies) {
102
- Object.entries(packageJson.dependencies)?.filter(([packageName, _packageVersion]) => {
103
- return packageName.includes('node-red-dashboard-2-')
104
- }).map(([packageName, _packageVersion]) => {
105
- const modulePath = path.join(RED.settings.userDir, 'node_modules', packageName)
106
- const packagePath = path.join(modulePath, 'package.json')
107
- // get third party package.json
108
- const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
109
- if (packageJson?.['node-red-dashboard-2']) {
110
- // loop over object of widgets
111
- Object.entries(packageJson['node-red-dashboard-2'].widgets).forEach(([widgetName, widgetConfig]) => {
112
- uiShared.contribs[widgetName] = {
113
- package: packageName,
114
- name: widgetName,
115
- src: widgetConfig.output,
116
- component: widgetConfig.component
117
- }
118
- })
119
- }
120
- return packageJson
121
- })
122
- }
93
+ uiShared.contribs = loadContribs(node)
123
94
 
124
95
  /**
125
96
  * Configure Web Server to handle UI traffic
@@ -217,6 +188,43 @@ module.exports = function (RED) {
217
188
  }
218
189
  }
219
190
 
191
+ function loadContribs (node) {
192
+ // from nodesDir
193
+ let contribs = { ...uiShared.contribs }
194
+ if (RED.settings?.nodesDir) {
195
+ const nodesDir = Array.isArray(RED.settings.nodesDir) ? RED.settings.nodesDir : [RED.settings.nodesDir]
196
+ for (const dir of nodesDir) {
197
+ try {
198
+ if (!dir || typeof dir !== 'string') { continue }
199
+ const _contribs = getThirdPartyWidgets(dir)
200
+ contribs = { ...contribs, ..._contribs }
201
+ } catch (error) {
202
+ node.log(`Cannot import third party widgets from nodes directory '${dir}}' package.json`)
203
+ }
204
+ }
205
+ }
206
+
207
+ // from user directory package.json
208
+ if (RED.settings?.userDir) {
209
+ try {
210
+ const _contribs = getThirdPartyWidgets(RED.settings.userDir)
211
+ contribs = { ...contribs, ..._contribs }
212
+ } catch (error) {
213
+ node.log('Cannot import third party widgets from user directory package.json')
214
+ }
215
+ }
216
+
217
+ // from main Node-RED package.json
218
+ try {
219
+ const appRoot = path.join(require.main.paths?.[0]?.split('node_modules')[0], '..')
220
+ const _contribs = getThirdPartyWidgets(appRoot)
221
+ contribs = { ...contribs, ..._contribs }
222
+ } catch (error) {
223
+ node.log('Cannot import third party widgets from main application root package.json')
224
+ }
225
+ return contribs
226
+ }
227
+
220
228
  /**
221
229
  * Close the SocketIO Server
222
230
  */
@@ -1,3 +1,6 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
1
4
  function asyncEvaluateNodeProperty (RED, value, type, node, msg) {
2
5
  return new Promise(function (resolve, reject) {
3
6
  RED.util.evaluateNodeProperty(value, type, node, msg, function (e, r) {
@@ -54,8 +57,50 @@ function addConnectionCredentials (RED, msg, conn, config) {
54
57
  return msg
55
58
  }
56
59
 
60
+ function getThirdPartyWidgets (directory) {
61
+ const contribs = {}
62
+ const packagePath = path.join(directory, 'package.json')
63
+ if (!fs.existsSync(packagePath)) {
64
+ return contribs
65
+ }
66
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
67
+ const getWidgets = (packageJson) => {
68
+ if (packageJson?.['node-red-dashboard-2']) {
69
+ // loop over object of widgets & add to contribs object
70
+ Object.entries(packageJson['node-red-dashboard-2'].widgets).forEach(([widgetName, widgetConfig]) => {
71
+ contribs[widgetName] = {
72
+ package: packageJson.name,
73
+ name: widgetName,
74
+ src: widgetConfig.output,
75
+ path: path.resolve(directory),
76
+ component: widgetConfig.component
77
+ }
78
+ })
79
+ }
80
+ }
81
+ if (packageJson?.['node-red-dashboard-2']) {
82
+ // this _is_ a dashboard node! get its widgets.
83
+ getWidgets(packageJson)
84
+ } else if (packageJson && packageJson.dependencies) {
85
+ // get widgets from dependencies of this package
86
+ Object.entries(packageJson.dependencies)?.filter(([packageName, _packageVersion]) => {
87
+ return packageName.includes('node-red-dashboard-2-')
88
+ }).forEach(([packageName, _packageVersion]) => {
89
+ const modulePath = path.join(directory, 'node_modules', packageName)
90
+ const packagePath = path.join(modulePath, 'package.json')
91
+ // get third party package.json
92
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
93
+ if (packageJson?.['node-red-dashboard-2']) {
94
+ getWidgets(packageJson)
95
+ }
96
+ })
97
+ }
98
+ return contribs
99
+ }
100
+
57
101
  module.exports = {
58
102
  asyncEvaluateNodeProperty,
59
103
  appendTopic,
60
- addConnectionCredentials
104
+ addConnectionCredentials,
105
+ getThirdPartyWidgets
61
106
  }
@@ -11,7 +11,9 @@
11
11
  <dt>Segments <span class="property-type">list</span></dt>
12
12
  <dd>Collection of objects that define the relevant color for a given value</dd>
13
13
  <dt>Label <span class="property-type">list</span></dt>
14
- <dd>The title text shown above the gauge</dd>
14
+ <dd>The label text shown above the gauge</dd>
15
+ <dt>Defaults <span class="property-type">list</span></dt>
16
+ <dd>Reset to default min/max and segments based on the selected gauge type</dd>
15
17
  </dl>
16
18
  <h3>Properties (Half &amp; 3/4 Gauges Only)</h3>
17
19
  <dl class="message-properties">
@@ -28,4 +30,48 @@
28
30
  <dt>Sizes (Segments) <span class="property-type">number</span></dt>
29
31
  <dd>A numerical value, in pixels, that defines the thickness of the segments rendered in the gauge.</dd>
30
32
  </dl>
33
+ <h3>Dynamic Properties (Inputs)</h3>
34
+ <p>Any of the following can be appended to a <code>msg.ui_update</code> in order to override or set properties on this node at runtime.</p>
35
+ <dl class="message-properties">
36
+ <dt class="optional">label <span class="property-type">string</span></dt>
37
+ <dd>Update the label rendered above the Gauge</dd>
38
+ <dt class="optional">segments <span class="property-type">array</span></dt>
39
+ <dd>
40
+ Change the options available in the dropdown at runtime
41
+ <ul>
42
+ <li><code>Array&lt;{color: String, from: Number}&gt;</code></li>
43
+ </ul>
44
+ </dd>
45
+ <dt class="optional">gtype <span class="property-type">see detail</span></dt>
46
+ <dd>Modify the type of Gauge rendered, with the following options:
47
+ <ul>
48
+ <li><code>gauge-battery</code></li>
49
+ <li><code>gauge-34</code></li>
50
+ <li><code>gauge-half</code></li>
51
+ <li><code>gauge-tile</code></li>
52
+ <li><code>gauge-tank</code></li>
53
+ </ul>
54
+ </dd>
55
+ <dt class="optional">gstyle <span class="property-type">see detail</span></dt>
56
+ <dd>Modify the style of 3/4 and half Gauge rendered, with the following options: (only applicable to for 3/4 and Half gauges)
57
+ <ul>
58
+ <li><code>neelde</code></li>
59
+ <li><code>rounded</code></li>
60
+ </ul>
61
+ </dd>
62
+ <dt class="optional">min <span class="property-type">number</span></dt>
63
+ <dd>Change the minimum value the gauge supports</dd>
64
+ <dt class="optional">max <span class="property-type">number</span></dt>
65
+ <dd>Change the maximum value the gauge supports</dd>
66
+ <dt class="optional">prefix <span class="property-type">string</span></dt>
67
+ <dd>Change the text rendered before the Gauge's value (only applicable to for 3/4 and Half gauges)</dd>
68
+ <dt class="optional">suffix <span class="property-type">string</span></dt>
69
+ <dd>Change the text rendered after the Gauge's value (only applicable to for 3/4 and Half gauges)</dd>
70
+ <dt class="optional">units <span class="property-type">array</span></dt>
71
+ <dd>Controls the "unit" display underneath the gauge's value for 3/4 and Half gauges (only applicable to for 3/4 and Half gauges)</dd>
72
+ <dt class="optional">icon <span class="property-type">string</span></dt>
73
+ <dd>Modify which icon is rendered within the gauge (must be a Material Design icon and only applicable to for 3/4 and Half gauges)</dd>
74
+ <dt class="optional">class <span class="property-type">string</span></dt>
75
+ <dd>Add a CSS class, or more, to the Button at runtime.</dd>
76
+ </dl>
31
77
  </script>
@@ -28,7 +28,8 @@
28
28
  "styling": "Styling",
29
29
  "class": "Class",
30
30
  "units": "Units",
31
- "icon": "Icon"
31
+ "icon": "Icon",
32
+ "defaults": "Defaults"
32
33
  },
33
34
  "errors": {
34
35
  "unique": "All 'from' values must be unique."
@@ -3,26 +3,37 @@
3
3
  Adds a single number input row to your dashboard
4
4
  </p>
5
5
  <h3>Dynamic Properties (Inputs)</h3>
6
- <p>Any of the following can be appended to a <code>msg.</code> in order to override or set properties on this node at runtime.</p>
6
+ <p>Any of the following can be appended to <code>msg.ui_update</code> in order to override or set properties on this node at runtime.</p>
7
7
  <dl class="message-properties">
8
8
  <dt class="optional">class <span class="property-type">string</span></dt>
9
- <dd>Add a CSS class, or more, to the Button at runtime.</dd>
10
- <dd>Range <span class="property-type">number</span></dd>
11
- <dd>
12
- min - the minimum value the slider can be changed to; max - the maximum value the
13
- slider can be changed to; step - the increment/decrement value when the slider is moved.
14
- </dd>
9
+ <dd>Add a CSS class, or more, to the number input at runtime.</dd>
15
10
  </dl>
16
11
  <dl class="message-properties">
17
12
  <dt class="optional">min <span class="property-type">number</span></dt>
18
- <dd>The minimum value available on the slider.</dd>
13
+ <dd>Override the minimum value available on the number input.</dd>
19
14
  </dl>
20
15
  <dl class="message-properties">
21
16
  <dt class="optional">step <span class="property-type">number</span></dt>
22
- <dd>The step size to allow a user to select from between the <code>min</code> and <code>max</code> values.</dd>
17
+ <dd>Override the step size to allow a user to select from between the <code>min</code> and <code>max</code> values.</dd>
23
18
  </dl>
24
19
  <dl class="message-properties">
25
20
  <dt class="optional">max <span class="property-type">number</span></dt>
26
- <dd>The maximum value available on the slider.</dd>
21
+ <dd>Override the maximum value available on the number input.</dd>
22
+ </dl>
23
+ <dl class="message-properties">
24
+ <dt class="optional">label <span class="property-type">string</span></dt>
25
+ <dd>Override the label displayed on the number input.</dd>
26
+ </dl>
27
+ <dl class="message-properties">
28
+ <dt class="optional">icon <span class="property-type">string</span></dt>
29
+ <dd>Override the icon defined in the initial configuration.</dd>
30
+ </dl>
31
+ <dl class="message-properties">
32
+ <dt class="optional">clearable <span class="property-type">boolean</span></dt>
33
+ <dd>Controls whether an "x" appears number on input as a user types in order to quickly clear any added numbers.</dd>
34
+ </dl>
35
+ <dl class="message-properties">
36
+ <dt class="optional">spinner <span class="property-type">string</span></dt>
37
+ <dd>Set the spinner layout as <code>inline</code> or <code>stacked</code> to control how the spinners appear in the number input.</dd>
27
38
  </dl>
28
39
  </script>
@@ -24,6 +24,8 @@
24
24
  <dd>If "Icon" is defined, this property controls which side of the "Label" the icon will render on</dd>
25
25
  <dt>Label <span class="property-type">string</span></dt>
26
26
  <dd>The text shown within the button. If not provided, then the button will only render the icon. It supports HTML content</dd>
27
+ <dt>Clickable <span class="property-type">string</span></dt>
28
+ <dd>The part of the horizontal line that is clickable. By default only the switch icon will be clickable.</dd>
27
29
  <dt>Icon <span class="property-type">default | custom</span></dt>
28
30
  <dd>Either use the "default" switch appearance, or define your own custom icons.</dd>
29
31
  <dt>On Icon <span class="property-type">string</span></dt>
@@ -55,6 +57,17 @@
55
57
  <dt class="optional">label <span class="property-type">string</span></dt>
56
58
  <dd>Change the switch label at runtime.</dd>
57
59
  </dl>
60
+ <dl class="message-properties">
61
+ <dt class="optional">clickableArea <span class="property-type">string</span></dt>
62
+ <dd>
63
+ Change the clickable area at runtime:
64
+ <ul>
65
+ <li><code>switch</code>: only the switch icon is clickable</li>
66
+ <li><code>label</code>: both the label text and the switch icon are clickable</li>
67
+ <li><code>line</code>: the entire line is clickable</li>
68
+ </ul>
69
+ </dd>
70
+ </dl>
58
71
  <dl class="message-properties">
59
72
  <dt class="optional">passthru <span class="property-type">boolean</span></dt>
60
73
  <dd>Change the passthrough behaviour of input messages at runtime.</dd>
@@ -4,9 +4,9 @@
4
4
  </p>
5
5
  <h3>Input</h3>
6
6
  <p>
7
- The ui-table widget requires an array of data to be sent via <code>msg.payload</code>.
8
- The table will then render a row for each object within the array, and, by default, a
9
- column for each property in the objects.
7
+ The ui-table widget requires an array or an object of data to be sent via <code>msg.payload</code>.
8
+ If an array is provided, the table will render a row for each object within the array, and, by default, a column for each property in the objects.
9
+ If an object is provided, the table will render a single row with columns for each property in the object.
10
10
  </p>
11
11
  <h3>Properties</h3>
12
12
  <dl class="message-properties">
@@ -15,6 +15,11 @@
15
15
  Defines the maximum number of data-rows to render in the table.
16
16
  Excess rows will be available through pagination control. Set to "0" for no pagination.
17
17
  </dd>
18
+ <dt>Action <span class="property-type">append | replace</span></dt>
19
+ <dd>
20
+ Determines the action taken when new data arrives, and whether the new data is appended
21
+ to the chart, or replaces the existing contents.
22
+ </dd>
18
23
  <dt>Breakpoint <span class="property-type">str | num</span></dt>
19
24
  <dd>
20
25
  Controls when a table will render, instead, as a card, with each column from a row rendering
@@ -18,7 +18,10 @@
18
18
  "selection": "Interaction",
19
19
  "search": "Search",
20
20
  "showSearch": "Show",
21
- "mobileBreakpoint": "Breakpoint"
21
+ "mobileBreakpoint": "Breakpoint",
22
+ "action": "Action",
23
+ "append": "Append",
24
+ "replace": "Replace"
22
25
  },
23
26
  "selection": {
24
27
  "none": "None",