From 1df2f5e96a2a32972c218ec3bd2fc96638b54fb8 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 9 Jul 2020 19:07:51 +0100 Subject: [PATCH 01/28] Allow Comms websocket auth to be done via token header Fixes #2642 --- .../editor-api/lib/auth/strategies.js | 61 ++++++++++++------- .../@node-red/editor-api/lib/auth/users.js | 4 +- .../@node-red/editor-api/lib/editor/comms.js | 33 ++++++++-- .../editor-api/lib/auth/users_spec.js | 4 ++ 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index 87023a487..bae4df5c3 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,38 +123,57 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } + +function authenticateUserToken(req) { + return new Promise( (resolve,reject) => { + var token = null; + var tokenHeader = Users.tokenHeader(); + if (Users.tokenHeader() === null) { + // No custom user token provided. Fail the request + reject(); + return; + } else if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } + if (token) { + Users.tokens(token).then(function(user) { + if (user) { + resolve(user); + } else { + reject(); + } + }); + } else { + reject(); + } + }); +} + + function TokensStrategy() { passport.Strategy.call(this); this.name = 'tokens'; } util.inherits(TokensStrategy, passport.Strategy); TokensStrategy.prototype.authenticate = function(req) { - var self = this; - var token = null; - if (Users.tokenHeader() === 'authorization') { - if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { - token = req.headers.authorization.split(' ')[1]; - } - } else { - token = req.headers[Users.tokenHeader()]; - } - if (token) { - Users.tokens(token).then(function(admin) { - if (admin) { - self.success(admin,{scope:admin.permissions}); - } else { - self.fail(401); - } - }); - } else { - self.fail(401); - } + authenticateUserToken(req).then(user => { + this.success(user,{scope:user.permissions}); + }).catch(err => { + this.fail(401); + }); } + + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, anonymousStrategy: new AnonymousStrategy(), - tokensStrategy: new TokensStrategy() + tokensStrategy: new TokensStrategy(), + authenticateUserToken: authenticateUserToken } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index f032332db..b5754bee9 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -61,7 +61,7 @@ var api = { authenticate: authenticate, default: getDefaultUser, tokens: getDefaultUser, - tokenHeader: "authorization" + tokenHeader: null } function init(config) { @@ -111,6 +111,8 @@ function init(config) { api.tokens = config.tokens; if (config.tokenHeader && typeof config.tokenHeader === "string") { api.tokenHeader = config.tokenHeader.toLowerCase(); + } else { + api.tokenHeader = "authorization"; } } } diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/comms.js b/packages/node_modules/@node-red/editor-api/lib/editor/comms.js index 2c46f87e8..84da62a69 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/comms.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/comms.js @@ -21,6 +21,7 @@ var log = require("@node-red/util").log; // TODO: separate module var Tokens; var Users; var Permissions; +var Strategies; var server; var settings; @@ -44,6 +45,7 @@ function init(_server,_settings,_runtimeAPI) { Tokens.onSessionExpiry(handleSessionExpiry); Users = require("../auth/users"); Permissions = require("../auth/permissions"); + Strategies = require("../auth/strategies"); } function handleSessionExpiry(session) { @@ -63,17 +65,18 @@ function generateSession(length) { return token.join(""); } -function CommsConnection(ws) { +function CommsConnection(ws, user) { this.session = generateSession(32); this.ws = ws; this.stack = []; - this.user = null; + this.user = user; this.lastSentTime = 0; var self = this; log.audit({event: "comms.open"}); log.trace("comms.open "+self.session); - var pendingAuth = (settings.adminAuth != null); + var preAuthed = !!user; + var pendingAuth = !this.user && (settings.adminAuth != null); if (!pendingAuth) { addActiveConnection(self); @@ -199,8 +202,8 @@ function start() { var commsPath = settings.httpAdminRoot || "/"; commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms"; wsServer = new ws.Server({ noServer: true }); - wsServer.on('connection',function(ws) { - var commsConnection = new CommsConnection(ws); + wsServer.on('connection',function(ws, request, user) { + var commsConnection = new CommsConnection(ws, user); }); wsServer.on('error', function(err) { log.warn(log._("comms.error-server",{message:err.toString()})); @@ -209,8 +212,26 @@ function start() { server.on('upgrade', function upgrade(request, socket, head) { const pathname = url.parse(request.url).pathname; if (pathname === commsPath) { + if (Users.tokenHeader() !== null && request.headers[Users.tokenHeader()]) { + // The user has provided custom token handling. For the websocket, + // the token could be provided in two ways: + // - as an http header (only possible with a reverse proxy setup) + // - passed over the connected websock in an auth packet + // If the header is present, verify the token. If not, use the auth + // packet over the connected socket + // + Strategies.authenticateUserToken(request).then(user => { + wsServer.handleUpgrade(request, socket, head, function done(ws) { + wsServer.emit('connection', ws, request, user); + }); + }).catch(err => { + log.audit({event: "comms.auth.fail"}); + socket.destroy(); + }) + return + } wsServer.handleUpgrade(request, socket, head, function done(ws) { - wsServer.emit('connection', ws, request); + wsServer.emit('connection', ws, request, null); }); } // Don't destroy the socket as other listeners may want to handle the diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 228163684..18a179ac7 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -155,6 +155,10 @@ describe("api/auth/users", function() { }); describe('#get',function() { + it("returns null for tokenHeader", function() { + should.not.exist(Users.tokenHeader()); + }); + it('delegates get user',function(done) { Users.get('dave').then(function(user) { try { From 97b7479081e5de34963081cc3f552d3a39e0688c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 9 Jul 2020 20:41:55 +0100 Subject: [PATCH 02/28] Disable keyboard handler when dialogs are open --- .../editor-client/src/js/ui/clipboard.js | 4 ++++ .../editor-client/src/js/ui/keyboard.js | 16 +++++++++++++++- .../@node-red/editor-client/src/js/ui/library.js | 4 ++++ .../editor-client/src/js/ui/projects/projects.js | 6 ++++++ .../@node-red/editor-client/src/js/user.js | 14 +++++++++----- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index 33b306752..32ba905af 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -159,7 +159,11 @@ RED.clipboard = (function() { } } ], + open: function( event, ui ) { + RED.keyboard.disable(); + }, close: function(e) { + RED.keyboard.enable(); if (popover) { popover.close(true); currentPopoverError = null; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index 3e79f6eb1..a202957ae 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -17,6 +17,8 @@ RED.keyboard = (function() { var isMac = /Mac/i.test(window.navigator.platform); + var handlersActive = true; + var handlers = {}; var partialState; @@ -225,6 +227,9 @@ RED.keyboard = (function() { } } d3.select(window).on("keydown",function() { + if (!handlersActive) { + return; + } if (metaKeyCodes[d3.event.keyCode]) { return; } @@ -570,6 +575,13 @@ RED.keyboard = (function() { return pane; } + function enable() { + handlersActive = true; + } + function disable() { + handlersActive = false; + } + return { init: init, add: addHandler, @@ -579,7 +591,9 @@ RED.keyboard = (function() { }, revertToDefault: revertToDefault, formatKey: formatKey, - validateKey: validateKey + validateKey: validateKey, + disable: disable, + enable: enable } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index f5e23d19d..54d73c022 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -472,6 +472,8 @@ RED.library = (function() { autoOpen: false, width: 800, resizable: false, + open: function( event, ui ) { RED.keyboard.disable() }, + close: function( event, ui ) { RED.keyboard.enable() }, classes: { "ui-dialog": "red-ui-editor-dialog", "ui-dialog-titlebar-close": "hide", @@ -556,9 +558,11 @@ RED.library = (function() { } ], open: function(e) { + RED.keyboard.disable(); $(this).parent().find(".ui-dialog-titlebar-close").hide(); }, close: function(e) { + RED.keyboard.enable(); if (libraryEditor) { libraryEditor.destroy(); libraryEditor = null; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index 39c9d81a9..13f9dcb02 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -2263,6 +2263,12 @@ RED.projects = (function() { autoOpen: false, width: 600, resizable: false, + open: function(e) { + RED.keyboard.disable(); + }, + close: function(e) { + RED.keyboard.enable(); + }, classes: { "ui-dialog": "red-ui-editor-dialog", "ui-dialog-titlebar-close": "hide", diff --git a/packages/node_modules/@node-red/editor-client/src/js/user.js b/packages/node_modules/@node-red/editor-client/src/js/user.js index 485670e4f..dd0ff8f90 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/user.js +++ b/packages/node_modules/@node-red/editor-client/src/js/user.js @@ -39,7 +39,11 @@ RED.user = (function() { closeOnEscape: !!opts.cancelable, width: 600, resizable: false, - draggable: false + draggable: false, + close: function( event, ui ) { + $("#node-dialog-login").dialog('destroy').remove(); + RED.keyboard.enable() + } }); $("#node-dialog-login-fields").empty(); @@ -98,10 +102,10 @@ RED.user = (function() { data: body }).done(function(data,textStatus,xhr) { RED.settings.set("auth-tokens",data); - $("#node-dialog-login").dialog('destroy').remove(); if (opts.updateMenu) { updateUserMenu(); } + $("#node-dialog-login").dialog("close"); done(); }).fail(function(jqXHR,textStatus,errorThrown) { RED.settings.remove("auth-tokens"); @@ -143,7 +147,8 @@ RED.user = (function() { } if (opts.cancelable) { $("#node-dialog-login-cancel").button().on("click", function( event ) { - $("#node-dialog-login").dialog('destroy').remove(); + $("#node-dialog-login").dialog('close'); + }); } @@ -152,8 +157,7 @@ RED.user = (function() { $("#node-dialog-login-image").load(function() { dialog.dialog("open"); }).attr("src",loginImageSrc); - - + RED.keyboard.disable(); } }); } From 979c5351a89ff9ab92da7f90cd2d26cf0ee98d46 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 10 Jul 2020 15:59:28 +0100 Subject: [PATCH 03/28] Ensure node/group xrefs are consistent on import --- .../@node-red/editor-client/src/js/nodes.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index ee7e7413f..43fdbd5f3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1442,7 +1442,15 @@ RED.nodes = (function() { return node_map[id]; }) // Just in case the group references a node that doesn't exist for some reason - n.nodes = n.nodes.filter(function(v) { return !!v}); + n.nodes = n.nodes.filter(function(v) { + if (v) { + // Repair any nodes that have forgotten they are in this group + if (v.g !== n.id) { + v.g = n.id; + } + } + return !!v + }); if (!n.g) { groupDepthMap[n.id] = 0; } From 612c565cfded1824bc9fd08ea0aac7b93b4e8e05 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 10 Jul 2020 16:00:18 +0100 Subject: [PATCH 04/28] Add RED.view.redrawStatus to avoid full redraw on update --- .../@node-red/editor-client/src/js/red.js | 2 +- .../@node-red/editor-client/src/js/ui/view.js | 56 +++++++++++-------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 814f5c542..f9f4eff00 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -372,7 +372,7 @@ var RED = (function() { node.status = msg; node.dirtyStatus = true; node.dirty = true; - RED.view.redraw(); + RED.view.redrawStatus(node); } }); RED.comms.subscribe("notification/node/#",function(topic,msg) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index b49e4a836..91f45db86 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3424,6 +3424,37 @@ RED.view = (function() { } } + function redrawStatus(d,nodeEl) { + if (d.z !== RED.workspaces.active()) { + return; + } + if (!nodeEl) { + nodeEl = document.getElementById(d.id); + } + if (nodeEl) { + if (!showStatus || !d.status) { + nodeEl.__statusGroup__.style.display = "none"; + } else { + nodeEl.__statusGroup__.style.display = "inline"; + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + nodeEl.__statusShape__.style.display = "none"; + nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); + } else { + nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); + var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; + nodeEl.__statusShape__.style.display = "inline"; + nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); + } + if (d.status.hasOwnProperty('text')) { + nodeEl.__statusLabel__.textContent = d.status.text; + } else { + nodeEl.__statusLabel__.textContent = ""; + } + } + delete d.dirtyStatus; + } + } var pendingRedraw; @@ -3992,27 +4023,7 @@ RED.view = (function() { } if (d.dirtyStatus) { - if (!showStatus || !d.status) { - this.__statusGroup__.style.display = "none"; - } else { - this.__statusGroup__.style.display = "inline"; - var fill = status_colours[d.status.fill]; // Only allow our colours for now - if (d.status.shape == null && fill == null) { - this.__statusShape__.style.display = "none"; - this.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); - } else { - this.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); - var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; - this.__statusShape__.style.display = "inline"; - this.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); - } - if (d.status.hasOwnProperty('text')) { - this.__statusLabel__.textContent = d.status.text; - } else { - this.__statusLabel__.textContent = ""; - } - } - delete d.dirtyStatus; + redrawStatus(d,this); } d.dirty = false; if (d.g) { @@ -4928,6 +4939,7 @@ RED.view = (function() { }, clipboard: function() { return clipboard - } + }, + redrawStatus: redrawStatus }; })(); From 580cc0096724db54dde351aaa64e3654f75beed2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 10 Jul 2020 16:00:38 +0100 Subject: [PATCH 05/28] Fix all the touch screen issues --- .../@node-red/editor-client/src/js/ui/view.js | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 91f45db86..27033fceb 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -34,7 +34,8 @@ RED.view = (function() { lineCurveScale = 0.75, scaleFactor = 1, node_width = 100, - node_height = 30; + node_height = 30, + dblClickInterval = 650; var touchLongPressTimeout = 1000, startTouchDistance = 0, @@ -125,6 +126,7 @@ RED.view = (function() { .attr("height", space_height) .attr("pointer-events", "all") .style("cursor","crosshair") + .style("touch-action","none") .on("mousedown", function() { focusView(); }) @@ -159,8 +161,13 @@ RED.view = (function() { } canvasMouseUp.call(this); }) - .on("touchcancel", function() { d3.event.preventDefault(); canvasMouseUp.call(this); }) + .on("touchcancel", function() { + if (RED.view.DEBUG) { console.warn("eventLayer.touchcancel", mouse_mode); } + d3.event.preventDefault(); + canvasMouseUp.call(this); + }) .on("touchstart", function() { + if (RED.view.DEBUG) { console.warn("eventLayer.touchstart", mouse_mode); } var touch0; if (d3.event.touches.length>1) { clearTimeout(touchStartTime); @@ -211,6 +218,7 @@ RED.view = (function() { d3.event.preventDefault(); return; } + if (RED.view.DEBUG) { console.warn("eventLayer.touchmove", mouse_mode, mousedown_node); } var touch0; if (d3.event.touches.length<2) { if (touchStartTime) { @@ -221,6 +229,12 @@ RED.view = (function() { if (d > 64) { clearTimeout(touchStartTime); touchStartTime = null; + if (!mousedown_node && !mousedown_group) { + mouse_mode = RED.state.PANNING; + mouse_position = [touch0.pageX,touch0.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + } + } } else if (lasso) { d3.event.preventDefault(); @@ -1156,8 +1170,11 @@ RED.view = (function() { //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); if (mouse_mode === RED.state.PANNING) { - var pos = [d3.event.pageX,d3.event.pageY]; + if (d3.event.touches) { + var touch0 = d3.event.touches.item(0); + pos = [touch0.pageX, touch0.pageY]; + } var deltaPos = [ mouse_position[0]-pos[0], mouse_position[1]-pos[1] @@ -1293,7 +1310,7 @@ RED.view = (function() { mousePos = d3.touches(document.body)[0]; } var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]); - if (d > 3) { + if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { mouse_mode = RED.state.MOVING_ACTIVE; clickElapsed = 0; spliceActive = false; @@ -1753,7 +1770,7 @@ RED.view = (function() { } function clearSelection() { - if (RED.view.DEBUG) { console.warn("clearSelection", mouse_mode); } + if (RED.view.DEBUG) { console.warn("clearSelection", mouse_mode,"moving_set.length:",moving_set.length); } for (var i=0;i 0 && clickElapsed < 750) { + if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; if (d.type != "subflow") { RED.editor.edit(d); @@ -2835,10 +2852,11 @@ RED.view = (function() { var now = Date.now(); clickElapsed = now-clickTime; clickTime = now; - dblClickPrimed = (lastClickNode == mousedown_node && - d3.event.button === 0 && - !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey); + (d3.event.touches || d3.event.button === 0) && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && + clickElapsed < dblClickInterval + ) lastClickNode = mousedown_node; var i; @@ -3113,7 +3131,7 @@ RED.view = (function() { } function groupMouseUp(g) { - if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < 750) { + if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; RED.editor.editGroup(g); d3.event.stopPropagation(); @@ -3150,8 +3168,9 @@ RED.view = (function() { dblClickPrimed = ( lastClickNode == g && - d3.event.button === 0 && - !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey + (d3.event.touches || d3.event.button === 0) && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && + clickElapsed < dblClickInterval ); lastClickNode = g; @@ -3805,9 +3824,11 @@ RED.view = (function() { //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + // This might be the first redraw after a node has been click-dragged to start a move. + // So its selected state might have changed since the last redraw. + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) if (mouse_mode != RED.state.MOVING_ACTIVE) { this.classList.toggle("red-ui-flow-node-disabled", d.d === true); - this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) this.__mainRect__.setAttribute("width", d.w) this.__mainRect__.setAttribute("height", d.h) this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); @@ -4253,6 +4274,8 @@ RED.view = (function() { .attr("y",-4) selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); + selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); @@ -4262,6 +4285,9 @@ RED.view = (function() { "stroke": d.stroke||"none", }) g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + g.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + g.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); + g.append('svg:text').attr("class","red-ui-flow-group-label"); d.dirty = true; }); From 7f671c9f3ffd0f318213fa89051681117551a127 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 10 Jul 2020 20:00:18 +0100 Subject: [PATCH 06/28] Ensure unknown nodes removed from outliner when node registers Fixes #2646 --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 43fdbd5f3..1ea08193e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1671,6 +1671,7 @@ RED.nodes = (function() { } } reimportList.push(convertNode(n)); + RED.events.emit('nodes:remove',n); }); // Remove any links between nodes that are going to be reimported. From 43db1824be519de70be8a0147b2d438be8abdbc4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 10 Jul 2020 20:13:05 +0100 Subject: [PATCH 07/28] Bump for 1.1.2 --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- .../node_modules/@node-red/registry/package.json | 4 ++-- .../node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 9 files changed, 31 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a24f89bc8..ee4589b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +### 1.1.2: Maintenance Release + +Editor + + - Fix all the touch screen issues Fixes #2647 + - Add RED.view.redrawStatus to avoid full redraw on update + - Ensure node/group xrefs are consistent on import + - Disable keyboard handler when dialogs are open + - Ensure unknown nodes removed from outliner when node registers Fixes #2646 + +Runtime + + - Allow Comms websocket auth to be done via token header Fixes #2642 + ### 1.1.1: Maintenance Release Editor diff --git a/package.json b/package.json index 57a58ced0..ba6f7b6f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.1.1", + "version": "1.1.2", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index ffbdbdba0..3971a65eb 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "1.1.1", - "@node-red/editor-client": "1.1.1", + "@node-red/util": "1.1.2", + "@node-red/editor-client": "1.1.2", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 60cf4ad04..eebd07b6a 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index e7e6db2ca..2150ef0cd 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index b78e2080a..a3c37c231 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "1.1.1", + "@node-red/util": "1.1.2", "semver": "6.3.0", "uglify-js": "3.10.0", "when": "3.7.8" diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index ccedee0f3..9b808a2aa 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.1.1", - "@node-red/util": "1.1.1", + "@node-red/registry": "1.1.2", + "@node-red/util": "1.1.2", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index f88cb7b73..9bedb0058 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.1.1", + "version": "1.1.2", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index be6754de7..7020c9b99 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.1.1", + "version": "1.1.2", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.1.1", - "@node-red/runtime": "1.1.1", - "@node-red/util": "1.1.1", - "@node-red/nodes": "1.1.1", + "@node-red/editor-api": "1.1.2", + "@node-red/runtime": "1.1.2", + "@node-red/util": "1.1.2", + "@node-red/nodes": "1.1.2", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", From 62c01b59b2e655bf07f363983abc3a09a8bdfcbe Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 10 Jul 2020 21:46:00 +0100 Subject: [PATCH 08/28] Extend release action to update website --- .github/scripts/update-node-red-website.js | 18 ++++++++++++++++++ .github/workflows/build.yml | 22 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .github/scripts/update-node-red-website.js diff --git a/.github/scripts/update-node-red-website.js b/.github/scripts/update-node-red-website.js new file mode 100644 index 000000000..cc40c2996 --- /dev/null +++ b/.github/scripts/update-node-red-website.js @@ -0,0 +1,18 @@ +const fs = require("fs"); + +const newVersion = require("../../package.json").version; + +if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) { + console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`); + process.exit(0); +} + +if (!/^\d+\.\d+\.\d+$/.test(newVersion)) { + console.log(`Not updating for a non-stable release - ${newVersion}`); + process.exit(0); +} + +const path = __dirname+"/../../../node-red.github.io/index.html"; +let contents = fs.readFileSync(path, "utf8"); +contents = contents.replace(/v\d+\.\d+\.\d+<\/span>/, `v${newVersion}<\/span>` ); +fs.writeFileSync(path, contents); \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 356d8d71f..43b0137c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,12 +18,16 @@ jobs: with: repository: 'node-red/node-red-docker' path: 'node-red-docker' + - name: Check out node-red.github.io repository + uses: actions/checkout@v2 + with: + repository: 'node-red/node-red.github.io' + path: 'node-red.github.io' - uses: actions/setup-node@v1 with: node-version: '12' - run: node ./node-red/.github/scripts/update-node-red-docker.js - id: updateFiles - - name: Create Pull Request + - name: Create Docker Pull Request uses: peter-evans/create-pull-request@v2 with: token: ${{ secrets.NR_REPO_TOKEN }} @@ -37,4 +41,18 @@ jobs: Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`. + This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary + - run: node ./node-red/.github/scripts/update-node-red-website.js + - name: Create Website Pull Request + uses: peter-evans/create-pull-request@v2 + with: + token: ${{ secrets.NR_REPO_TOKEN }} + committer: GitHub + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + path: 'node-red.github.io' + commit-message: 'Bump to ${{ env.newVersion }}' + title: '🚀 Update to Node-RED ${{ env.newVersion }} release' + body: | + Updates the Node-RED Website repo for the ${{ env.newVersion }} release. + This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary From 98c736492403dc8e2af7eda5d419e421d6183ea6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 13 Jul 2020 20:44:53 +0100 Subject: [PATCH 09/28] Ensure group theme picks up theme defaults properly Fixes #2651 --- .../@node-red/editor-client/src/js/ui/group.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 9dbd9c19b..e7220b817 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -105,18 +105,18 @@ RED.group = (function() { cellHeight: 16, cellMargin: 3, none: true, - opacity: style['stroke-opacity'] || 1.0 + opacity: style.hasOwnProperty('stroke-opacity')?style['stroke-opacity']:(defaultGroupStyle.hasOwnProperty('stroke-opacity')?defaultGroupStyle['stroke-opacity']:1.0) }).appendTo("#node-input-row-style-stroke"); RED.colorPicker.create({ id:"node-input-style-fill", - value: style.fill || "none", + value: style.fill || defaultGroupStyle.fill ||"none", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, cellHeight: 16, cellMargin: 3, none: true, - opacity: style['fill-opacity'] || 1.0 + opacity: style.hasOwnProperty('fill-opacity')?style['fill-opacity']:(defaultGroupStyle.hasOwnProperty('fill-opacity')?defaultGroupStyle['fill-opacity']:1.0) }).appendTo("#node-input-row-style-fill"); createLayoutPicker({ @@ -162,12 +162,6 @@ RED.group = (function() { delete this.style.color; } - if (this.style["stroke-opacity"] === "1") { - delete this.style["stroke-opacity"] - } - if (this.style["fill-opacity"] === "1") { - delete this.style["fill-opacity"] - } var node = this; ['stroke','fill','stroke-opacity','fill-opacity','color','label-position'].forEach(function(prop) { if (node.style[prop] === defaultGroupStyle[prop]) { From 82677c304e7640a5ea640bac407eb7ac719d6c78 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jul 2020 16:12:16 +0100 Subject: [PATCH 10/28] Show node help when switching node edit dialogs Fixes #2652 --- .../@node-red/editor-client/src/js/ui/editor.js | 2 ++ .../@node-red/editor-client/src/js/ui/tab-help.js | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index c59e02d0e..1e80643b4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1630,6 +1630,7 @@ RED.editor = (function() { show: function() { if (editing_node) { RED.sidebar.info.refresh(editing_node); + RED.sidebar.help.show(editing_node.type, false); } } } @@ -1836,6 +1837,7 @@ RED.editor = (function() { show: function() { if (editing_config_node) { RED.sidebar.info.refresh(editing_config_node); + RED.sidebar.help.show(type, false); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index db8abee2b..c1344e755 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -261,10 +261,12 @@ RED.sidebar.help = (function() { } - function show(type) { - RED.sidebar.show("help"); + function show(type, bringToFront) { + if (bringToFront !== false) { + RED.sidebar.show("help"); + } if (type) { - hideTOC(); + // hideTOC(); showHelp(type); } resizeStack(); From e4dd89570969b5395f3fb89e1d15506e0a424ae8 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Wed, 22 Jul 2020 12:54:07 +0900 Subject: [PATCH 11/28] update Chinese message for debug node --- .../node_modules/@node-red/nodes/locales/zh-CN/messages.json | 2 +- .../node_modules/@node-red/nodes/locales/zh-TW/messages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 8355cf353..7c5e50a5d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -123,7 +123,7 @@ "none": "None", "invalid-exp": "无效的JSONata表达式: __error__", "msgprop": "信息属性", - "msgobj": "完整信息", + "msgobj": "与调试输出相同", "autostatus": "自动的", "to": "目标", "debtab": "调试窗口", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 0875cab9b..6f8860de1 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -123,7 +123,7 @@ "none": "None", "invalid-exp": "無效的JSONata表達式: __error__", "msgprop": "資訊屬性", - "msgobj": "完整資訊", + "msgobj": "與調試輸出相同", "autostatus": "自動的", "to": "目標", "debtab": "除錯窗口", From 442b9d23f12491cc39d927ae4cc2ebcaf7102dc9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 22 Jul 2020 14:51:07 +0100 Subject: [PATCH 12/28] Remove filtering of duplicate fa icons --- .../@node-red/editor-client/src/js/font-awesome.js | 12 +----------- .../@node-red/editor-client/src/sass/editor.scss | 1 + 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js b/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js index c97e09c66..8811f1514 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js +++ b/packages/node_modules/@node-red/editor-client/src/js/font-awesome.js @@ -808,17 +808,7 @@ RED.nodes.fontAwesome = (function() { "fa-youtube": "\uf167", }; - var iconList = []; - var isUsed = {}; - Object.keys(iconMap).forEach(function(icon) { - var unicode = iconMap[icon]; - // skip icons with a same unicode - if (isUsed[unicode] !== true) { - iconList.push(icon); - isUsed[unicode] = true; - } - }); - isUsed = undefined; + var iconList = Object.keys(iconMap); return { getIconUnicode: function(name) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 4e0aca45b..1f3cdb181 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -597,6 +597,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { padding: 4px; color: $secondary-text-color; font-size: 0.9em; + line-height: 24px; } button { float: right; From 889224715bf7a94d780ab84b0000db1855d78628 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 23 Jul 2020 10:04:24 +0100 Subject: [PATCH 13/28] Fix hhp-in to handle application/cbor as binary as per discussion https://discourse.nodered.org/t/http-request-node-invalid-message-body-was-specified-to-be-cbor-but-could-not-decode-message-failed-to-parse/30503 --- .../node_modules/@node-red/nodes/core/network/21-httpin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 6cf243cf8..df099bbc7 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -46,10 +46,10 @@ module.exports = function(RED) { isText = true; } else if (parsedType.type !== "application") { isText = false; - } else if (parsedType.subtype !== "octet-stream") { + } else if ((parsedType.subtype !== "octet-stream") && (parsedType.subtype !== "cbor")) { checkUTF = true; } else { - // applicatino/octet-stream + // application/octet-stream or application/cbor isText = false; } From 16c26d8098bfe482b48007e32bc4253c79a53201 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 31 Jul 2020 15:26:21 +0100 Subject: [PATCH 14/28] Move runtime settings back to adminApi from editorApi Fixes #2662 --- .../@node-red/editor-api/lib/admin/index.js | 6 +- .../editor-api/lib/admin/settings.js | 72 ++++++++++++++ .../@node-red/editor-api/lib/editor/index.js | 2 +- .../editor-api/lib/editor/settings.js | 44 --------- .../@node-red/editor-api/lib/index.js | 2 +- .../@node-red/runtime/lib/api/settings.js | 54 +++++------ .../editor-api/lib/admin/index_spec.js | 2 +- .../editor-api/lib/admin/settings_spec.js | 93 +++++++++++++++++++ .../editor-api/lib/editor/settings_spec.js | 26 ------ .../runtime/lib/api/settings_spec.js | 51 ++++++++++ 10 files changed, 252 insertions(+), 100 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-api/lib/admin/settings.js create mode 100644 test/unit/@node-red/editor-api/lib/admin/settings_spec.js diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index 50d7b168f..d982c560d 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -21,15 +21,17 @@ var flows = require("./flows"); var flow = require("./flow"); var context = require("./context"); var auth = require("../auth"); +var info = require("./settings"); var apiUtil = require("../util"); module.exports = { - init: function(runtimeAPI) { + init: function(settings,runtimeAPI) { flows.init(runtimeAPI); flow.init(runtimeAPI); nodes.init(runtimeAPI); context.init(runtimeAPI); + info.init(settings,runtimeAPI); var needsPermission = auth.needsPermission; @@ -67,6 +69,8 @@ module.exports = { // adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler); adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler); + adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); + return adminApp; } } diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/settings.js b/packages/node_modules/@node-red/editor-api/lib/admin/settings.js new file mode 100644 index 000000000..d72f9e094 --- /dev/null +++ b/packages/node_modules/@node-red/editor-api/lib/admin/settings.js @@ -0,0 +1,72 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +var apiUtils = require("../util"); +var runtimeAPI; +var settings; +var theme = require("../editor/theme"); +var clone = require("clone"); + +var i18n = require("@node-red/util").i18n + +function extend(target, source) { + var keys = Object.keys(source); + var i = keys.length; + while(i--) { + var value = source[keys[i]] + var type = typeof value; + if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) { + target[keys[i]] = value; + } else if (value === null) { + if (target.hasOwnProperty(keys[i])) { + delete target[keys[i]]; + } + } else { + // Object + if (target.hasOwnProperty(keys[i])) { + target[keys[i]] = extend(target[keys[i]],value); + } else { + target[keys[i]] = value; + } + } + } + return target; +} + +module.exports = { + init: function(_settings,_runtimeAPI) { + runtimeAPI = _runtimeAPI; + settings = _settings; + }, + runtimeSettings: function(req,res) { + var opts = { + user: req.user + } + runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) { + if (!settings.disableEditor) { + result.editorTheme = result.editorTheme||{}; + var themeSettings = theme.settings(); + if (themeSettings) { + // result.editorTheme may already exist with the palette + // disabled. Need to merge that into the receive settings + result.editorTheme = extend(clone(themeSettings),result.editorTheme); + } + result.editorTheme.languages = i18n.availableLanguages("editor"); + } + res.json(result); + }); + }, + +} diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index a9cdf0ffc..71876eaa6 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -103,7 +103,7 @@ module.exports = { editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler); // Settings - editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); + // Main /settings route is an admin route - see lib/admin/settings.js // User Settings editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler); // User Settings diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/settings.js b/packages/node_modules/@node-red/editor-api/lib/editor/settings.js index 9d9867f1b..5fa2476e1 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/settings.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/settings.js @@ -16,56 +16,12 @@ var apiUtils = require("../util"); var runtimeAPI; var sshkeys = require("./sshkeys"); -var theme = require("./theme"); -var clone = require("clone"); - -var i18n = require("@node-red/util").i18n - -function extend(target, source) { - var keys = Object.keys(source); - var i = keys.length; - while(i--) { - var value = source[keys[i]] - var type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) { - target[keys[i]] = value; - } else if (value === null) { - if (target.hasOwnProperty(keys[i])) { - delete target[keys[i]]; - } - } else { - // Object - if (target.hasOwnProperty(keys[i])) { - target[keys[i]] = extend(target[keys[i]],value); - } else { - target[keys[i]] = value; - } - } - } - return target; -} module.exports = { init: function(_runtimeAPI) { runtimeAPI = _runtimeAPI; sshkeys.init(runtimeAPI); }, - runtimeSettings: function(req,res) { - var opts = { - user: req.user - } - runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) { - result.editorTheme = result.editorTheme||{}; - var themeSettings = theme.settings(); - if (themeSettings) { - // result.editorTheme may already exist with the palette - // disabled. Need to merge that into the receive settings - result.editorTheme = extend(clone(themeSettings),result.editorTheme); - } - result.editorTheme.languages = i18n.availableLanguages("editor"); - res.json(result); - }); - }, userSettings: function(req, res) { var opts = { user: req.user diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 534a77869..457b99cc9 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -99,7 +99,7 @@ function init(settings,_server,storage,runtimeAPI) { adminApp.use(corsHandler); } - var adminApiApp = require("./admin").init(runtimeAPI); + var adminApiApp = require("./admin").init(settings, runtimeAPI); adminApp.use(adminApiApp); } else { adminApp = null; diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 96bb6c973..6ebeeccfb 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -81,39 +81,41 @@ var api = module.exports = { }) } - safeSettings.context = runtime.nodes.listContextStores(); + if (!runtime.settings.disableEditor) { + safeSettings.context = runtime.nodes.listContextStores(); - if (util.isArray(runtime.settings.paletteCategories)) { - safeSettings.paletteCategories = runtime.settings.paletteCategories; - } + if (util.isArray(runtime.settings.paletteCategories)) { + safeSettings.paletteCategories = runtime.settings.paletteCategories; + } - if (runtime.settings.flowFilePretty) { - safeSettings.flowFilePretty = runtime.settings.flowFilePretty; - } + if (runtime.settings.flowFilePretty) { + safeSettings.flowFilePretty = runtime.settings.flowFilePretty; + } - if (!runtime.nodes.paletteEditorEnabled()) { - safeSettings.editorTheme = safeSettings.editorTheme || {}; - safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; - safeSettings.editorTheme.palette.editable = false; - } - if (runtime.storage.projects) { - var activeProject = runtime.storage.projects.getActiveProject(); - if (activeProject) { - safeSettings.project = activeProject; - } else if (runtime.storage.projects.flowFileExists()) { - safeSettings.files = { - flow: runtime.storage.projects.getFlowFilename(), - credentials: runtime.storage.projects.getCredentialsFilename() + if (!runtime.nodes.paletteEditorEnabled()) { + safeSettings.editorTheme = safeSettings.editorTheme || {}; + safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {}; + safeSettings.editorTheme.palette.editable = false; + } + if (runtime.storage.projects) { + var activeProject = runtime.storage.projects.getActiveProject(); + if (activeProject) { + safeSettings.project = activeProject; + } else if (runtime.storage.projects.flowFileExists()) { + safeSettings.files = { + flow: runtime.storage.projects.getFlowFilename(), + credentials: runtime.storage.projects.getCredentialsFilename() + } + } + safeSettings.git = { + globalUser: runtime.storage.projects.getGlobalGitUser() } } - safeSettings.git = { - globalUser: runtime.storage.projects.getGlobalGitUser() - } + + safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); + runtime.settings.exportNodeSettings(safeSettings); } - safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType(); - - runtime.settings.exportNodeSettings(safeSettings); resolve(safeSettings); }catch(err) { diff --git a/test/unit/@node-red/editor-api/lib/admin/index_spec.js b/test/unit/@node-red/editor-api/lib/admin/index_spec.js index ead5f274a..4f9f2d55a 100644 --- a/test/unit/@node-red/editor-api/lib/admin/index_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/index_spec.js @@ -102,7 +102,7 @@ describe("api/admin/index", function() { }); before(function() { - app = adminApi.init({}); + app = adminApi.init({},{}); }); beforeEach(function() { diff --git a/test/unit/@node-red/editor-api/lib/admin/settings_spec.js b/test/unit/@node-red/editor-api/lib/admin/settings_spec.js new file mode 100644 index 000000000..d0c4cbe94 --- /dev/null +++ b/test/unit/@node-red/editor-api/lib/admin/settings_spec.js @@ -0,0 +1,93 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var should = require("should"); +var request = require('supertest'); +var express = require('express'); +var bodyParser = require("body-parser"); +var sinon = require('sinon'); + +var app; + +var NR_TEST_UTILS = require("nr-test-utils"); + +var info = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/settings"); +var theme = NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"); + +describe("api/editor/settings", function() { + before(function() { + sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); + app = express(); + app.use(bodyParser.json()); + app.get("/settings",info.runtimeSettings); + }); + + after(function() { + theme.settings.restore(); + }); + + it('returns the runtime settings', function(done) { + info.init({},{ + settings: { + getRuntimeSettings: function(opts) { + return Promise.resolve({ + a:1, + b:2, + editorTheme: { existing: 789 } + }) + } + } + }); + request(app) + .get("/settings") + .expect(200) + .end(function(err,res) { + if (err) { + return done(err); + } + res.body.should.have.property("a",1); + res.body.should.have.property("b",2); + res.body.should.have.property("editorTheme",{existing: 789, test:456}); + done(); + }); + }); + it('returns the runtime settings - disableEditor true', function(done) { + info.init({disableEditor: true},{ + settings: { + getRuntimeSettings: function(opts) { + return Promise.resolve({ + a:1, + b:2 + }) + } + } + }); + request(app) + .get("/settings") + .expect(200) + .end(function(err,res) { + if (err) { + return done(err); + } + res.body.should.have.property("a",1); + res.body.should.have.property("b",2); + // no editorTheme if disabledEditor true + res.body.should.not.have.property("editorTheme"); + done(); + }); + }); + +}); diff --git a/test/unit/@node-red/editor-api/lib/editor/settings_spec.js b/test/unit/@node-red/editor-api/lib/editor/settings_spec.js index 8fcdbeba2..f8c690751 100644 --- a/test/unit/@node-red/editor-api/lib/editor/settings_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/settings_spec.js @@ -32,7 +32,6 @@ describe("api/editor/settings", function() { sinon.stub(theme,"settings",function() { return { existing: 123, test: 456 };}); app = express(); app.use(bodyParser.json()); - app.get("/settings",info.runtimeSettings); app.get("/settings/user",function(req,res,next) {req.user = "fred"; next()}, info.userSettings); app.post("/settings/user",function(req,res,next) {req.user = "fred"; next()},info.updateUserSettings); }); @@ -41,31 +40,6 @@ describe("api/editor/settings", function() { theme.settings.restore(); }); - it('returns the runtime settings', function(done) { - info.init({ - settings: { - getRuntimeSettings: function(opts) { - return Promise.resolve({ - a:1, - b:2, - editorTheme: { existing: 789 } - }) - } - } - }); - request(app) - .get("/settings") - .expect(200) - .end(function(err,res) { - if (err) { - return done(err); - } - res.body.should.have.property("a",1); - res.body.should.have.property("b",2); - res.body.should.have.property("editorTheme",{existing: 789, test:456}); - done(); - }); - }); it('returns the user settings', function(done) { info.init({ settings: { diff --git a/test/unit/@node-red/runtime/lib/api/settings_spec.js b/test/unit/@node-red/runtime/lib/api/settings_spec.js index dbc5567da..3830e9ab2 100644 --- a/test/unit/@node-red/runtime/lib/api/settings_spec.js +++ b/test/unit/@node-red/runtime/lib/api/settings_spec.js @@ -101,7 +101,58 @@ describe("runtime-api/settings", function() { result.user.should.not.have.property("private"); }) }); + it("gets the filtered settings when editor disabled ", function() { + settings.init({ + settings: { + disableEditor: true, + foo: 123, + httpNodeRoot: "testHttpNodeRoot", + version: "testVersion", + paletteCategories :["red","blue","green"], + exportNodeSettings: (obj) => { + obj.testNodeSetting = "helloWorld"; + } + }, + nodes: { + listContextStores: () => { return {stores:["file","memory"], default: "file"} }, + paletteEditorEnabled: () => false, + getCredentialKeyType: () => "test-key-type" + }, + storage: { + projects: { + getActiveProject: () => 'test-active-project', + getFlowFilename: () => 'test-flow-file', + getCredentialsFilename: () => 'test-creds-file', + getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} + } + } + }) + return settings.getRuntimeSettings({ + user: { + username: "nick", + anonymous: false, + image: "http://example.com", + permissions: "*", + private: "secret" + } + }).then(result => { + result.should.have.property("user"); + result.user.should.have.property("username","nick"); + result.user.should.have.property("permissions","*"); + result.user.should.have.property("image","http://example.com"); + result.user.should.have.property("anonymous",false); + result.user.should.not.have.property("private"); + // Filtered out when disableEditor is true + result.should.not.have.property("paletteCategories",["red","blue","green"]); + result.should.not.have.property("testNodeSetting","helloWorld"); + result.should.not.have.property("foo",123); + result.should.not.have.property("flowEncryptionType","test-key-type"); + result.should.not.have.property("project"); + result.should.not.have.property("git"); + + }) + }); it('includes project settings if projects available', function() { settings.init({ settings: { From 758f44e25f6caf874015b6d81eda81ca01aa4660 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 31 Jul 2020 23:22:33 +0100 Subject: [PATCH 15/28] Improve performance of moving groups --- .../@node-red/editor-client/src/js/ui/view.js | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 27033fceb..2d6d5bf41 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1339,6 +1339,7 @@ RED.view = (function() { node.n.y = mousePos[1]+node.dy; node.n.dirty = true; if (node.n.type === "group") { + node.n.groupMoved = true; RED.group.markDirty(node.n); minX = Math.min(node.n.x-5,minX); minY = Math.min(node.n.y-5,minY); @@ -1711,6 +1712,7 @@ RED.view = (function() { if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) { return; } + clearSelection(); exitActiveGroup(); activeGroups.forEach(function(g) { if (!g.g) { @@ -2912,7 +2914,7 @@ RED.view = (function() { } else { dblClickPrimed = false; } - selectGroup(nodeGroup, !activeGroup); + selectGroup(nodeGroup, !activeGroup, !!groupNodeSelectPrimed); if (activeGroup) { mousedown_node.selected = true; moving_set.push({n:mousedown_node}); @@ -4301,7 +4303,9 @@ RED.view = (function() { }) } group[0].reverse(); + var groupOpCount=0; group.each(function(d,i) { + groupOpCount++ if (d.resize) { d.minWidth = 0; delete d.resize; @@ -4309,29 +4313,36 @@ RED.view = (function() { if (d.dirty || dirtyGroups[d.id]) { var g = d3.select(this); if (d.nodes.length > 0) { - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = 0; - var maxY = 0; - var margin = 26; - d.nodes.forEach(function(n) { - if (n.type !== "group") { - minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-margin); - maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+margin); - } else { - minX = Math.min(minX,n.x-margin) - minY = Math.min(minY,n.y-margin) - maxX = Math.max(maxX,n.x+n.w+margin) - maxY = Math.max(maxY,n.y+n.h+margin) - } - }); + // If the group was just moved, all of its contents was + // also moved - so no need to recalculate its bounding box + if (!d.groupMoved) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + var margin = 26; + d.nodes.forEach(function(n) { + groupOpCount++ + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-margin); + maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+margin); + } else { + minX = Math.min(minX,n.x-margin) + minY = Math.min(minY,n.y-margin) + maxX = Math.max(maxX,n.x+n.w+margin) + maxY = Math.max(maxY,n.y+n.h+margin) + } + }); - d.x = minX; - d.y = minY; - d.w = maxX - minX; - d.h = maxY - minY; + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + } else { + delete d.groupMoved; + } } else { d.w = 40; d.h = 40; From 29142128f21f620370e2ae59b10a32b1db8bffd0 Mon Sep 17 00:00:00 2001 From: Sebastian Raff Date: Sun, 2 Aug 2020 19:34:10 +0200 Subject: [PATCH 16/28] german translation, wording (#2660) (#2666) to also close #2660 --- packages/node_modules/@node-red/nodes/locales/de/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index 58fce3a92..7d9bd3523 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -604,7 +604,7 @@ "inputrange" : "von einem Eingabebereich", "resultrange" : "in einen Zielbereich", "from" : "von", - "to" : "bis", + "to" : "auf", "roundresult" : "Runde das Ergebnis auf die nächste ganze Zahl?" }, "placeholder" : { From e691b1b7c32e5c1524f12799bb8588ce3252744c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 3 Aug 2020 16:55:36 +0100 Subject: [PATCH 17/28] Add additional check for git auth failure response Fixes #2656 --- .../runtime/lib/storage/localfilesystem/projects/git/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index d812c05f2..dbcb5fb61 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -51,6 +51,8 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_auth_failed"; } else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_auth_failed"; + } else if(/Authentication failed/i.test(stderr)) { + err.code = "git_auth_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) { From 80d65b5acb99285ec44506e2cefd5de074813d12 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 4 Aug 2020 20:59:32 +0100 Subject: [PATCH 18/28] Add Set(iterable) polyfill for IE11 --- .../@node-red/editor-client/src/js/polyfills.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/polyfills.js b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js index 626b53599..8d346b55b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/polyfills.js +++ b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js @@ -37,5 +37,20 @@ } return result; } + + if (new Set([0]).size === 0) { + // IE does not support passing an iterable to Set constructor + var _Set = Set; + Set = function Set(iterable) { + var set = new _Set(); + if (iterable) { + iterable.forEach(set.add, set); + } + return set; + }; + Set.prototype = _Set.prototype; + Set.prototype.constructor = Set; + } + } })(); From d590bbdd2c95031702cd27fce8cae6b19b9b3c83 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 4 Aug 2020 20:59:51 +0100 Subject: [PATCH 19/28] Fix copy/paste of nested groups --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 1ea08193e..84499ffe6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -998,6 +998,7 @@ RED.nodes = (function() { var new_nodes = []; var new_links = []; var new_groups = []; + var new_group_set = new Set(); var nid; var def; var configNode; @@ -1326,6 +1327,7 @@ RED.nodes = (function() { new_nodes.push(node); } else if (node.type === "group") { new_groups.push(node); + new_group_set.add(node.id); } } } @@ -1433,9 +1435,8 @@ RED.nodes = (function() { var groupDepthMap = {}; for (i=0;i Date: Tue, 4 Aug 2020 21:01:08 +0100 Subject: [PATCH 20/28] Support select-all inside active group --- .../@node-red/editor-client/src/js/ui/view.js | 426 +++++++++++------- 1 file changed, 254 insertions(+), 172 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 2d6d5bf41..81a5eed10 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -74,7 +74,6 @@ RED.view = (function() { var mouse_position = null; var mouse_mode = 0; var mousedown_group_handle = null; - var moving_set = []; var lasso = null; var ghostNode = null; var showStatus = false; @@ -116,6 +115,49 @@ RED.view = (function() { var groupLayer; var drag_lines; + var movingSet = (function() { + var setIds = new Set(); + var set = []; + var api = { + add: function(node) { + if (Array.isArray(node)) { + for (var i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && @@ -1329,8 +1371,8 @@ RED.view = (function() { var minY = 0; var maxX = space_width; var maxY = space_height; - for (var n = 0; n 0) { + if (snapGrid != d3.event.shiftKey && movingSet.length() > 0) { var i = 0; // Prefer to snap nodes to the grid if there is one in the selection do { - node = moving_set[i++]; - } while(i x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } }); activeSubflow.out.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } }); if (activeSubflow.status) { activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); if (activeSubflow.status.selected) { activeSubflow.status.dirty = true; - moving_set.push({n:activeSubflow.status}); + movingSet.add(activeSubflow.status); } } } @@ -1594,11 +1638,11 @@ RED.view = (function() { updateSelection(); } if (mouse_mode == RED.state.MOVING_ACTIVE) { - if (moving_set.length > 0) { + if (movingSet.length() > 0) { var addedToGroup = null; if (activeHoverGroup) { - for (var j=0;j 0) { - var node = moving_set[0].n; + if (movingSet.length() > 0) { + var node = movingSet.get(0).n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); } else if (node.type === "group") { @@ -1955,7 +2020,7 @@ RED.view = (function() { updateActiveNodes(); updateSelection(); redraw(); - } else if (moving_set.length > 0 || selected_link != null) { + } else if (movingSet.length() > 0 || selected_link != null) { var result; var node; var removedNodes = []; @@ -1969,10 +2034,10 @@ RED.view = (function() { var startDirty = RED.nodes.dirty(); var startChanged = false; var selectedGroups = []; - if (moving_set.length > 0) { + if (movingSet.length() > 0) { - for (var i=0;i 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) { RED.nodes.dirty(true); } @@ -2700,12 +2765,13 @@ RED.view = (function() { function prepareDrag(mouse) { mouse_mode = RED.state.MOVING; - // Called when moving_set should be prepared to be dragged - for (i=0;i= 0; i -= 1) { - if (moving_set[i].n === group) { - moving_set.splice(i,1); - break; - } - } + movingSet.remove(group); } function exitActiveGroup() { if (activeGroup) { @@ -3263,11 +3333,12 @@ RED.view = (function() { } var nodeSet = new Set(g.nodes); nodeSet.add(g); - for (var i = moving_set.length-1; i >= 0; i -= 1) { - if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) { - moving_set[i].n.selected = false; - moving_set[i].n.dirty = true; - moving_set.splice(i,1); + for (var i = movingSet.length()-1; i >= 0; i -= 1) { + var msn = movingSet.get(i); + if (nodeSet.has(msn.n) || msn.n === g) { + msn.n.selected = false; + msn.n.dirty = true; + movingSet.remove(msn.n,i) } } } @@ -3351,11 +3422,11 @@ RED.view = (function() { function showTouchMenu(obj,pos) { var mdn = mousedown_node; var options = []; - options.push({name:"delete",disabled:(moving_set.length===0 && selected_link === null),onselect:function() {deleteSelection();}}); - options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}}); - options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}}); + options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}}); + options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,false,true);}}); - options.push({name:"edit",disabled:(moving_set.length != 1),onselect:function() { RED.editor.edit(mdn);}}); + options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); options.push({name:"select",onselect:function() {selectAll();}}); options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); options.push({name:"add",onselect:function() { @@ -4173,7 +4244,7 @@ RED.view = (function() { targets.forEach(function(n) { n.selected = true; n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); }); updateSelection(); redraw(); @@ -4340,6 +4411,13 @@ RED.view = (function() { d.y = minY; d.w = maxX - minX; d.h = maxY - minY; + // if set explicitly to false, this group has just been + // imported so needed this initial resize calculation. + // Now that's done, delete the flag so the normal + // logic kicks in. + if (d.groupMoved === false) { + delete d.groupMoved; + } } else { delete d.groupMoved; } @@ -4388,12 +4466,12 @@ RED.view = (function() { var selectGroupRect = selectGroup.children[0]; selectGroupRect.setAttribute("width",d.w+8) selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; selectGroupRect = selectGroup.children[1]; selectGroupRect.setAttribute("width",d.w+8) selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; if (d.highlighted) { @@ -4517,21 +4595,25 @@ RED.view = (function() { if (addNewFlow && new_default_workspace) { RED.workspaces.show(new_default_workspace.id); } - var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); - new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}).map(function(g) { return {n:g}})) + var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); + new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); + + clearSelection(); + movingSet.clear(); + movingSet.add(new_ms); + + // TODO: pick a more sensible root node - if (new_ms.length > 0) { - - + if (movingSet.length() > 0) { if (mouse_position == null) { mouse_position = [0,0]; } var dx = mouse_position[0]; var dy = mouse_position[1]; - if (new_ms.length > 0) { - var root_node = new_ms[0].n; + if (movingSet.length() > 0) { + var root_node = movingSet.get(0).n; dx = root_node.x; dy = root_node.y; } @@ -4540,9 +4622,9 @@ RED.view = (function() { var minY = 0; var i; var node,group; - - for (i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) @@ -4594,8 +4677,6 @@ RED.view = (function() { RED.history.pop(); mouse_mode = 0; }); - clearSelection(); - moving_set = new_ms; } var historyEvent = { @@ -4607,7 +4688,7 @@ RED.view = (function() { subflows:new_subflows, dirty:RED.nodes.dirty() }; - if (new_ms.length === 0) { + if (movingSet.length() === 0) { RED.nodes.dirty(true); } if (activeSubflow) { @@ -4692,10 +4773,10 @@ RED.view = (function() { var changed = false; if (workspaceSelection.length > 0) { // TODO: toggle workspace state - } else if (moving_set.length > 0) { + } else if (movingSet.length() > 0) { var historyEvents = []; - for (var i=0;i 0) { - moving_set.forEach(function(n) { + if (movingSet.length() > 0) { + movingSet.forEach(function(n) { if (n.n.type !== 'group') { allNodes.add(n.n); } @@ -4796,12 +4877,13 @@ RED.view = (function() { if (selectedNode) { selectedNode.selected = true; selectedNode.dirty = true; - moving_set = [{n:selectedNode}]; + movingSet.clear(); + movingSet.add(selectedNode); } } else if (selection) { if (selection.nodes) { updateActiveNodes(); - moving_set = []; + movingSet.clear(); // TODO: this selection group span groups // - if all in one group -> activate the group // - if in multiple groups (or group/no-group) @@ -4810,7 +4892,7 @@ RED.view = (function() { if (n.type !== "group") { n.selected = true; n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } else { selectGroup(n,true); } @@ -4917,7 +4999,7 @@ RED.view = (function() { if (n) { n.selected = true; n.dirty = true; - moving_set.push({n:n}); + movingSet.add(n); } }) } @@ -4953,7 +5035,7 @@ RED.view = (function() { text: RED._("common.label.done"), class: "primary", click: function(e) { - var selection = moving_set.map(function(n) { return n.n;}); + var selection = movingSet.nodes() selectNodesOptions.done(selection); } }); From 1aa494a97a359d09ff9bc8fb0990b71c56afc4a0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Aug 2020 10:38:14 +0100 Subject: [PATCH 21/28] Make ctrl-click on nexted group more intuitive --- .../@node-red/editor-client/src/js/ui/view.js | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 81a5eed10..faa7ea810 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -2928,15 +2928,26 @@ RED.view = (function() { var nodeGroup = RED.nodes.group(d.g); if (nodeGroup !== activeGroup && (d3.event.ctrlKey || d3.event.metaKey)) { - // Clicked on a node in a non-active group with ctrl pressed - // - exit active group - // - toggle the select state of the group - exitActiveGroup(); - groupNodeSelectPrimed = true; - if (nodeGroup.selected) { - deselectGroup(nodeGroup); + if (activeGroup && nodeGroup.g === activeGroup.id) { + // Clicked on a node in a non-active group, inside the activeGroup, with ctrl pressed + // - add/remove the group from the current selection + groupNodeSelectPrimed = true; + if (nodeGroup.selected) { + deselectGroup(nodeGroup); + } else { + selectGroup(nodeGroup,true); + } } else { - selectGroup(nodeGroup,true); + // Clicked on a node in a non-active group with ctrl pressed + // - exit active group + // - toggle the select state of the group + exitActiveGroup(); + groupNodeSelectPrimed = true; + if (nodeGroup.selected) { + deselectGroup(nodeGroup); + } else { + selectGroup(nodeGroup,true); + } } } else if (nodeGroup === activeGroup ) { if (d3.event.shiftKey) { @@ -2958,9 +2969,14 @@ RED.view = (function() { // Clicked on a node in the active group if (!d3.event.ctrlKey && !d3.event.metaKey) { // Ctrl not pressed so clear selection + var ag = activeGroup; clearSelection(); deselectGroup(nodeGroup); selectGroup(nodeGroup,false,false); + if (ag) { + enterActiveGroup(ag); + activeGroup.selected = true; + } } // Select this node @@ -2984,18 +3000,18 @@ RED.view = (function() { clearSelection(); } if (ag) { - if (ag !== nodeGroup) { + if (ag !== nodeGroup && ag.id !== nodeGroup.g) { ag.active = false; ag.dirty = true; } else { - activeGroup = nodeGroup; + activeGroup = ag; activeGroup.active = true; } } else { dblClickPrimed = false; } - selectGroup(nodeGroup, !activeGroup, !!groupNodeSelectPrimed); - if (activeGroup) { + selectGroup(nodeGroup, !(activeGroup && activeGroup === nodeGroup), !!groupNodeSelectPrimed); + if (activeGroup && activeGroup === nodeGroup) { mousedown_node.selected = true; movingSet.add(mousedown_node); } @@ -3260,7 +3276,12 @@ RED.view = (function() { } else { if (!g.selected) { if (!d3.event.ctrlKey && !d3.event.metaKey) { + var ag = activeGroup; clearSelection(); + if (ag && g.g === ag.id) { + enterActiveGroup(ag); + activeGroup.selected = true; + } } if (activeGroup) { if (!RED.group.contains(activeGroup,g)) { @@ -3270,7 +3291,7 @@ RED.view = (function() { } } selectGroup(g,true);//!wasSelected); - } else { + } else if (activeGroup && g.g !== activeGroup.id){ exitActiveGroup(); } From 85edee288f2a06a59b0598108e0e46b1db906cbe Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Aug 2020 11:16:53 +0100 Subject: [PATCH 22/28] Allow lasso selection to be restricted to active group --- .../@node-red/editor-client/src/js/ui/view.js | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index faa7ea810..d488ed201 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1558,17 +1558,28 @@ RED.view = (function() { var y = parseInt(lasso.attr("y")); var x2 = x+parseInt(lasso.attr("width")); var y2 = y+parseInt(lasso.attr("height")); + var ag = activeGroup; if (!d3.event.shiftKey) { clearSelection(); + if (ag) { + if (x < ag.x+ag.w && x2 > ag.x && y < ag.y+ag.h && y2 > ag.y) { + // There was an active group and the lasso intersects with it, + // so reenter the group + enterActiveGroup(ag); + activeGroup.selected = true; + } + } } activeGroups.forEach(function(g) { if (!g.selected) { if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { - while (g.g) { - g = RED.nodes.group(g.g); - } - if (!g.selected) { - selectGroup(g,true); + if (!activeGroup || RED.group.contains(activeGroup,g)) { + while (g.g && (!activeGroup || g.g !== activeGroup.id)) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } } } } @@ -1577,18 +1588,21 @@ RED.view = (function() { activeNodes.forEach(function(n) { if (!n.selected) { if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { - if (n.g) { - var group = RED.nodes.group(n.g); - while (group.g) { - group = RED.nodes.group(group.g); + if (!activeGroup || RED.group.contains(activeGroup,n)) { + if (n.g && (!activeGroup || n.g !== activeGroup.id)) { + console.log("HERE") + var group = RED.nodes.group(n.g); + while (group.g && (!activeGroup || group.g !== activeGroup.id)) { + group = RED.nodes.group(group.g); + } + if (!group.selected) { + selectGroup(group,true); + } + } else { + n.selected = true; + n.dirty = true; + movingSet.add(n); } - if (!group.selected) { - selectGroup(group,true); - } - } else { - n.selected = true; - n.dirty = true; - movingSet.add(n); } } } From 3fb83c46e2c384d52a6eb0b9d8b67b6743d69c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABl=20L=C3=A9caud=C3=A9?= Date: Fri, 10 Jul 2020 13:08:40 -0400 Subject: [PATCH 23/28] Update 10-switch.html Clarify switch help regarding booleans for the is empty / is not empty rules. --- .../@node-red/nodes/locales/en-US/function/10-switch.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html index b5b825c20..7f4a1a85b 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html @@ -38,8 +38,8 @@

