@node-red/editor-client 4.1.5 → 4.1.7

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.5",
3
+ "version": "4.1.7",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/public/red/about CHANGED
@@ -1,3 +1,25 @@
1
+ #### 4.1.7: Maintenance Release
2
+
3
+ - Do not block touch events on ports (#5527) @knolleary
4
+ - Allow palette.categories to be set via theme plugin (#5526) @knolleary
5
+ - Bump i18next version (#5519) @knolleary
6
+ - Suppress i18n notice in frontend (#5528) @knolleary
7
+ - Set showSupportNotice option on i18n (#5520) @knolleary
8
+ - Do not cache subflow colors as each subflow can have its own (#5518) @knolleary
9
+ - Update tar/multer deps (#5515) @knolleary
10
+ - Remove IE7 CSS hacks (#5511) @bonanitech
11
+
12
+ #### 4.1.6: Maintenance Release
13
+
14
+ - Allow palette.theme to be set via theme plugin and include icons (#5500) @knolleary
15
+ - Ensure config sidebar tooltip handles html content (#5501) @knolleary
16
+ - Allow node-red integrator access to available updates (#5499) @Steve-Mcl
17
+ - Add frontend pre and post debug message hooks (#5495) @Steve-Mcl
18
+ - Fix: allow middle-click panning over links and ports (#5496) @lklivingstone
19
+ - Support ctrl key to select configuration nodes (#5486) @kazuhitoyokoi
20
+ - Add § as shortcut meta-key (#5482) @gorenje
21
+ - Update dependencies (#5502) @knolleary
22
+
1
23
  #### 4.1.5: Maintenance Release
2
24
 
3
25
  - chore: bump tar to 7.5.7 (#5472) @bryopsida
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
+ })
1097
+
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
+ */
1083
1109
 
1084
- var VALID_HOOKS = [
1085
1110
 
1086
- ]
1111
+ /** @type {Hooks} - A mapping of HookIds to the head of their linked list of HookItems */
1112
+ let hooks = {}
1087
1113
 
1088
- var hooks = { }
1089
- var labelledHooks = { }
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")
1125
+ }
1126
+ if (typeof callback !== "function") {
1127
+ throw new Error("Invalid hook '" + hookId + "'. Callback must be a function")
1100
1128
  }
1101
- var hookItem = {cb:callback, previousHook: null, nextHook: null }
1102
1129
 
1103
- var tailItem = hooks[id];
1130
+ /** @type {HookItem} */
1131
+ const hookItem = { cb: callback, previousHook: null, nextHook: null }
1132
+
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
1256
+ }
1257
+ if (result && typeof result.then === 'function') {
1258
+ result.then(handleResolve, callNextHook)
1259
+ return
1186
1260
  }
1187
- hookItem = hookItem.nextHook;
1188
- return callNextHook();
1189
- } catch(e) {
1190
- console.warn(e);
1191
- if (done) { done(e);}
1192
- return e;
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
  *
@@ -1264,6 +1363,7 @@ RED.i18n = (function() {
1264
1363
  apiRootUrl = options.apiRootUrl||"";
1265
1364
  var preferredLanguage = localStorage.getItem("editor-language") || detectLanguage();
1266
1365
  var opts = {
1366
+ showSupportNotice: false,
1267
1367
  backend: {
1268
1368
  loadPath: apiRootUrl+'locales/__ns__?lng=__lng__',
1269
1369
  },
@@ -10892,20 +10992,36 @@ RED.utils = (function() {
10892
10992
  return result;
10893
10993
  }
10894
10994
 
10995
+ /**
10996
+ * Get the default icon for a given node based on its definition.
10997
+ * @param {*} def
10998
+ * @param {*} node
10999
+ * @returns
11000
+ */
10895
11001
  function getDefaultNodeIcon(def,node) {
10896
11002
  def = def || {};
10897
11003
  var icon_url;
10898
11004
  if (node && node.type === "subflow") {
10899
11005
  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
11006
  } else {
10908
- icon_url = def.icon;
11007
+ let themeRule = nodeIconCache[def.type]
11008
+ if (themeRule === undefined) {
11009
+ // If undefined, we've not checked the theme yet
11010
+ nodeIconCache[def.type] = getThemeOverrideForNode(def, 'icon') || null;
11011
+ themeRule = nodeIconCache[def.type];
11012
+ }
11013
+ if (themeRule) {
11014
+ icon_url = themeRule.icon;
11015
+ } else if (typeof def.icon === "function") {
11016
+ try {
11017
+ icon_url = def.icon.call(node);
11018
+ } catch(err) {
11019
+ console.log("Definition error: "+def.type+".icon",err);
11020
+ icon_url = "arrow-in.svg";
11021
+ }
11022
+ } else {
11023
+ icon_url = def.icon;
11024
+ }
10909
11025
  }
10910
11026
 
10911
11027
  var iconPath = separateIconPath(icon_url);
@@ -11031,48 +11147,63 @@ RED.utils = (function() {
11031
11147
  return label
11032
11148
  }
11033
11149
 
11034
- var nodeColorCache = {};
11150
+ let nodeColorCache = {};
11151
+ let nodeIconCache = {}
11035
11152
  function clearNodeColorCache() {
11036
11153
  nodeColorCache = {};
11037
11154
  }
11038
11155
 
11039
- function getNodeColor(type, def) {
11040
- def = def || {};
11041
- var result = def.color;
11042
- var paletteTheme = RED.settings.theme('palette.theme') || [];
11156
+ /**
11157
+ * Checks if there is a theme override for the given node definition and property
11158
+ * @param {*} def node definition
11159
+ * @param {*} property either 'color' or 'icon'
11160
+ * @returns the theme override value if there is a match, otherwise null
11161
+ */
11162
+ function getThemeOverrideForNode(def, property) {
11163
+ const paletteTheme = RED.settings.theme('palette.theme') || [];
11043
11164
  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
- }
11165
+ for (let i = 0; i < paletteTheme.length; i++ ){
11166
+ const themeRule = paletteTheme[i];
11167
+ if (themeRule.hasOwnProperty('category')) {
11168
+ if (!themeRule.hasOwnProperty('_category')) {
11169
+ themeRule._category = new RegExp(themeRule.category);
11056
11170
  }
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
- }
11171
+ if (!themeRule._category.test(def.category)) {
11172
+ continue;
11064
11173
  }
11065
- nodeColorCache[type] = themeRule.color || def.color;
11066
- break;
11174
+ }
11175
+ if (themeRule.hasOwnProperty('type')) {
11176
+ if (!themeRule.hasOwnProperty('_type')) {
11177
+ themeRule._type = new RegExp(themeRule.type);
11178
+ }
11179
+ if (!themeRule._type.test(def.type)) {
11180
+ continue;
11181
+ }
11182
+ }
11183
+ // We have found a rule that matches - now see if it provides the requested property
11184
+ if (themeRule.hasOwnProperty(property)) {
11185
+ return themeRule;
11067
11186
  }
11068
11187
  }
11069
- result = nodeColorCache[type];
11070
11188
  }
11071
- if (result) {
11072
- return result;
11073
- } else {
11074
- return "#ddd";
11189
+ return null;
11190
+ }
11191
+
11192
+ function getNodeColor(type, def) {
11193
+ def = def || {};
11194
+ if (type === 'subflow') {
11195
+ return def.color
11196
+ }
11197
+ if (!nodeColorCache.hasOwnProperty(type)) {
11198
+ const paletteTheme = RED.settings.theme('palette.theme') || [];
11199
+ if (paletteTheme.length > 0) {
11200
+ const themeRule = getThemeOverrideForNode(def, 'color');
11201
+ nodeColorCache[type] = themeRule?.color || def.color;
11202
+ } else {
11203
+ nodeColorCache[type] = def.color;
11204
+ }
11075
11205
  }
11206
+ return nodeColorCache[type] || "#ddd";
11076
11207
  }
11077
11208
 
11078
11209
  function addSpinnerOverlay(container,contain) {
@@ -20790,6 +20921,7 @@ RED.keyboard = (function() {
20790
20921
  "-":189,
20791
20922
  ".":190,
20792
20923
  "/":191,
20924
+ "§":192, // <- top left key MacOS
20793
20925
  "\\":220,
20794
20926
  "'":222,
20795
20927
  "?":191, // <- QWERTY specific
@@ -25892,7 +26024,8 @@ RED.view = (function() {
25892
26024
  clearSuggestedFlow();
25893
26025
  RED.contextMenu.hide();
25894
26026
  evt = evt || d3.event;
25895
- if (evt === 1) {
26027
+
26028
+ if (!evt.touches && evt.button !== 0) {
25896
26029
  return;
25897
26030
  }
25898
26031
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -26748,7 +26881,7 @@ RED.view = (function() {
26748
26881
  d3.event.stopPropagation();
26749
26882
  return;
26750
26883
  }
26751
- if (d3.event.button === 2) {
26884
+ if (!d3.event.touches && d3.event.button !== 0) {
26752
26885
  return
26753
26886
  }
26754
26887
  mousedown_link = d;
@@ -34636,7 +34769,7 @@ RED.sidebar.config = (function() {
34636
34769
  nodeDiv.addClass("red-ui-palette-node-config-invalid");
34637
34770
  RED.popover.tooltip(nodeDivAnnotations, function () {
34638
34771
  if (node.validationErrors && node.validationErrors.length > 0) {
34639
- return RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ");
34772
+ return $('<span>' + RED._("editor.errors.invalidProperties") + "<br> - " + node.validationErrors.join("<br> - ") + '</span>');
34640
34773
  }
34641
34774
  })
34642
34775
  }
@@ -34644,7 +34777,7 @@ RED.sidebar.config = (function() {
34644
34777
  nodeDiv.on('click',function(e) {
34645
34778
  e.stopPropagation();
34646
34779
  RED.view.select(false);
34647
- if (e.metaKey) {
34780
+ if (e.metaKey || e.ctrlKey) {
34648
34781
  $(this).toggleClass("selected");
34649
34782
  } else {
34650
34783
  $(content).find(".red-ui-palette-node").removeClass("selected");
@@ -35294,6 +35427,12 @@ RED.palette.editor = (function() {
35294
35427
  // Install tab - search input
35295
35428
  let searchInput;
35296
35429
 
35430
+ // Core and Package Updates
35431
+ /** @type {Array<{package: string, current: string, available: string}>} */
35432
+ const moduleUpdates = []
35433
+ const updateStatusState = { version: null, moduleCount: 0 }
35434
+
35435
+
35297
35436
  const SMALL_CATALOGUE_SIZE = 40
35298
35437
 
35299
35438
  const typesInUse = {};
@@ -37079,8 +37218,6 @@ RED.palette.editor = (function() {
37079
37218
 
37080
37219
  const updateStatusWidget = $('<button type="button" class="red-ui-footer-button red-ui-update-status"></button>');
37081
37220
  let updateStatusWidgetPopover;
37082
- const updateStatusState = { moduleCount: 0 }
37083
- let updateAvailable = [];
37084
37221
 
37085
37222
  function addUpdateInfoToStatusBar() {
37086
37223
  updateStatusWidgetPopover = RED.popover.create({
@@ -37089,7 +37226,7 @@ RED.palette.editor = (function() {
37089
37226
  interactive: true,
37090
37227
  direction: "bottom",
37091
37228
  content: function () {
37092
- const count = updateAvailable.length || 0;
37229
+ const count = moduleUpdates.length || 0
37093
37230
  const content = $('<div style="display: flex; flex-direction: column; gap: 5px;"></div>');
37094
37231
  if (updateStatusState.version) {
37095
37232
  $(`<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 +37236,7 @@ RED.palette.editor = (function() {
37099
37236
  updateStatusWidgetPopover.close()
37100
37237
  RED.actions.invoke("core:manage-palette", {
37101
37238
  view: "nodes",
37102
- filter: '"' + updateAvailable.join('", "') + '"'
37239
+ filter: '"' + moduleUpdates.map(u => u.package).join('", "') + '"'
37103
37240
  });
37104
37241
  }).appendTo(content)
37105
37242
  }
@@ -37121,7 +37258,7 @@ RED.palette.editor = (function() {
37121
37258
  function refreshUpdateStatus() {
37122
37259
  clearTimeout(pendingRefreshTimeout)
37123
37260
  pendingRefreshTimeout = setTimeout(() => {
37124
- updateAvailable = [];
37261
+ moduleUpdates.length = 0
37125
37262
  for (const module of Object.keys(nodeEntries)) {
37126
37263
  if (loadedIndex.hasOwnProperty(module)) {
37127
37264
  const moduleInfo = nodeEntries[module].info;
@@ -37129,36 +37266,52 @@ RED.palette.editor = (function() {
37129
37266
  // Module updated
37130
37267
  continue;
37131
37268
  }
37269
+ const current = moduleInfo.version
37270
+ const latest = loadedIndex[module].version
37132
37271
  if (updateAllowed &&
37133
- semVerCompare(loadedIndex[module].version, moduleInfo.version) > 0 &&
37272
+ semVerCompare(latest, current) > 0 &&
37134
37273
  RED.utils.checkModuleAllowed(module, null, updateAllowList, updateDenyList)
37135
37274
  ) {
37136
- updateAvailable.push(module);
37275
+ moduleUpdates.push({ package: module, current, latest })
37137
37276
  }
37138
37277
  }
37139
37278
  }
37140
- updateStatusState.moduleCount = updateAvailable.length;
37279
+ updateStatusState.moduleCount = moduleUpdates.length
37141
37280
  updateStatus();
37142
37281
  }, 200)
37143
37282
  }
37144
37283
 
37145
37284
  function updateStatus() {
37146
- if (updateStatusState.moduleCount || updateStatusState.version) {
37285
+ const updates = RED.palette.editor.getAvailableUpdates()
37286
+ if (updates.count > 0) {
37147
37287
  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);
37288
+ $(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: updates.count })}</span>`).appendTo(updateStatusWidget);
37153
37289
  RED.statusBar.show("red-ui-status-package-update");
37154
37290
  } else {
37155
37291
  RED.statusBar.hide("red-ui-status-package-update");
37156
37292
  }
37293
+ RED.events.emit("registry:updates-available", updates)
37294
+ }
37295
+
37296
+ function getAvailableUpdates () {
37297
+ const palette = [...moduleUpdates]
37298
+ let core = null
37299
+ let count = palette.length
37300
+ if (updateStatusState.version) {
37301
+ core = { current: RED.settings.version, latest: updateStatusState.version }
37302
+ count ++
37303
+ }
37304
+ return {
37305
+ count,
37306
+ core,
37307
+ palette
37308
+ }
37157
37309
  }
37158
37310
 
37159
37311
  return {
37160
- init: init,
37161
- install: install
37312
+ init,
37313
+ install,
37314
+ getAvailableUpdates
37162
37315
  }
37163
37316
  })();
37164
37317
  ;/**