@node-red/editor-client 4.1.4 → 4.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/editor-client",
3
- "version": "4.1.4",
3
+ "version": "4.1.6",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/public/red/about CHANGED
@@ -1,4 +1,20 @@
1
- #### 4.1.4: Maintenance Releas
1
+ #### 4.1.6: Maintenance Release
2
+
3
+ - Allow palette.theme to be set via theme plugin and include icons (#5500) @knolleary
4
+ - Ensure config sidebar tooltip handles html content (#5501) @knolleary
5
+ - Allow node-red integrator access to available updates (#5499) @Steve-Mcl
6
+ - Add frontend pre and post debug message hooks (#5495) @Steve-Mcl
7
+ - Fix: allow middle-click panning over links and ports (#5496) @lklivingstone
8
+ - Support ctrl key to select configuration nodes (#5486) @kazuhitoyokoi
9
+ - Add § as shortcut meta-key (#5482) @gorenje
10
+ - Update dependencies (#5502) @knolleary
11
+
12
+ #### 4.1.5: Maintenance Release
13
+
14
+ - chore: bump tar to 7.5.7 (#5472) @bryopsida
15
+ - Update node-red-admin dependency @knolleary
16
+
17
+ #### 4.1.4: Maintenance Release
2
18
 
3
19
  - Update tar dependency @knolleary
4
20
  - Revert overflow fix in editableList (#5467) @knolleary
package/public/red/red.js CHANGED
@@ -1079,138 +1079,209 @@ var RED = (function() {
1079
1079
  emit: emit
1080
1080
  }
1081
1081
  })();
1082
- ;RED.hooks = (function() {
1082
+ ;RED.hooks = (function () {
1083
+ // At the time of writing this PR, VALID_HOOKS were not enforced. There may be a good reason for this
1084
+ // so the below flag has been added to permit this behaviour. If desired, this can be set to false to
1085
+ // enforce that only known hooks can be added/triggered.
1086
+ const knownHooksOnly = false
1087
+
1088
+ const VALID_HOOKS = Object.freeze({
1089
+ viewRemoveNode: true,
1090
+ viewAddNode: true,
1091
+ viewRemovePort: true,
1092
+ viewAddPort: true,
1093
+ viewRedrawNode: true,
1094
+ debugPreProcessMessage: true,
1095
+ debugPostProcessMessage: true
1096
+ })
1083
1097
 
1084
- var VALID_HOOKS = [
1098
+ /**
1099
+ * @typedef {keyof typeof VALID_HOOKS} HookId - A string literal type representing a hook identifier (sans label).
1100
+ *
1101
+ * @typedef {Object} HookItem - An item in the linked list of hooks for a given HookId
1102
+ * @property {function} cb - The callback function to be called when the hook is triggered
1103
+ * @property {HookItem|null} previousHook - The previous hook in the linked list
1104
+ * @property {HookItem|null} nextHook - The next hook in the linked list
1105
+ * @property {boolean} removed - Flag indicating if the hook has been removed
1106
+ *
1107
+ * @typedef {Record<HookId, HookItem|null>} Hooks - A mapping of HookIds to the head of their linked list of HookItems
1108
+ */
1085
1109
 
1086
- ]
1087
1110
 
1088
- var hooks = { }
1089
- var labelledHooks = { }
1111
+ /** @type {Hooks} - A mapping of HookIds to the head of their linked list of HookItems */
1112
+ let hooks = {}
1113
+
1114
+ /** @type {Record<string, Record<HookId, HookItem>>} - A mapping of labels to their hooks */
1115
+ let labelledHooks = {}
1090
1116
 
1091
1117
  function add(hookId, callback) {
1092
- var parts = hookId.split(".");
1093
- var id = parts[0], label = parts[1];
1118
+ const { label, id } = parseLabelledHook(hookId)
1094
1119
 
1095
- // if (VALID_HOOKS.indexOf(id) === -1) {
1096
- // throw new Error("Invalid hook '"+id+"'");
1097
- // }
1120
+ if (knownHooksOnly && !isKnownHook(id)) {
1121
+ throw new Error("Invalid hook '" + id + "'")
1122
+ }
1098
1123
  if (label && labelledHooks[label] && labelledHooks[label][id]) {
1099
- throw new Error("Hook "+hookId+" already registered")
1124
+ throw new Error("Hook " + hookId + " already registered")
1100
1125
  }
1101
- var hookItem = {cb:callback, previousHook: null, nextHook: null }
1126
+ if (typeof callback !== "function") {
1127
+ throw new Error("Invalid hook '" + hookId + "'. Callback must be a function")
1128
+ }
1129
+
1130
+ /** @type {HookItem} */
1131
+ const hookItem = { cb: callback, previousHook: null, nextHook: null }
1102
1132
 
1103
- var tailItem = hooks[id];
1133
+ let tailItem = hooks[id]
1104
1134
  if (tailItem === undefined) {
1105
- hooks[id] = hookItem;
1135
+ hooks[id] = hookItem
1106
1136
  } else {
1107
- while(tailItem.nextHook !== null) {
1137
+ while (tailItem.nextHook !== null) {
1108
1138
  tailItem = tailItem.nextHook
1109
1139
  }
1110
- tailItem.nextHook = hookItem;
1111
- hookItem.previousHook = tailItem;
1140
+ tailItem.nextHook = hookItem
1141
+ hookItem.previousHook = tailItem
1112
1142
  }
1113
1143
 
1114
1144
  if (label) {
1115
- labelledHooks[label] = labelledHooks[label]||{};
1116
- labelledHooks[label][id] = hookItem;
1145
+ labelledHooks[label] = labelledHooks[label] || {}
1146
+ labelledHooks[label][id] = hookItem
1117
1147
  }
1118
1148
  }
1149
+
1119
1150
  function remove(hookId) {
1120
- var parts = hookId.split(".");
1121
- var id = parts[0], label = parts[1];
1122
- if ( !label) {
1123
- throw new Error("Cannot remove hook without label: "+hookId)
1151
+ const { label, id } = parseLabelledHook(hookId)
1152
+ if (!label) {
1153
+ throw new Error("Cannot remove hook without label: " + hookId)
1124
1154
  }
1125
1155
  if (labelledHooks[label]) {
1126
1156
  if (id === "*") {
1127
1157
  // Remove all hooks for this label
1128
- var hookList = Object.keys(labelledHooks[label]);
1129
- for (var i=0;i<hookList.length;i++) {
1130
- removeHook(hookList[i],labelledHooks[label][hookList[i]])
1158
+ const hookList = Object.keys(labelledHooks[label])
1159
+ for (let i = 0; i < hookList.length; i++) {
1160
+ removeHook(hookList[i], labelledHooks[label][hookList[i]])
1131
1161
  }
1132
- delete labelledHooks[label];
1162
+ delete labelledHooks[label]
1133
1163
  } else if (labelledHooks[label][id]) {
1134
- removeHook(id,labelledHooks[label][id])
1135
- delete labelledHooks[label][id];
1136
- if (Object.keys(labelledHooks[label]).length === 0){
1137
- delete labelledHooks[label];
1164
+ removeHook(id, labelledHooks[label][id])
1165
+ delete labelledHooks[label][id]
1166
+ if (Object.keys(labelledHooks[label]).length === 0) {
1167
+ delete labelledHooks[label]
1138
1168
  }
1139
1169
  }
1140
1170
  }
1141
1171
  }
1142
1172
 
1143
- function removeHook(id,hookItem) {
1144
- var previousHook = hookItem.previousHook;
1145
- var nextHook = hookItem.nextHook;
1173
+ /**
1174
+ * Remove a hook from the linked list of hooks for a given id
1175
+ * @param {HookId} id
1176
+ * @param {HookItem} hookItem
1177
+ * @private
1178
+ */
1179
+ function removeHook(id, hookItem) {
1180
+ let previousHook = hookItem.previousHook
1181
+ let nextHook = hookItem.nextHook
1146
1182
 
1147
1183
  if (previousHook) {
1148
- previousHook.nextHook = nextHook;
1184
+ previousHook.nextHook = nextHook
1149
1185
  } else {
1150
- hooks[id] = nextHook;
1186
+ hooks[id] = nextHook
1151
1187
  }
1152
1188
  if (nextHook) {
1153
- nextHook.previousHook = previousHook;
1189
+ nextHook.previousHook = previousHook
1154
1190
  }
1155
- hookItem.removed = true;
1191
+ hookItem.removed = true
1156
1192
  if (!previousHook && !nextHook) {
1157
- delete hooks[id];
1193
+ delete hooks[id]
1158
1194
  }
1159
1195
  }
1160
1196
 
1161
- function trigger(hookId, payload, done) {
1162
- var hookItem = hooks[hookId];
1197
+ /**
1198
+ * Trigger a hook, calling all registered callbacks in sequence.
1199
+ * If any callback returns false, the flow is halted and no further hooks are called.
1200
+ * @param {HookId} id The id of the hook to trigger (should not include a label - e.g. "viewAddNode", not "viewAddNode.myLabel")
1201
+ * @param {*} payload The payload to be passed to each hook callback
1202
+ * @param {function(?Error=):void} [done] Optional callback. If not provided, a Promise will be returned.
1203
+ * @return {Promise|undefined} Returns a Promise if the done callback is not provided, otherwise undefined
1204
+ */
1205
+ function trigger(id, payload, done) {
1206
+ let hookItem = hooks[id]
1163
1207
  if (!hookItem) {
1164
1208
  if (done) {
1165
- done();
1209
+ done()
1210
+ return
1211
+ } else {
1212
+ return Promise.resolve()
1166
1213
  }
1167
- return;
1168
1214
  }
1215
+ if (!done) {
1216
+ return new Promise((resolve, reject) => {
1217
+ invokeStack(hookItem, payload, function (err) {
1218
+ if (err !== undefined && err !== false) {
1219
+ if (!(err instanceof Error)) {
1220
+ err = new Error(err)
1221
+ }
1222
+ err.hook = id
1223
+ reject(err)
1224
+ } else {
1225
+ resolve(err)
1226
+ }
1227
+ })
1228
+ })
1229
+ } else {
1230
+ invokeStack(hookItem, payload, done)
1231
+ }
1232
+ }
1233
+
1234
+ /**
1235
+ * @private
1236
+ */
1237
+ function invokeStack(hookItem, payload, done) {
1169
1238
  function callNextHook(err) {
1170
1239
  if (!hookItem || err) {
1171
- if (done) { done(err) }
1172
- return err;
1240
+ done(err)
1241
+ return
1173
1242
  }
1174
1243
  if (hookItem.removed) {
1175
- hookItem = hookItem.nextHook;
1176
- return callNextHook();
1244
+ hookItem = hookItem.nextHook
1245
+ callNextHook()
1246
+ return
1177
1247
  }
1178
- var callback = hookItem.cb;
1248
+ const callback = hookItem.cb
1179
1249
  if (callback.length === 1) {
1180
1250
  try {
1181
- let result = callback(payload);
1251
+ let result = callback(payload)
1182
1252
  if (result === false) {
1183
1253
  // Halting the flow
1184
- if (done) { done(false) }
1185
- return result;
1254
+ done(false)
1255
+ return
1186
1256
  }
1187
- hookItem = hookItem.nextHook;
1188
- return callNextHook();
1189
- } catch(e) {
1190
- console.warn(e);
1191
- if (done) { done(e);}
1192
- return e;
1257
+ if (result && typeof result.then === 'function') {
1258
+ result.then(handleResolve, callNextHook)
1259
+ return
1260
+ }
1261
+ hookItem = hookItem.nextHook
1262
+ callNextHook()
1263
+ } catch (e) {
1264
+ done(e)
1265
+ return
1193
1266
  }
1194
1267
  } else {
1195
- // There is a done callback
1196
1268
  try {
1197
- callback(payload,function(result) {
1198
- if (result === undefined) {
1199
- hookItem = hookItem.nextHook;
1200
- callNextHook();
1201
- } else {
1202
- if (done) { done(result)}
1203
- }
1204
- })
1205
- } catch(e) {
1206
- console.warn(e);
1207
- if (done) { done(e) }
1208
- return e;
1269
+ callback(payload, handleResolve)
1270
+ } catch (e) {
1271
+ done(e)
1272
+ return
1209
1273
  }
1210
1274
  }
1211
1275
  }
1212
-
1213
- return callNextHook();
1276
+ function handleResolve(result) {
1277
+ if (result === undefined) {
1278
+ hookItem = hookItem.nextHook
1279
+ callNextHook()
1280
+ } else {
1281
+ done(result)
1282
+ }
1283
+ }
1284
+ callNextHook()
1214
1285
  }
1215
1286
 
1216
1287
  function clear() {
@@ -1218,23 +1289,51 @@ var RED = (function() {
1218
1289
  labelledHooks = {}
1219
1290
  }
1220
1291
 
1292
+ /**
1293
+ * Check if a hook with the given id exists
1294
+ * @param {string} hookId The hook identifier, which may include a label (e.g. "viewAddNode.myLabel")
1295
+ * @returns {boolean}
1296
+ */
1221
1297
  function has(hookId) {
1222
- var parts = hookId.split(".");
1223
- var id = parts[0], label = parts[1];
1298
+ const { label, id } = parseLabelledHook(hookId)
1224
1299
  if (label) {
1225
1300
  return !!(labelledHooks[label] && labelledHooks[label][id])
1226
1301
  }
1227
1302
  return !!hooks[id]
1228
1303
  }
1229
1304
 
1305
+ function isKnownHook(hookId) {
1306
+ const { id } = parseLabelledHook(hookId)
1307
+ return !!VALID_HOOKS[id]
1308
+ }
1309
+
1310
+ /**
1311
+ * Split a hook identifier into its id and label components.
1312
+ * @param {*} hookId A hook identifier, which may include a label (e.g. "viewAddNode.myLabel")
1313
+ * @returns {{label: string, id: HookId}}
1314
+ * @private
1315
+ */
1316
+ function parseLabelledHook(hookId) {
1317
+ if (typeof hookId !== "string") {
1318
+ return { label: '', id: '' }
1319
+ }
1320
+ const parts = hookId.split(".")
1321
+ const id = parts[0]
1322
+ const label = parts[1]
1323
+ return { label, id }
1324
+ }
1325
+
1326
+ VALID_HOOKS['all'] = true // Special wildcard to allow hooks to indicate they should be triggered for all ids
1327
+
1230
1328
  return {
1231
- has: has,
1232
- clear: clear,
1233
- add: add,
1234
- remove: remove,
1235
- trigger: trigger
1329
+ has,
1330
+ clear,
1331
+ add,
1332
+ remove,
1333
+ trigger,
1334
+ isKnownHook
1236
1335
  }
1237
- })();
1336
+ })()
1238
1337
  ;/**
1239
1338
  * Copyright JS Foundation and other contributors, http://js.foundation
1240
1339
  *
@@ -10892,20 +10991,36 @@ RED.utils = (function() {
10892
10991
  return result;
10893
10992
  }
10894
10993
 
10994
+ /**
10995
+ * Get the default icon for a given node based on its definition.
10996
+ * @param {*} def
10997
+ * @param {*} node
10998
+ * @returns
10999
+ */
10895
11000
  function getDefaultNodeIcon(def,node) {
10896
11001
  def = def || {};
10897
11002
  var icon_url;
10898
11003
  if (node && node.type === "subflow") {
10899
11004
  icon_url = "node-red/subflow.svg";
10900
- } else if (typeof def.icon === "function") {
10901
- try {
10902
- icon_url = def.icon.call(node);
10903
- } catch(err) {
10904
- console.log("Definition error: "+def.type+".icon",err);
10905
- icon_url = "arrow-in.svg";
10906
- }
10907
11005
  } else {
10908
- icon_url = def.icon;
11006
+ let themeRule = nodeIconCache[def.type]
11007
+ if (themeRule === undefined) {
11008
+ // If undefined, we've not checked the theme yet
11009
+ nodeIconCache[def.type] = getThemeOverrideForNode(def, 'icon') || null;
11010
+ themeRule = nodeIconCache[def.type];
11011
+ }
11012
+ if (themeRule) {
11013
+ icon_url = themeRule.icon;
11014
+ } else if (typeof def.icon === "function") {
11015
+ try {
11016
+ icon_url = def.icon.call(node);
11017
+ } catch(err) {
11018
+ console.log("Definition error: "+def.type+".icon",err);
11019
+ icon_url = "arrow-in.svg";
11020
+ }
11021
+ } else {
11022
+ icon_url = def.icon;
11023
+ }
10909
11024
  }
10910
11025
 
10911
11026
  var iconPath = separateIconPath(icon_url);
@@ -11031,48 +11146,60 @@ RED.utils = (function() {
11031
11146
  return label
11032
11147
  }
11033
11148
 
11034
- var nodeColorCache = {};
11149
+ let nodeColorCache = {};
11150
+ let nodeIconCache = {}
11035
11151
  function clearNodeColorCache() {
11036
11152
  nodeColorCache = {};
11037
11153
  }
11038
11154
 
11039
- function getNodeColor(type, def) {
11040
- def = def || {};
11041
- var result = def.color;
11042
- var paletteTheme = RED.settings.theme('palette.theme') || [];
11155
+ /**
11156
+ * Checks if there is a theme override for the given node definition and property
11157
+ * @param {*} def node definition
11158
+ * @param {*} property either 'color' or 'icon'
11159
+ * @returns the theme override value if there is a match, otherwise null
11160
+ */
11161
+ function getThemeOverrideForNode(def, property) {
11162
+ const paletteTheme = RED.settings.theme('palette.theme') || [];
11043
11163
  if (paletteTheme.length > 0) {
11044
- if (!nodeColorCache.hasOwnProperty(type)) {
11045
- nodeColorCache[type] = def.color;
11046
- var l = paletteTheme.length;
11047
- for (var i = 0; i < l; i++ ){
11048
- var themeRule = paletteTheme[i];
11049
- if (themeRule.hasOwnProperty('category')) {
11050
- if (!themeRule.hasOwnProperty('_category')) {
11051
- themeRule._category = new RegExp(themeRule.category);
11052
- }
11053
- if (!themeRule._category.test(def.category)) {
11054
- continue;
11055
- }
11164
+ for (let i = 0; i < paletteTheme.length; i++ ){
11165
+ const themeRule = paletteTheme[i];
11166
+ if (themeRule.hasOwnProperty('category')) {
11167
+ if (!themeRule.hasOwnProperty('_category')) {
11168
+ themeRule._category = new RegExp(themeRule.category);
11056
11169
  }
11057
- if (themeRule.hasOwnProperty('type')) {
11058
- if (!themeRule.hasOwnProperty('_type')) {
11059
- themeRule._type = new RegExp(themeRule.type);
11060
- }
11061
- if (!themeRule._type.test(type)) {
11062
- continue;
11063
- }
11170
+ if (!themeRule._category.test(def.category)) {
11171
+ continue;
11172
+ }
11173
+ }
11174
+ if (themeRule.hasOwnProperty('type')) {
11175
+ if (!themeRule.hasOwnProperty('_type')) {
11176
+ themeRule._type = new RegExp(themeRule.type);
11177
+ }
11178
+ if (!themeRule._type.test(def.type)) {
11179
+ continue;
11064
11180
  }
11065
- nodeColorCache[type] = themeRule.color || def.color;
11066
- break;
11181
+ }
11182
+ // We have found a rule that matches - now see if it provides the requested property
11183
+ if (themeRule.hasOwnProperty(property)) {
11184
+ return themeRule;
11067
11185
  }
11068
11186
  }
11069
- result = nodeColorCache[type];
11070
11187
  }
11071
- if (result) {
11072
- return result;
11073
- } else {
11074
- return "#ddd";
11188
+ return null;
11189
+ }
11190
+
11191
+ function getNodeColor(type, def) {
11192
+ def = def || {};
11193
+ if (!nodeColorCache.hasOwnProperty(type)) {
11194
+ const paletteTheme = RED.settings.theme('palette.theme') || [];
11195
+ if (paletteTheme.length > 0) {
11196
+ const themeRule = getThemeOverrideForNode(def, 'color');
11197
+ nodeColorCache[type] = themeRule?.color || def.color;
11198
+ } else {
11199
+ nodeColorCache[type] = def.color;
11200
+ }
11075
11201
  }
11202
+ return nodeColorCache[type] || "#ddd";
11076
11203
  }
11077
11204
 
11078
11205
  function addSpinnerOverlay(container,contain) {
@@ -20790,6 +20917,7 @@ RED.keyboard = (function() {
20790
20917
  "-":189,
20791
20918
  ".":190,
20792
20919
  "/":191,
20920
+ "§":192, // <- top left key MacOS
20793
20921
  "\\":220,
20794
20922
  "'":222,
20795
20923
  "?":191, // <- QWERTY specific
@@ -25892,7 +26020,7 @@ RED.view = (function() {
25892
26020
  clearSuggestedFlow();
25893
26021
  RED.contextMenu.hide();
25894
26022
  evt = evt || d3.event;
25895
- if (evt === 1) {
26023
+ if (evt.button !== 0) {
25896
26024
  return;
25897
26025
  }
25898
26026
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -26748,7 +26876,7 @@ RED.view = (function() {
26748
26876
  d3.event.stopPropagation();
26749
26877
  return;
26750
26878
  }
26751
- if (d3.event.button === 2) {
26879
+ if (d3.event.button !== 0) {
26752
26880
  return
26753
26881
  }
26754
26882
  mousedown_link = d;
@@ -34636,7 +34764,7 @@ RED.sidebar.config = (function() {
34636
34764
  nodeDiv.addClass("red-ui-palette-node-config-invalid");
34637
34765
  RED.popover.tooltip(nodeDivAnnotations, function () {
34638
34766
  if (node.validationErrors && node.validationErrors.length > 0) {
34639
- return RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ");
34767
+ return $('<span>' + RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ") + '</span>');
34640
34768
  }
34641
34769
  })
34642
34770
  }
@@ -34644,7 +34772,7 @@ RED.sidebar.config = (function() {
34644
34772
  nodeDiv.on('click',function(e) {
34645
34773
  e.stopPropagation();
34646
34774
  RED.view.select(false);
34647
- if (e.metaKey) {
34775
+ if (e.metaKey || e.ctrlKey) {
34648
34776
  $(this).toggleClass("selected");
34649
34777
  } else {
34650
34778
  $(content).find(".red-ui-palette-node").removeClass("selected");
@@ -35294,6 +35422,12 @@ RED.palette.editor = (function() {
35294
35422
  // Install tab - search input
35295
35423
  let searchInput;
35296
35424
 
35425
+ // Core and Package Updates
35426
+ /** @type {Array<{package: string, current: string, available: string}>} */
35427
+ const moduleUpdates = []
35428
+ const updateStatusState = { version: null, moduleCount: 0 }
35429
+
35430
+
35297
35431
  const SMALL_CATALOGUE_SIZE = 40
35298
35432
 
35299
35433
  const typesInUse = {};
@@ -37079,8 +37213,6 @@ RED.palette.editor = (function() {
37079
37213
 
37080
37214
  const updateStatusWidget = $('<button type="button" class="red-ui-footer-button red-ui-update-status"></button>');
37081
37215
  let updateStatusWidgetPopover;
37082
- const updateStatusState = { moduleCount: 0 }
37083
- let updateAvailable = [];
37084
37216
 
37085
37217
  function addUpdateInfoToStatusBar() {
37086
37218
  updateStatusWidgetPopover = RED.popover.create({
@@ -37089,7 +37221,7 @@ RED.palette.editor = (function() {
37089
37221
  interactive: true,
37090
37222
  direction: "bottom",
37091
37223
  content: function () {
37092
- const count = updateAvailable.length || 0;
37224
+ const count = moduleUpdates.length || 0
37093
37225
  const content = $('<div style="display: flex; flex-direction: column; gap: 5px;"></div>');
37094
37226
  if (updateStatusState.version) {
37095
37227
  $(`<a class='red-ui-button' href="https://github.com/node-red/node-red/releases/tag/${updateStatusState.version}" target="_blank">${RED._("telemetry.updateAvailableDesc", updateStatusState)}</a>`).appendTo(content)
@@ -37099,7 +37231,7 @@ RED.palette.editor = (function() {
37099
37231
  updateStatusWidgetPopover.close()
37100
37232
  RED.actions.invoke("core:manage-palette", {
37101
37233
  view: "nodes",
37102
- filter: '"' + updateAvailable.join('", "') + '"'
37234
+ filter: '"' + moduleUpdates.map(u => u.package).join('", "') + '"'
37103
37235
  });
37104
37236
  }).appendTo(content)
37105
37237
  }
@@ -37121,7 +37253,7 @@ RED.palette.editor = (function() {
37121
37253
  function refreshUpdateStatus() {
37122
37254
  clearTimeout(pendingRefreshTimeout)
37123
37255
  pendingRefreshTimeout = setTimeout(() => {
37124
- updateAvailable = [];
37256
+ moduleUpdates.length = 0
37125
37257
  for (const module of Object.keys(nodeEntries)) {
37126
37258
  if (loadedIndex.hasOwnProperty(module)) {
37127
37259
  const moduleInfo = nodeEntries[module].info;
@@ -37129,36 +37261,52 @@ RED.palette.editor = (function() {
37129
37261
  // Module updated
37130
37262
  continue;
37131
37263
  }
37264
+ const current = moduleInfo.version
37265
+ const latest = loadedIndex[module].version
37132
37266
  if (updateAllowed &&
37133
- semVerCompare(loadedIndex[module].version, moduleInfo.version) > 0 &&
37267
+ semVerCompare(latest, current) > 0 &&
37134
37268
  RED.utils.checkModuleAllowed(module, null, updateAllowList, updateDenyList)
37135
37269
  ) {
37136
- updateAvailable.push(module);
37270
+ moduleUpdates.push({ package: module, current, latest })
37137
37271
  }
37138
37272
  }
37139
37273
  }
37140
- updateStatusState.moduleCount = updateAvailable.length;
37274
+ updateStatusState.moduleCount = moduleUpdates.length
37141
37275
  updateStatus();
37142
37276
  }, 200)
37143
37277
  }
37144
37278
 
37145
37279
  function updateStatus() {
37146
- if (updateStatusState.moduleCount || updateStatusState.version) {
37280
+ const updates = RED.palette.editor.getAvailableUpdates()
37281
+ if (updates.count > 0) {
37147
37282
  updateStatusWidget.empty();
37148
- let count = updateStatusState.moduleCount || 0;
37149
- if (updateStatusState.version) {
37150
- count ++
37151
- }
37152
- $(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: count })}</span>`).appendTo(updateStatusWidget);
37283
+ $(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: updates.count })}</span>`).appendTo(updateStatusWidget);
37153
37284
  RED.statusBar.show("red-ui-status-package-update");
37154
37285
  } else {
37155
37286
  RED.statusBar.hide("red-ui-status-package-update");
37156
37287
  }
37288
+ RED.events.emit("registry:updates-available", updates)
37289
+ }
37290
+
37291
+ function getAvailableUpdates () {
37292
+ const palette = [...moduleUpdates]
37293
+ let core = null
37294
+ let count = palette.length
37295
+ if (updateStatusState.version) {
37296
+ core = { current: RED.settings.version, latest: updateStatusState.version }
37297
+ count ++
37298
+ }
37299
+ return {
37300
+ count,
37301
+ core,
37302
+ palette
37303
+ }
37157
37304
  }
37158
37305
 
37159
37306
  return {
37160
- init: init,
37161
- install: install
37307
+ init,
37308
+ install,
37309
+ getAvailableUpdates
37162
37310
  }
37163
37311
  })();
37164
37312
  ;/**