Notes

The is true/false and is null rules perform strict comparisons against those types. They do not convert between types.

-

The is empty rule passes for Strings, Arrays and Buffers that have - a length of 0, or Objects that have no properties. It does not pass for null +

The is empty and is not empty rules passes for Strings, Arrays and Buffers that have + a length of 0, or Objects that have no properties. It does not pass for boolean, null or undefined values.

Handling message sequences

By default, the node does not modify the msg.parts property of messages From 7e11ff2b200fefeccf00f6a43858daca65b420a0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 3 Aug 2020 16:58:09 +0100 Subject: [PATCH 24/28] Update packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html --- .../@node-red/nodes/locales/en-US/function/10-switch.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html index 7f4a1a85b..6f1cee1b1 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html @@ -38,9 +38,8 @@

Notes

The is true/false and is null rules perform strict comparisons against those types. They do not convert between types.

-

The is empty and is not empty rules passes for Strings, Arrays and Buffers that have - a length of 0, or Objects that have no properties. It does not pass for boolean, null - or undefined values.

+

The is empty and is not empty rules can be used to test the length of Strings, Arrays and Buffers, or the number of properties an Object has. Neither rule will pass if the property being tested has a boolean, null + or undefined value.

Handling message sequences

By default, the node does not modify the msg.parts property of messages that are part of a sequence.

From c50ed2c328238c71636ee620ea98b9c64998675b Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 4 Aug 2020 21:41:23 +0900 Subject: [PATCH 25/28] Add Japanese translation for empty rules in switch node --- .../@node-red/nodes/locales/ja/function/10-switch.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/ja/function/10-switch.html index af0ccde6d..852494b90 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/10-switch.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/10-switch.html @@ -28,11 +28,9 @@
  • JSONata式 - メッセージ全体に対して評価を行い、結果が真の場合にマッチ
  • その他 - これより前のルールにマッチするものがなかった場合に適用
  • -

    注釈

    is true/falseis nullのルールは、型に対して厳密な比較を行います。型変換した上での比較はしません。

    -

    is emptyのルールは、長さ0の文字列・配列・バッファ、またはプロパティを持たないオブジェクトを出力します。nullundefinedは出力しません。

    - +

    is emptyis not emptyのルールは、文字列・配列・バッファの長さや、オブジェクトが持つプロパティの数をテストするために用いられます。どちらのルールも、テストされるプロパティがbooleannullundefinedの値を持つ場合、通過しません。

    メッセージ列の扱い

    switchノードは入力メッセージの列に関する情報を保持するmsg.partsをデフォルトでは変更しません。

    メッセージ列の補正」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchノードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。settings.jsnodeMessageBufferMaxLengthを設定すると、蓄積するメッセージ数を制限できます。

    From fba505bc90803a6ac95c8000945a4689c4e11afd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Aug 2020 13:53:49 +0100 Subject: [PATCH 26/28] Fix vertical align of fa node icons Fixes #2670 --- .../node_modules/@node-red/editor-client/src/sass/palette.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 922a31e33..7a2b53eb2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -204,7 +204,7 @@ .red-ui-palette-icon-fa { color: white; position: absolute; - top: 7px; + top: calc(50% - 7px); left: 3px; } .red-ui-palette-node-small { From d28c26442266b7656025c2faa3504d90156b2afe Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Aug 2020 14:58:43 +0100 Subject: [PATCH 27/28] Fix jshint error on polyfill --- .../node_modules/@node-red/editor-client/src/js/polyfills.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/polyfills.js b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js index 8d346b55b..0e7206f7a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/polyfills.js +++ b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js @@ -41,6 +41,7 @@ if (new Set([0]).size === 0) { // IE does not support passing an iterable to Set constructor var _Set = Set; + /*global Set:true */ Set = function Set(iterable) { var set = new _Set(); if (iterable) { From ec368ae3fd24dcb688f2918e25bb04d62fafc798 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Aug 2020 14:59:25 +0100 Subject: [PATCH 28/28] Bump for 1.1.3 --- CHANGELOG.md | 24 +++++++++++++++++++ package.json | 2 +- .../@node-red/editor-api/package.json | 6 ++--- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 2 +- .../@node-red/registry/package.json | 4 ++-- .../@node-red/runtime/package.json | 6 ++--- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 ++++---- 9 files changed, 41 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4589b87..0d4baaf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +### 1.1.3: Maintenance Release + +Editor + - Fix vertical align of fa node icons Fixes #2670 + - Allow lasso selection to be restricted to active group + - Make ctrl-click on nested group more intuitive + - Fix copy/paste of nested groups + - Add Set(iterable) polyfill for IE11 + - Support select-all inside active group + - Improve performance of moving groups + - Add additional check for git auth failure response Fixes #2656 + - german translation, wording (#2660) (#2666) + - Remove filtering of duplicate fa icons + - Show node help when switching node edit dialogs Fixes #2652 + - Ensure group theme picks up theme defaults properly Fixes #2651 + +Nodes + - Clarify Switch node isEmpty help + - HTTP In: handle application/cbor as binary + +Runtime + - Move runtime settings back to adminApi from editorApi Fixes #2662 + - Update Chinese message for debug node + ### 1.1.2: Maintenance Release Editor diff --git a/package.json b/package.json index ba6f7b6f2..b215610dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.1.2", + "version": "1.1.3", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 3971a65eb..41995f87d 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.1.2", + "version": "1.1.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "1.1.2", - "@node-red/editor-client": "1.1.2", + "@node-red/util": "1.1.3", + "@node-red/editor-client": "1.1.3", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index eebd07b6a..bba9bcf43 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.1.2", + "version": "1.1.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 2150ef0cd..74506269e 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.1.2", + "version": "1.1.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index a3c37c231..6928c9b82 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.1.2", + "version": "1.1.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "1.1.2", + "@node-red/util": "1.1.3", "semver": "6.3.0", "uglify-js": "3.10.0", "when": "3.7.8" diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 9b808a2aa..8346bbdef 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.1.2", + "version": "1.1.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.1.2", - "@node-red/util": "1.1.2", + "@node-red/registry": "1.1.3", + "@node-red/util": "1.1.3", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 9bedb0058..0d3a99a01 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.1.2", + "version": "1.1.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 7020c9b99..4eaf5fc36 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.1.2", + "version": "1.1.3", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.1.2", - "@node-red/runtime": "1.1.2", - "@node-red/util": "1.1.2", - "@node-red/nodes": "1.1.2", + "@node-red/editor-api": "1.1.3", + "@node-red/runtime": "1.1.3", + "@node-red/util": "1.1.3", + "@node-red/nodes": "1.1.3", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1",