From ad788fbed1cb942ea0d93f586adfefe9e860a543 Mon Sep 17 00:00:00 2001
From: Kristian Heljas <kristian@kristian.ee>
Date: Thu, 8 Apr 2021 21:09:44 +0300
Subject: [PATCH 01/48] Function node: describe `node.outputCount` in help text

---
 .../@node-red/nodes/locales/en-US/function/10-function.html      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
index 891801bf9..02e85922e 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
@@ -56,6 +56,7 @@
     <ul>
         <li><code>node.id</code> - id of the node</li>
         <li><code>node.name</code> - name of the node</li>
+        <li><code>node.outputCount</code> - number of node outputs</li>
     </ul>
     <h4>Using environment variables</h4>
     <p>Environment variables can be accessed using <code>env.get("MY_ENV_VAR")</code>.</p>

From 6087002562dc4ebdae08fa779808282ba63b1085 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Sat, 10 Apr 2021 21:34:26 +0100
Subject: [PATCH 02/48] Fix handling of user-provided keymap Fixes #2926

---
 .../node_modules/@node-red/editor-client/src/js/ui/keyboard.js  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 2c81cfe63..9e976e4fc 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
@@ -119,7 +119,7 @@ RED.keyboard = (function() {
                 } else {
                     mergedKeymap[action] = [{
                         scope: themeKeymap[action].scope || "*",
-                        key: [themeKeymap[action].key],
+                        key: themeKeymap[action].key,
                         user: false
                     }]
                     if (mergedKeymap[action][0].scope === "workspace") {

From 858b3d640adad3390ca49777197b8e3380b1fb8d Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Sat, 10 Apr 2021 22:17:31 +0100
Subject: [PATCH 03/48] fix CSV parsing with other than , separator

(and joining as well...
and add tests
to close #2925
---
 .../@node-red/nodes/core/parsers/70-CSV.js    | 11 +++---
 test/nodes/core/parsers/70-CSV_spec.js        | 36 +++++++++++++++++++
 2 files changed, 42 insertions(+), 5 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
index c5be65020..95a4eb2b2 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
@@ -38,10 +38,11 @@ module.exports = function(RED) {
         if (this.hdrout === true) { this.hdrout = "all"; }
         var tmpwarn = true;
         var node = this;
-        var re = new RegExp(',(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
+        var re = new RegExp(node.sep+'(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
 
-        // pass in an array of column names to be trimed, de-quoted and retrimed
-        var clean = function(col) {
+        // pass in an array of column names to be trimmed, de-quoted and retrimmed
+        var clean = function(col,sep) {
+            if (sep) { re = new RegExp(sep+'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
             col = col.trim().split(re) || [""];
             col = col.map(x => x.replace(/"/g,'').trim());
             if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
@@ -67,7 +68,7 @@ module.exports = function(RED) {
                         if (node.hdrout !== "none" && node.hdrSent === false) {
                             if ((template.length === 1) && (template[0] === '')) {
                                 if (msg.hasOwnProperty("columns")) {
-                                    template = clean(msg.columns || "");
+                                    template = clean(msg.columns || "",",");
                                 }
                                 else {
                                     template = Object.keys(msg.payload[0]);
@@ -93,7 +94,7 @@ module.exports = function(RED) {
                             }
                             else {
                                 if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
-                                    template = clean(msg.columns || "");
+                                    template = clean(msg.columns || "",",");
                                 }
                                 if ((template.length === 1) && (template[0] === '')) {
                                     /* istanbul ignore else */
diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index fa73380ae..cccf7bf1c 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -170,6 +170,24 @@ describe('CSV node', function() {
                 n1.emit("input", {payload:testString});
             });
         });
+
+        it('should allow passing in a template as first line of CSV (not comma)', function(done) {
+            var flow = [ { id:"n1", type:"csv", temp:"", hdrin:true, sep:";", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    msg.should.have.property('payload', { a: 1, "b b":2, "c;c":3, "d, d": 4 });
+                    msg.should.have.property('columns', 'a,b b,c;c,"d, d"');
+                    check_parts(msg, 0, 1);
+                    done();
+                });
+                var testString = 'a;b b;"c;c";" d, d "'+"\n"+"1;2;3;4"+String.fromCharCode(10);
+                n1.emit("input", {payload:testString});
+            });
+        });
+
         it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) {
             var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];
@@ -609,6 +627,24 @@ describe('CSV node', function() {
             });
         });
 
+        it('should convert a simple object back to a tsv using a tab as a separator', function(done) {
+            var flow = [ { id:"n1", type:"csv", temp:"", sep:"\t", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    try {
+                        msg.should.have.property('payload', '1\tfoo\t"ba""r"\tdi,ng\n');
+                        done();
+                    }
+                    catch(e) { done(e); }
+                });
+                var testJson = { d:1, b:"foo", c:"ba\"r", a:"di,ng" };
+                n1.emit("input", {payload:testJson});
+            });
+        });
+
         it('should handle a template with spaces in the property names', function(done) {
             var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];

From 4672d98e8a13d8ff2f695c8cc76ec2092fff4a9a Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Mon, 12 Apr 2021 09:47:18 +0100
Subject: [PATCH 04/48] split node - add comment to info re $N being number of
 messages arriving

---
 .../@node-red/nodes/locales/en-US/sequence/17-split.html       | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html
index b0059992f..3334ec126 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html
@@ -91,7 +91,7 @@
         </ul>
         </dd>
         <dt class="optional">complete</dt>
-        <dd>If set, the node will append the payload, and then send the output message in its current state. 
+        <dd>If set, the node will append the payload, and then send the output message in its current state.
             If you don't wish to append the payload, delete it from the msg.</dd>
     </dl>
     <h3>Details</h3>
@@ -150,6 +150,7 @@
         <p>By default, the reduce expression is applied in order, from the first
            to the last message of the sequence. It can optionally be applied in
            reverse order.</p>
+        <p>$N is the number of messages that arrive - even if they are identical.</p>
     </dl>
     <p><b>Example:</b> the following settings, given a sequence of numeric values,
        calculates the average value:

From 13406e76de045117afad9beae1d203b953b44f3d Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 12 Apr 2021 10:05:44 +0100
Subject: [PATCH 05/48] Ensure theme login image is passed through to api
 response Fixes #2929

---
 .../node_modules/@node-red/editor-api/lib/auth/index.js    | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js
index d4ec10f08..fb95ede7b 100644
--- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js
+++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js
@@ -90,7 +90,7 @@ function getToken(req,res,next) {
     return server.token()(req,res,next);
 }
 
-function login(req,res) {
+async function login(req,res) {
     var response = {};
     if (settings.adminAuth) {
         var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
@@ -116,8 +116,9 @@ function login(req,res) {
                 response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
             }
         }
-        if (theme.context().login && theme.context().login.image) {
-            response.image = theme.context().login.image;
+        let themeContext = await theme.context();
+        if (themeContext.login && themeContext.login.image) {
+            response.image = themeContext.login.image;
         }
     }
     res.json(response);

From 51aaf1b1503c50d111bc8d3080a888c834ce49f2 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 12 Apr 2021 10:34:43 +0100
Subject: [PATCH 06/48] Handle package.json without dependencies section

---
 packages/node_modules/@node-red/registry/lib/localfilesystem.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
index 9e08c4c53..62080b4a8 100644
--- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js
+++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
@@ -472,7 +472,7 @@ function getPackageList() {
         try {
             var userPackage = path.join(settings.userDir,"package.json");
             var pkg = JSON.parse(fs.readFileSync(userPackage,"utf-8"));
-            return pkg.dependencies;
+            return pkg.dependencies || {};
         } catch(err) {
             log.error(err);
         }

From 5028377d45a7cb6f7e3998782eef905e499eefcf Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 12 Apr 2021 11:48:10 +0100
Subject: [PATCH 07/48] Fix MQTT Broker TLS config row layout Fixes #2927

---
 .../node_modules/@node-red/nodes/core/network/10-mqtt.html    | 4 ++--
 .../node_modules/@node-red/nodes/locales/de/messages.json     | 2 +-
 .../node_modules/@node-red/nodes/locales/ko/messages.json     | 2 +-
 .../node_modules/@node-red/nodes/locales/ru/messages.json     | 2 +-
 .../node_modules/@node-red/nodes/locales/zh-CN/messages.json  | 2 +-
 .../node_modules/@node-red/nodes/locales/zh-TW/messages.json  | 2 +-
 6 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html
index ee5e973b0..7b96aaf2f 100644
--- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html
+++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html
@@ -186,8 +186,8 @@
                 <input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px">
             </div>
             <div class="form-row" style="height: 34px;">
-                <input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 0 0 104px; display: inline-block; width: auto; vertical-align: top;">
-                <label for="node-config-input-usetls" style="width: 80px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
+                <input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;">
+                <label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
                 <span id="node-config-row-tls" class="hide"><input style="width: 320px;" type="text" id="node-config-input-tls"></span>
             </div>
             <div class="form-row">
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 aabab9f79..38fe83ce6 100755
--- a/packages/node_modules/@node-red/nodes/locales/de/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json
@@ -363,7 +363,7 @@
             "keepalive": "Keep-Alive",
             "cleansession": "Bereinigte Sitzung (clean session) verwenden",
             "cleanstart": "Verwende bereinigten Start",
-            "use-tls": "Sichere Verbindung (SSL/TLS) verwenden",
+            "use-tls": "TLS",
             "tls-config": "TLS-Konfiguration",
             "verify-server-cert": "Server-Zertifikat überprüfen",
             "compatmode": "MQTT 3.1 unterstützen",
diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json
index 8e3a4f325..74a7885d2 100755
--- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json
@@ -333,7 +333,7 @@
             "port": "포트",
             "keepalive": "킵 얼라이브 시간",
             "cleansession": "세션 초기화",
-            "use-tls": "SSL/TLS접속을 사용",
+            "use-tls": "사용TLS",
             "tls-config": "TLS설정",
             "verify-server-cert": "서버인증서를 확인",
             "compatmode": "구 MQTT 3.1서포트"
diff --git a/packages/node_modules/@node-red/nodes/locales/ru/messages.json b/packages/node_modules/@node-red/nodes/locales/ru/messages.json
index 08e2b6b73..f32160bf1 100755
--- a/packages/node_modules/@node-red/nodes/locales/ru/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ru/messages.json
@@ -352,7 +352,7 @@
             "port": "Порт",
             "keepalive": "Keep-alive время (сек)",
             "cleansession": "Использовать чистую сессию",
-            "use-tls": "Включить безопасное (SSL/TLS) соединение",
+            "use-tls": "TLS",
             "tls-config":"Конфигурация TLS",
             "verify-server-cert":"Проверить сертификат сервера",
             "compatmode": "Использовать устаревшую поддержку MQTT 3.1"
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 ee074c0fe..e2afa0fc1 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
@@ -353,7 +353,7 @@
             "port": "端口",
             "keepalive": "Keepalive计时(秒)",
             "cleansession": "使用新的会话",
-            "use-tls": "使用安全连接 (SSL/TLS)",
+            "use-tls": "使用 TLS",
             "tls-config": "TLS 设置",
             "verify-server-cert": "验证服务器证书",
             "compatmode": "使用旧式MQTT 3.1支持"
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 de48fe9f9..1335e9ff2 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
@@ -353,7 +353,7 @@
             "port": "埠",
             "keepalive": "Keepalive計時(秒)",
             "cleansession": "使用新的會話",
-            "use-tls": "使用安全連接 (SSL/TLS)",
+            "use-tls": "使用 TLS",
             "tls-config": "TLS 設置",
             "verify-server-cert": "驗證伺服器憑證",
             "compatmode": "使用舊式MQTT 3.1支援"

From e44131f97a828943aa0da670924ba8bdac10a1ba Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 12 Apr 2021 12:08:07 +0100
Subject: [PATCH 08/48] Update function node help reference to node properties

---
 .../@node-red/nodes/locales/en-US/function/10-function.html     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
index 02e85922e..dd13b6732 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html
@@ -52,7 +52,7 @@
     pass <code>msg</code> as a second argument to <code>node.error</code>:</p>
     <pre>node.error("Error",msg);</pre>
     <h4>Accessing Node Information</h4>
-    <p>In the function block, id and name of the node can be referenced using the following properties:</p>
+    <p>The following properties are available to access information about the node:</p>
     <ul>
         <li><code>node.id</code> - id of the node</li>
         <li><code>node.name</code> - name of the node</li>

From 023486e175f76aa5996c04c0fcb9d25a63f23f8f Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Mon, 12 Apr 2021 12:16:23 +0100
Subject: [PATCH 09/48] File out node - fix timing of msg.send to be after
 close., and...

allow msg.encoding to set encoding if desired.
To close #2921
---
 .../@node-red/nodes/core/storage/10-file.html |  4 ++
 .../@node-red/nodes/core/storage/10-file.js   | 52 ++++++++++++-------
 .../nodes/locales/en-US/messages.json         |  1 +
 .../nodes/locales/en-US/storage/10-file.html  |  2 +
 4 files changed, 39 insertions(+), 20 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html
index 5487920b8..a7349715f 100755
--- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html
+++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html
@@ -219,6 +219,10 @@
                 value: "none",
                 label: label
             }).text(label).appendTo(encSel);
+            $("<option/>", {
+                value: "setbymsg",
+                label: node._("file.encoding.setbymsg")
+            }).text(label).appendTo(encSel);
             encodings.forEach(function(item) {
                 if(Array.isArray(item)) {
                     var group = $("<optgroup/>", {
diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.js b/packages/node_modules/@node-red/nodes/core/storage/10-file.js
index 54ce55764..f2065c87a 100644
--- a/packages/node_modules/@node-red/nodes/core/storage/10-file.js
+++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.js
@@ -61,11 +61,13 @@ module.exports = function(RED) {
             if (filename === "") {
                 node.warn(RED._("file.errors.nofilename"));
                 done();
-            } else if (node.overwriteFile === "delete") {
+            }
+            else if (node.overwriteFile === "delete") {
                 fs.unlink(filename, function (err) {
                     if (err) {
                         node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
-                    } else {
+                    }
+                    else {
                         if (RED.settings.verbose) {
                             node.log(RED._("file.status.deletedfile",{file:filename}));
                         }
@@ -73,12 +75,14 @@ module.exports = function(RED) {
                     }
                     done();
                 });
-            } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
+            }
+            else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
                 var dir = path.dirname(filename);
                 if (node.createDir) {
                     try {
                         fs.ensureDirSync(dir);
-                    } catch(err) {
+                    }
+                    catch(err) {
                         node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
                         done();
                         return;
@@ -92,7 +96,11 @@ module.exports = function(RED) {
                 if (typeof data === "boolean") { data = data.toString(); }
                 if (typeof data === "number") { data = data.toString(); }
                 if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
-                var buf = encode(data, node.encoding);
+                var buf;
+                if (node.encoding === "setbymsg") {
+                    buf = encode(data, msg.encoding || "none");
+                }
+                else { buf = encode(data, node.encoding); }
                 if (node.overwriteFile === "true") {
                     var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
                     node.wstream = wstream;
@@ -101,10 +109,11 @@ module.exports = function(RED) {
                         done();
                     });
                     wstream.on("open", function() {
-                        wstream.end(buf, function() {
+                        wstream.once("close", function() {
                             nodeSend(msg);
                             done();
                         });
+                        wstream.end(buf);
                     })
                     return;
                 }
@@ -126,7 +135,8 @@ module.exports = function(RED) {
                                 delete node.wstream;
                                 delete node.wstreamIno;
                             }
-                        } catch(err) {
+                        }
+                        catch(err) {
                             // File does not exist
                             recreateStream = true;
                             node.wstream.end();
@@ -154,14 +164,16 @@ module.exports = function(RED) {
                             nodeSend(msg);
                             done();
                         });
-                    } else {
+                    }
+                    else {
                         // Dynamic filename - write and close the stream
-                        node.wstream.end(buf, function() {
+                        node.wstream.once("close", function() {
                             nodeSend(msg);
                             delete node.wstream;
                             delete node.wstreamIno;
                             done();
                         });
+                        node.wstream.end(buf);
                     }
                 }
             }
@@ -276,7 +288,6 @@ module.exports = function(RED) {
                     ch = "\n";
                     type = "string";
                 }
-                var hwm;
                 var getout = false;
 
                 var rs = fs.createReadStream(filename)
@@ -340,16 +351,17 @@ module.exports = function(RED) {
                             nodeSend(msg);
                         }
                         else if (node.format === "lines") {
-                            var m = { payload: spare,
-                                      topic:msg.topic,
-                                      parts: {
-                                          index: count,
-                                          count: count+1,
-                                          ch: ch,
-                                          type: type,
-                                          id: msg._msgid
-                                      }
-                                    };
+                            var m = {
+                                payload: spare,
+                                topic:msg.topic,
+                                parts: {
+                                    index: count,
+                                    count: count+1,
+                                    ch: ch,
+                                    type: type,
+                                    id: msg._msgid
+                                }
+                            };
                             nodeSend(m);
                         }
                         else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
index 17b230c69..5ca06426c 100755
--- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
@@ -877,6 +877,7 @@
         },
         "encoding": {
             "none": "default",
+            "setbymsg": "set by msg.encoding",
             "native": "Native",
             "unicode": "Unicode",
             "japanese": "Japanese",
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html
index fe2bbc324..70400f676 100644
--- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html
@@ -21,6 +21,8 @@
     <dl class="message-properties">
         <dt class="optional">filename <span class="property-type">string</span></dt>
         <dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
+        <dt class="optional">encoding <span class="property-type">string</span></dt>
+        <dd>If encoding is configured to be set by msg, then this optional property can set the encoding.</dt>
     </dl>
     <h3>Output</h3>
     <p>On completion of write, input message is sent to output port.</p>

From 39274b0c5dbd88650618639a3c03390bc7a4e31b Mon Sep 17 00:00:00 2001
From: Kazuhito Yokoi <kazuhito.yokoi.nx@hitachi.com>
Date: Mon, 12 Apr 2021 22:15:07 +0900
Subject: [PATCH 10/48] Add Japanese translations for Node-RED v1.3.1 (#2930)

---
 .../@node-red/editor-client/locales/ja/editor.json            | 4 ++--
 .../node_modules/@node-red/nodes/core/function/10-function.js | 2 +-
 .../node_modules/@node-red/nodes/locales/en-US/messages.json  | 1 +
 .../@node-red/nodes/locales/ja/function/10-function.html      | 3 ++-
 .../node_modules/@node-red/nodes/locales/ja/messages.json     | 2 ++
 .../@node-red/nodes/locales/ja/storage/10-file.html           | 2 ++
 6 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json
index 6a7edb202..3870c9336 100644
--- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json
+++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json
@@ -524,8 +524,8 @@
             "title": "パレットの管理",
             "palette": "パレット",
             "times": {
-                "seconds": "秒前",
-                "minutes": "分前",
+                "seconds": "数秒前",
+                "minutes": "数分前",
                 "minutesV": "__count__ 分前",
                 "hoursV": "__count__ 時間前",
                 "hoursV_plural": "__count__ 時間前",
diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js
index 0e8b7f5ef..17e6e569a 100644
--- a/packages/node_modules/@node-red/nodes/core/function/10-function.js
+++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js
@@ -302,7 +302,7 @@ module.exports = function(RED) {
                 }
             });
             if (moduleErrors) {
-                throw new Error("Function node failed to load external modules");
+                throw new Error(RED._("function.error.externalModuleLoadError"));
             }
         }
 
diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
index 5ca06426c..f4766c634 100755
--- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
@@ -227,6 +227,7 @@
         "error": {
             "externalModuleNotAllowed": "Function node not allowed to load external modules",
             "moduleNotAllowed": "Module __module__ not allowed",
+            "externalModuleLoadError": "Function node failed to load external modules",
             "moduleLoadError": "Failed to load module __module__: __error__",
             "moduleNameError": "Invalid module variable name: __name__",
             "moduleNameReserved": "Reserved variable name: __name__",
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html
index de6f44e3d..1ee1aa2b0 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html
@@ -44,10 +44,11 @@
     <p>catchノードを用いてエラー処理が可能です。catchノードで処理させるためには、<code>msg</code>を<code>node.error</code>の第二引数として渡します:</p>
     <pre>node.error("エラー",msg);</pre>
     <h4>ノード情報の参照</h4>
-    <p>コード中ではノードのIDおよび名前を以下のプロパティで参照できます:</p>
+    <p>ノードに関する情報を参照するための以下のプロパティを利用できます:</p>
     <ul>
         <li><code>node.id</code> - ノードのID</li>
         <li><code>node.name</code> - ノードの名称</li>
+        <li><code>node.outputCount</code> - ノードの出力数</li>
     </ul>
     <h4>環境変数の利用</h4>
     <p>環境変数は<code>env.get("MY_ENV_VAR")</code>により参照できます。</p>
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
index f8412efff..bab9fd724 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json
@@ -227,6 +227,7 @@
         "error": {
             "externalModuleNotAllowed": "Functionノードは、外部モジュールを読み込みできません",
             "moduleNotAllowed": "モジュール __module__ は利用できません",
+            "externalModuleLoadError": "Functionノードは、外部モジュールの読み込みに失敗しました",
             "moduleLoadError": "モジュール __module__ の読み込みに失敗しました: __error__",
             "moduleNameError": "モジュール変数名が不正です: __name__",
             "moduleNameReserved": "予約された変数名です: __name__",
@@ -875,6 +876,7 @@
         },
         "encoding": {
             "none": "デフォルト",
+            "setbymsg": "msg.encodingで設定",
             "native": "ネイティブ",
             "unicode": "UNICODE",
             "japanese": "日本",
diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html
index 21d4af9be..3e67368d3 100644
--- a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html
+++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html
@@ -20,6 +20,8 @@
     <dl class="message-properties">
         <dt class="optional">filename <span class="property-type">文字列</span></dt>
         <dd>対象ファイル名をノードに設定していない場合、このプロパティでファイルを指定できます</dd>
+        <dt class="optional">encoding <span class="property-type">文字列</span></dt>
+        <dd>エンコーディングをmsgで設定する構成にした際は、この任意のプロパティでエンコーディングを設定できます。</dt>
     </dl>
     <h3>出力</h3>
     <p>書き込みの完了時、入力メッセージを出力端子に送出します。</p>

From 6a8cf1b7686ca7fd62aa00e05eba089225359d31 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 13 Apr 2021 13:24:54 +0100
Subject: [PATCH 11/48] Fix variable reference error in editableList Fixes
 #2933

---
 .../editor-client/src/js/ui/common/editableList.js          | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js
index 7f823289e..00666e5b4 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js
@@ -71,7 +71,7 @@
             var buttons = this.options.buttons || [];
 
             if (this.options.addButton !== false) {
-                var addLabel, addTittle;
+                var addLabel, addTitle;
                 if (typeof this.options.addButton === 'string') {
                     addLabel = this.options.addButton
                 } else {
@@ -102,7 +102,7 @@
                             button.click(evt);
                         }
                     });
-                    
+
                 if (button.title) {
                     element.attr("title", button.title);
                 }
@@ -113,7 +113,7 @@
                     element.append($("<span></span>").text(" " + button.label));
                 }
             });
- 
+
             if (this.element.css("position") === "absolute") {
                 ["top","left","bottom","right"].forEach(function(s) {
                     var v = that.element.css(s);

From a4a624d53776a9032d367d377b85ab4626b507e5 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 13 Apr 2021 13:33:41 +0100
Subject: [PATCH 12/48] Update changelog

---
 CHANGELOG.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8cb1464cc..40724e589 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+### 1.3.2: Maintenance Release
+
+
+Runtime
+ - Handle package.json without dependencies section
+
+Editor
+
+ - Fix variable reference error in editableList Fixes #2933
+ - Fix handling of user-provided keymap Fixes #2926
+ - Ensure theme login image is passed through to api response Fixes #2929
+ - Add Japanese translations for Node-RED v1.3.1 (#2930) @kazuhitoyokoi
+
+Nodes
+
+ - CSV: Fix CSV parsing with other than , separator
+ - File out: Fix timing of msg.send to be after close
+ - Function: describe `node.outputCount` in help text
+ - MQTT: Fix MQTT Broker TLS config row layout Fixes #2927
+ - Split: add comment to info re $N being number of messages arriving
 
 ### 1.3.1: Maintenance Release
 

From b0955705be599ff091d6fe0bae841985d10c04ac Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 13 Apr 2021 13:34:16 +0100
Subject: [PATCH 13/48] Update to 1.3.2

---
 package.json                                           |  2 +-
 .../node_modules/@node-red/editor-api/package.json     |  6 +++---
 .../node_modules/@node-red/editor-client/package.json  |  2 +-
 packages/node_modules/@node-red/nodes/package.json     |  2 +-
 packages/node_modules/@node-red/registry/package.json  |  4 ++--
 packages/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 +++++-----
 8 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/package.json b/package.json
index 1c5861755..c836a35d5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "node-red",
-    "version": "1.3.1",
+    "version": "1.3.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 34fb833d4..c1c0bf905 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.3.1",
+    "version": "1.3.2",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,8 +16,8 @@
         }
     ],
     "dependencies": {
-        "@node-red/util": "1.3.1",
-        "@node-red/editor-client": "1.3.1",
+        "@node-red/util": "1.3.2",
+        "@node-red/editor-client": "1.3.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 9da45ea59..ff0722c0e 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.3.1",
+    "version": "1.3.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 84465ba5e..77143545d 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.3.1",
+    "version": "1.3.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 513385e7a..ac1c66709 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.3.1",
+    "version": "1.3.2",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,7 +16,7 @@
         }
     ],
     "dependencies": {
-        "@node-red/util": "1.3.1",
+        "@node-red/util": "1.3.2",
         "semver": "6.3.0",
         "tar": "6.1.0",
         "uglify-js": "3.13.3"
diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json
index a9e7bc0ac..0f434840e 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.3.1",
+    "version": "1.3.2",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,8 +16,8 @@
         }
     ],
     "dependencies": {
-        "@node-red/registry": "1.3.1",
-        "@node-red/util": "1.3.1",
+        "@node-red/registry": "1.3.2",
+        "@node-red/util": "1.3.2",
         "async-mutex": "0.3.1",
         "clone": "2.1.2",
         "express": "4.17.1",
diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json
index 1c04d1e4e..6bee2d78c 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.3.1",
+    "version": "1.3.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 fd9462798..2df31c0c6 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.3.1",
+    "version": "1.3.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.3.1",
-        "@node-red/runtime": "1.3.1",
-        "@node-red/util": "1.3.1",
-        "@node-red/nodes": "1.3.1",
+        "@node-red/editor-api": "1.3.2",
+        "@node-red/runtime": "1.3.2",
+        "@node-red/util": "1.3.2",
+        "@node-red/nodes": "1.3.2",
         "basic-auth": "2.0.1",
         "bcryptjs": "2.4.3",
         "express": "4.17.1",

From b5fda5642f037f3402bcc4f7ed8e54b5eb377523 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Wed, 14 Apr 2021 18:06:59 +0100
Subject: [PATCH 14/48] Fix package semver comparison to allow >1 version
 increment

---
 .../@node-red/editor-client/src/js/ui/palette-editor.js         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
index 2fe3d448e..e57a19349 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
@@ -331,7 +331,7 @@ RED.palette.editor = (function() {
                 nodeEntry.versionSpan.html(moduleInfo.version+' <i class="fa fa-long-arrow-right"></i> '+moduleInfo.pending_version).appendTo(nodeEntry.metaRow)
                 nodeEntry.updateButton.text(RED._('palette.editor.updated')).addClass('disabled').css('display', 'inline-block');
             } else if (loadedIndex.hasOwnProperty(module)) {
-                if (semVerCompare(loadedIndex[module].version,moduleInfo.version) === 1) {
+                if (semVerCompare(loadedIndex[module].version,moduleInfo.version) > 0) {
                     nodeEntry.updateButton.show();
                     nodeEntry.updateButton.text(RED._('palette.editor.update',{version:loadedIndex[module].version}));
                 } else {

From 04a3c4bb222f1708877ddd98379be801bfc6fc72 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Wed, 14 Apr 2021 22:28:25 +0100
Subject: [PATCH 15/48] Ensure mqtt-close message is published when closing
 mqtt nodes

The change in 1.3 where we ensure config nodes are closed last broke this behaviour. Previously, the config node would get closed triggering the close message. With the new 1.3 behaviour, the flow nodes are stopped and as soon as the last flow node deregisters itself, the broker node would disconnect without sending the close message.

The fix is to send the close message as part of the deregister flow as that will handle all cases properly
---
 .../@node-red/nodes/core/network/10-mqtt.js      | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js
index db4d300fe..fe4ce1b3b 100644
--- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js
+++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js
@@ -400,7 +400,15 @@ module.exports = function(RED) {
             }
             if (Object.keys(node.users).length === 0) {
                 if (node.client && node.client.connected) {
-                    return node.client.end(done);
+                    // Send close message
+                    if (node.closeMessage) {
+                        node.publish(node.closeMessage,function(err) {
+                            node.client.end(done);
+                        });
+                    } else {
+                        node.client.end(done);
+                    }
+                    return;
                 } else {
                     node.client.end();
                     return done();
@@ -639,10 +647,6 @@ module.exports = function(RED) {
         this.on('close', function(done) {
             this.closing = true;
             if (this.connected) {
-                // Send close message
-                if (node.closeMessage) {
-                    node.publish(node.closeMessage);
-                }
                 this.client.once('close', function() {
                     done();
                 });
@@ -873,4 +877,4 @@ module.exports = function(RED) {
         }
     }
     RED.nodes.registerType("mqtt out",MQTTOutNode);
-};
\ No newline at end of file
+};

From 0167c25e088a5f8b0c0a1aa61a3861d446531468 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Fri, 16 Apr 2021 11:56:23 +0100
Subject: [PATCH 16/48] Export package version in Grunt file so docs template
 can access

---
 Gruntfile.js | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index 23481f793..f19432a18 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -40,8 +40,11 @@ module.exports = function(grunt) {
     if (nonHeadless) {
         process.env.NODE_RED_NON_HEADLESS = true;
     }
+    let packageFile = grunt.file.readJSON('package.json')
+    process.env.NODE_RED_PACKAGE_VERSION = packageFile.version;
+
     grunt.initConfig({
-        pkg: grunt.file.readJSON('package.json'),
+        pkg: packageFile,
         paths: {
             dist: ".dist"
         },
@@ -467,7 +470,8 @@ module.exports = function(grunt) {
                 ],
                 options: {
                     destination: 'docs',
-                    configure: './jsdoc.json'
+                    configure: './jsdoc.json',
+                    fred: "hi there"
                 }
             },
             _editor: {

From 235690064fe25bba1f5442b59c3cfc3993cb6dc3 Mon Sep 17 00:00:00 2001
From: Ben Hardill <hardillb@gmail.com>
Date: Fri, 16 Apr 2021 13:26:11 +0100
Subject: [PATCH 17/48] Fix for #2935

---
 .../node_modules/@node-red/nodes/core/network/21-httpin.js    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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 26234b737..b458a459c 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,7 +46,9 @@ module.exports = function(RED) {
                     isText = true;
                 } else if (parsedType.type !== "application") {
                     isText = false;
-                } else if ((parsedType.subtype !== "octet-stream") && (parsedType.subtype !== "cbor")) {
+                } else if ((parsedType.subtype !== "octet-stream") 
+                    && (parsedType.subtype !== "cbor")
+                    && (parsedType.subtype !== "x-protobuf")) {
                     checkUTF = true;
                 } else {
                     // application/octet-stream or application/cbor

From 0253dc9623fc18a8c6855ce7ac1fb1d31c79da19 Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Mon, 19 Apr 2021 10:03:11 +0100
Subject: [PATCH 18/48] ensure CSV node can send false as string

---
 packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
index 95a4eb2b2..894968935 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
@@ -81,7 +81,7 @@ module.exports = function(RED) {
                             if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
                                 if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
                                 for (var t = 0; t < msg.payload[s].length; t++) {
-                                    if (!msg.payload[s][t] && (msg.payload[s][t] !== 0)) { msg.payload[s][t] = ""; }
+                                    if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
                                     if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
                                         msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
                                         msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;

From 4a4e7fc7cb685dd1e013572267b91f9ea6ce7c2e Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 19 Apr 2021 10:39:58 +0100
Subject: [PATCH 19/48] Make typedInput.disable more consistent in behaviour
 Fixes #2942

---
 .../editor-client/src/js/ui/common/typedInput.js      | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 1b5af5f13..b5d82b830 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -1004,16 +1004,17 @@
             this.uiSelect.hide();
         },
         disable: function(val) {
-            if(val === true) {
+            if(val === undefined || !!val === true) {
                 this.uiSelect.attr("disabled", "disabled");
-            } else if (val === false) {
-                this.uiSelect.attr("disabled", null); //remove attr
             } else {
-                this.uiSelect.attr("disabled", val); //user value
+                this.uiSelect.attr("disabled", null); //remove attr
             }
         },
+        enable: function() {
+            this.uiSelect.attr("disabled", null); //remove attr
+        },
         disabled: function() {
-            return this.uiSelect.attr("disabled");
+            return this.uiSelect.attr("disabled") === "disabled";
         }
     });
 })(jQuery);

From 3f43dc1855007aa2708d4ef413b7dfac526dec48 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 19 Apr 2021 10:43:01 +0100
Subject: [PATCH 20/48] Fix jshint error

---
 .../@node-red/editor-client/src/js/ui/common/typedInput.js      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index b5d82b830..7cbad2f05 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -1004,7 +1004,7 @@
             this.uiSelect.hide();
         },
         disable: function(val) {
-            if(val === undefined || !!val === true) {
+            if(val === undefined || !!val ) {
                 this.uiSelect.attr("disabled", "disabled");
             } else {
                 this.uiSelect.attr("disabled", null); //remove attr

From ff00afb5d7ed1356d0752f75a33d6adfb392ca8c Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 19 Apr 2021 11:32:26 +0100
Subject: [PATCH 21/48] Fix project credential secret reset handling Part of
 #2868

---
 .../editor-client/src/js/ui/projects/projectSettings.js     | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js
index c39ac97be..336893a59 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js
@@ -928,11 +928,11 @@ RED.projects.settings = (function() {
 
             saveDisabled = isFlowInvalid || credFileLabelText.text()==="";
 
-            if (credentialSecretExistingInput.is(":visible")) {
+            if (credentialSecretExistingRow.is(":visible")) {
                 credentialSecretExistingInput.toggleClass("input-error", credentialSecretExistingInput.val() === "");
                 saveDisabled = saveDisabled || credentialSecretExistingInput.val() === "";
             }
-            if (credentialSecretNewInput.is(":visible")) {
+            if (credentialSecretNewRow.is(":visible")) {
                 credentialSecretNewInput.toggleClass("input-error", credentialSecretNewInput.val() === "");
                 saveDisabled = saveDisabled || credentialSecretNewInput.val() === "";
             }
@@ -1130,7 +1130,7 @@ RED.projects.settings = (function() {
                 }
                 if (credentialSecretResetButton.hasClass('selected') || credentialSecretEditButton.hasClass('selected')) {
                     payload.credentialSecret = credentialSecretNewInput.val();
-                    if (credentialSecretExistingInput.is(":visible")) {
+                    if (credentialSecretExistingRow.is(":visible")) {
                         payload.currentCredentialSecret = credentialSecretExistingInput.val();
                     }
                 }

From 233a74c1460ef63b19a79df818ba4490c0719c6d Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 19 Apr 2021 15:27:47 +0100
Subject: [PATCH 22/48] Remove TypedInput from tab focus when only one type
 available

---
 .../@node-red/editor-client/src/js/ui/common/typedInput.js   | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 7cbad2f05..5da62c74a 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -669,6 +669,11 @@
                 that.typeMap[result.value] = result;
                 return result;
             });
+            if (this.typeList.length < 2) {
+                this.selectTrigger.attr("tabindex", -1)
+            } else {
+                this.selectTrigger.attr("tabindex", 0)
+            }
             this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
             this.selectTrigger.find(".fa-caret-down").toggle(this.typeList.length > 1)
             if (this.menu) {

From 9eb668ab30c9f99db8c40727e8fdcaeb5c941739 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 19 Apr 2021 15:28:17 +0100
Subject: [PATCH 23/48] Prevent TypedInput label overflowing element

Also adds title attribute to the button for the label so it gets a tooltip
---
 .../@node-red/editor-client/src/js/ui/common/typedInput.js   | 5 +++++
 .../editor-client/src/sass/ui/common/typedInput.scss         | 4 ++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 5da62c74a..764c6185c 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -773,6 +773,11 @@
                     if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
                         this.selectLabel.text(opt.label);
                     }
+                    if (opt.label) {
+                        this.selectTrigger.attr("title",opt.label);
+                    } else {
+                        this.selectTrigger.attr("title","");
+                    }
                     if (opt.hasValue === false) {
                         this.selectTrigger.addClass("red-ui-typedInput-full-width");
                     } else {
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss
index 14cb3f70e..2b12fffea 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss
@@ -24,7 +24,7 @@
     margin: 0;
     vertical-align: middle;
     box-sizing: border-box;
-    overflow:visible;
+    overflow: hidden;
     position: relative;
     &[disabled] {
         input, button {
@@ -33,7 +33,7 @@
             cursor: not-allowed;
         }
     }
-    
+
     .red-ui-typedInput-input-wrap {
         flex-grow: 1;
     }

From 0e06da6c63326b92cacc5926e054a94b3d3ef31e Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 20 Apr 2021 11:06:23 +0100
Subject: [PATCH 24/48] Update for 1.3.3

---
 CHANGELOG.md                                  | 22 ++++++++++++++++++-
 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, 38 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40724e589..744ff06e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,25 @@
-### 1.3.2: Maintenance Release
+### 1.3.3: Maintenance Release
 
+Editor
+
+ - Fix package semver comparison to allow >1 version increment
+ - Prevent TypedInput label overflowing element Fixes #2941
+ - Remove TypedInput from tab focus when only one type available
+ - Make typedInput.disable more consistent in behaviour Fixes #2942
+ - Fix project credential secret reset handling Part of #2868
+
+Runtime
+
+ - Export package version in Grunt file so docs template can access
+
+Nodes
+
+ - CSV: ensure CSV node can send false as string
+ - HTTPIn: handle application/x-protobuf as Buffer type (#2935 #2938) @hardillb
+ - MQTT: Ensure mqtt-close message is published when closing mqtt nodes
+
+
+### 1.3.2: Maintenance Release
 
 Runtime
  - Handle package.json without dependencies section
diff --git a/package.json b/package.json
index c836a35d5..9c336d690 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "node-red",
-    "version": "1.3.2",
+    "version": "1.3.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 c1c0bf905..25b217c58 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.3.2",
+    "version": "1.3.3",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,8 +16,8 @@
         }
     ],
     "dependencies": {
-        "@node-red/util": "1.3.2",
-        "@node-red/editor-client": "1.3.2",
+        "@node-red/util": "1.3.3",
+        "@node-red/editor-client": "1.3.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 ff0722c0e..785f2ad2c 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.3.2",
+    "version": "1.3.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 77143545d..524f0d2e8 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.3.2",
+    "version": "1.3.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 ac1c66709..b0f943ab6 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.3.2",
+    "version": "1.3.3",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,7 +16,7 @@
         }
     ],
     "dependencies": {
-        "@node-red/util": "1.3.2",
+        "@node-red/util": "1.3.3",
         "semver": "6.3.0",
         "tar": "6.1.0",
         "uglify-js": "3.13.3"
diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json
index 0f434840e..5d7e44b2e 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.3.2",
+    "version": "1.3.3",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,8 +16,8 @@
         }
     ],
     "dependencies": {
-        "@node-red/registry": "1.3.2",
-        "@node-red/util": "1.3.2",
+        "@node-red/registry": "1.3.3",
+        "@node-red/util": "1.3.3",
         "async-mutex": "0.3.1",
         "clone": "2.1.2",
         "express": "4.17.1",
diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json
index 6bee2d78c..64603d084 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.3.2",
+    "version": "1.3.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 2df31c0c6..c42a97fe6 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.3.2",
+    "version": "1.3.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.3.2",
-        "@node-red/runtime": "1.3.2",
-        "@node-red/util": "1.3.2",
-        "@node-red/nodes": "1.3.2",
+        "@node-red/editor-api": "1.3.3",
+        "@node-red/runtime": "1.3.3",
+        "@node-red/util": "1.3.3",
+        "@node-red/nodes": "1.3.3",
         "basic-auth": "2.0.1",
         "bcryptjs": "2.4.3",
         "express": "4.17.1",

From f8d5fef3c40cb3315f766fb8c78fb5a7ab38441e Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 20 Apr 2021 23:25:56 +0100
Subject: [PATCH 25/48] Ensure typedInput without value has focus class removed
 Closes #2945

---
 .../@node-red/editor-client/src/js/ui/common/typedInput.js   | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 764c6185c..63455fe17 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -389,6 +389,11 @@
                 evt.stopPropagation();
             }).on('focus', function() {
                 that.uiSelect.addClass('red-ui-typedInput-focus');
+            }).on('blur', function() {
+                var opt = that.typeMap[that.propertyType];
+                if (opt.hasValue === false) {
+                    that.uiSelect.removeClass('red-ui-typedInput-focus');
+                }
             })
 
             // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'

From 23a5cb1917fa6f7fd80e8cae8d04c753c76eecbb Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 20 Apr 2021 23:39:21 +0100
Subject: [PATCH 26/48] Ensure typedInput option is selected in dropdown menu
 Part of #2945

---
 .../@node-red/editor-client/src/js/ui/common/typedInput.js  | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 63455fe17..2dbd6d71a 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -443,7 +443,11 @@
                 });
 
                 this._showMenu(this.optionMenu,this.optionSelectTrigger);
-                var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
+                var targetValue = this.optionValue;
+                if (this.optionValue === null || this.optionValue === undefined) {
+                    targetValue = this.value();
+                }
+                var selectedOption = this.optionMenu.find("[value='"+targetValue+"']");
                 if (selectedOption.length === 0) {
                     selectedOption = this.optionMenu.children(":first");
                 }

From 372122037fa0ff514d05152c27d60d96110223f5 Mon Sep 17 00:00:00 2001
From: Kazuhito Yokoi <kazuhito.yokoi.nx@hitachi.com>
Date: Wed, 21 Apr 2021 13:14:46 +0900
Subject: [PATCH 27/48] Fix margin between nodes on palette

---
 .../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 385ad761f..2855ee494 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
@@ -134,7 +134,7 @@
     &:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child {
         margin-top: 15px;
     }
-    &:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child {
+    &:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):last-child {
         margin-bottom: 15px;
     }
 }

From 4cebbf8d22617e17d3cbf55391c221041db3c85b Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Fri, 23 Apr 2021 10:47:23 +0100
Subject: [PATCH 28/48] Fix CSV handling of special chars as separators

(ie escape regex special chars)
and add tests
to close #2950
---
 .../@node-red/nodes/core/parsers/70-CSV.js    |  8 ++---
 test/nodes/core/parsers/70-CSV_spec.js        | 34 +++++++++++++++++++
 2 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
index 894968935..8ab296159 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
@@ -38,18 +38,18 @@ module.exports = function(RED) {
         if (this.hdrout === true) { this.hdrout = "all"; }
         var tmpwarn = true;
         var node = this;
-        var re = new RegExp(node.sep+'(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
+        var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
 
         // pass in an array of column names to be trimmed, de-quoted and retrimmed
         var clean = function(col,sep) {
-            if (sep) { re = new RegExp(sep+'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
+            if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
             col = col.trim().split(re) || [""];
             col = col.map(x => x.replace(/"/g,'').trim());
             if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
             else { node.goodtmpl = true; }
             return col;
         }
-        var template = clean(node.template);
+        var template = clean(node.template,',');
         var notemplate = template.length === 1 && template[0] === '';
         node.hdrSent = false;
 
@@ -185,7 +185,7 @@ module.exports = function(RED) {
                             if ((node.hdrin === true) && first) { // if the template is in the first line
                                 if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
                                     if (line.length - i === 1) { tmp += line[i]; }
-                                    template = clean(tmp);
+                                    template = clean(tmp,node.sep);
                                     first = false;
                                 }
                                 else { tmp += line[i]; }
diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index cccf7bf1c..b2004eb75 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -87,6 +87,40 @@ describe('CSV node', function() {
             });
         });
 
+        it('should convert a simple string to a javascript object with | separator (no template)', function(done) {
+            var flow = [ { id:"n1", type:"csv", sep:"|", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 });
+                    msg.should.have.property('columns', "col1,col2,col3,col4");
+                    check_parts(msg, 0, 1);
+                    done();
+                });
+                var testString = "1|2|3|4"+String.fromCharCode(10);
+                n1.emit("input", {payload:testString});
+            });
+        });
+
+        it('should convert a simple string to a javascript object with tab separator (with template)', function(done) {
+            var flow = [ { id:"n1", type:"csv", sep:"\t", temp:"A,B,,D", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    msg.should.have.property('payload', { A: 1, B: 2, D: 4 });
+                    msg.should.have.property('columns', "A,B,D");
+                    check_parts(msg, 0, 1);
+                    done();
+                });
+                var testString = "1\t2\t3\t4"+String.fromCharCode(10);
+                n1.emit("input", {payload:testString});
+            });
+        });
+
         it('should remove quotes and whitespace from template', function(done) {
             var flow = [ { id:"n1", type:"csv", temp:'"a",  "b" , " c "," d  " ', wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];

From 9f1deb0c36b07d871a4e474783c85a8959dcc100 Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Fri, 23 Apr 2021 11:19:23 +0100
Subject: [PATCH 29/48] CSV Add couple more special character tests just to
 make sure

---
 test/nodes/core/parsers/70-CSV_spec.js | 34 ++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index b2004eb75..8cecbb7fb 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -222,6 +222,40 @@ describe('CSV node', function() {
             });
         });
 
+        it('should allow passing in a template as first line of CSV (special char /)', function(done) {
+            var flow = [ { id:"n1", type:"csv", temp:"", hdrin:true, sep:"/", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    msg.should.have.property('payload', { a: 1, "b b":2, "c/c":3, "d, d": 4 });
+                    msg.should.have.property('columns', 'a,b b,c/c,"d, d"');
+                    check_parts(msg, 0, 1);
+                    done();
+                });
+                var testString = 'a/b b/"c/c"/" d, d "'+"\n"+"1/2/3/4"+String.fromCharCode(10);
+                n1.emit("input", {payload:testString});
+            });
+        });
+
+        it('should allow passing in a template as first line of CSV (special char \\)', function(done) {
+            var flow = [ { id:"n1", type:"csv", temp:"", hdrin:true, sep:"\\", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    msg.should.have.property('payload', { a: 1, "b b":2, "c\\c":3, "d, d": 4 });
+                    msg.should.have.property('columns', 'a,b b,c\\c,"d, d"');
+                    check_parts(msg, 0, 1);
+                    done();
+                });
+                var testString = 'a\\b b\\"c\\c"\\" d, d "'+"\n"+"1\\2\\3\\4"+String.fromCharCode(10);
+                n1.emit("input", {payload:testString});
+            });
+        });
+
         it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) {
             var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];

From f8abf9fce16981df75243b722ede62031fde0d6b Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Sun, 25 Apr 2021 08:53:18 +0100
Subject: [PATCH 30/48] add another test to csv

---
 test/nodes/core/parsers/70-CSV_spec.js | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index 8cecbb7fb..f8e99ac31 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -121,6 +121,23 @@ describe('CSV node', function() {
             });
         });
 
+        it('should convert a simple string to a javascript object with space separator (with spaced template)', function(done) {
+            var flow = [ { id:"n1", type:"csv", sep:" ", temp:"A, B, , D", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    msg.should.have.property('payload', { A: 1, B: 2, D: 4 });
+                    msg.should.have.property('columns', "A,B,D");
+                    check_parts(msg, 0, 1);
+                    done();
+                });
+                var testString = "1 2 3 4"+String.fromCharCode(10);
+                n1.emit("input", {payload:testString});
+            });
+        });
+
         it('should remove quotes and whitespace from template', function(done) {
             var flow = [ { id:"n1", type:"csv", temp:'"a",  "b" , " c "," d  " ', wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];

From a480919ec3ff361c6aef97cce24fbed88213cabc Mon Sep 17 00:00:00 2001
From: Hiroyasu Nishiyama <hiroyasu.nishiyama.uq@hitachi.com>
Date: Mon, 26 Apr 2021 09:05:53 +0900
Subject: [PATCH 31/48] fix error on auto commit for no flow change

---
 .../lib/storage/localfilesystem/projects/index.js     | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
index 0533fe5e6..1565decdd 100644
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
@@ -608,8 +608,15 @@ async function saveFlows(flows, user) {
             var workflowMode = (gitSettings.workflow||{}).mode || settings.editorTheme.projects.workflow.mode;
             if (workflowMode === 'auto') {
                 return activeProject.stageFile([flowsFullPath, credentialsFile]).then(() => {
-                    return activeProject.commit(user,{message:"Update flow files"})
-                })
+                    return activeProject.status(user, false).then((result) => {
+                        const items = Object.values(result.files || {});
+                        // check if saved flow make modification to repository
+                        if (items.findIndex((item) => (item.status === "M ")) < 0) {
+                            return Promise.resolve();
+                        }
+                        return activeProject.commit(user,{message:"Update flow files"})
+                    });
+                });
             }
         }
     });

From 1d12017f116aebe020443e39566cd0645f2c2434 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 26 Apr 2021 10:13:57 +0100
Subject: [PATCH 32/48] Sort context stores in TypedInput and ensure default
 first Fixes #2954

---
 .../editor-client/src/js/ui/common/typedInput.js       | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 2dbd6d71a..0401be1b9 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -15,7 +15,7 @@
  **/
 (function($) {
     var contextParse = function(v,defaultStore) {
-        var parts = RED.utils.parseContextKey(v, defaultStore);
+        var parts = RED.utils.parseContextKey(v, defaultStore&&defaultStore.value);
         return {
             option: parts.store,
             value: parts.key
@@ -279,6 +279,14 @@
                 var contextStores = RED.settings.context.stores;
                 var contextOptions = contextStores.map(function(store) {
                     return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
+                }).sort(function(A,B) {
+                    if (A.value === RED.settings.context.default) {
+                        return -1;
+                    } else if (B.value === RED.settings.context.default) {
+                        return 1;
+                    } else {
+                        return A.value.localeCompare(B.value);
+                    }
                 })
                 if (contextOptions.length < 2) {
                     allOptions.flow.options = [];

From bbac49ff3819d9b0b6afce7825bd8e74e31860dc Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 26 Apr 2021 10:18:25 +0100
Subject: [PATCH 33/48] Ensure function expand button is above vertical
 scrollbar Fixes #2955

---
 .../@node-red/nodes/core/function/10-function.html          | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html
index a57655cf8..20e51da6c 100644
--- a/packages/node_modules/@node-red/nodes/core/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html
@@ -74,21 +74,21 @@
         <div id="func-tab-init" style="display:none">
             <div class="form-row node-text-editor-row" style="position:relative">
                 <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-init-editor" ></div>
-                <div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
+                <div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
             </div>
         </div>
 
         <div id="func-tab-body" style="display:none">
             <div class="form-row node-text-editor-row" style="position:relative">
                 <div style="height: 220px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
-                <div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
+                <div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
             </div>
         </div>
 
         <div id="func-tab-finalize" style="display:none">
             <div class="form-row node-text-editor-row" style="position:relative">
                 <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
-                <div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
+                <div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
             </div>
         </div>
 

From 4cb8e99430bcb6667e6e42dd9b22ea24f1bad313 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 26 Apr 2021 11:45:28 +0100
Subject: [PATCH 34/48] Timeout http upgrade requests that are not otherwise
 handled Fixes #2956

---
 .../@node-red/runtime/lib/index.js            | 21 +++++++++++++++++++
 packages/node_modules/node-red/settings.js    |  6 ++++++
 2 files changed, 27 insertions(+)

diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js
index 30481740f..c09d202f9 100644
--- a/packages/node_modules/@node-red/runtime/lib/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/index.js
@@ -64,6 +64,27 @@ var server;
  */
 function init(userSettings,httpServer,_adminApi) {
     server = httpServer;
+
+    if (server && server.on) {
+        // Add a listener to the upgrade event so that we can properly timeout connection
+        // attempts that do not get handled by any nodes in the user's flow.
+        // See #2956
+        server.on('upgrade',(request, socket, head) => {
+            // Add a no-op handler to the error event in case nothing upgrades this socket
+            // before the remote end closes it. This ensures we don't get as uncaughtException
+            socket.on("error", err => {})
+            setTimeout(function() {
+                // If this request has been handled elsewhere, the upgrade will have
+                // been completed and bytes written back to the client.
+                // If nothing has been written on the socket, nothing has handled the
+                // upgrade, so we can consider this an unhandled upgrade.
+                if (socket.bytesWritten === 0) {
+                    socket.destroy();
+                }
+            },userSettings.inboundWebSocketTimeout || 5000)
+        });
+    }
+
     userSettings.version = getVersion();
     settings.init(userSettings);
 
diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js
index 331654ed8..a39731f37 100644
--- a/packages/node_modules/node-red/settings.js
+++ b/packages/node_modules/node-red/settings.js
@@ -46,6 +46,12 @@ module.exports = {
     //  defaults to 10Mb
     //execMaxBufferSize: 10000000,
 
+    // Timeout in milliseconds for inbound WebSocket connections that do not
+    // match any configured node.
+    //  defaults to 5000
+    //inboundWebSocketTimeout: 5000
+
+
     // The maximum length, in characters, of any message sent to the debug sidebar tab
     debugMaxLength: 1000,
 

From 8e7efd98b2c8ac2b387808dbf2f0c8ddd03b4858 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Mon, 26 Apr 2021 16:48:21 +0100
Subject: [PATCH 35/48] Don't let 'escape' whilst moving nodes interrupt things
 Fixes #2960

---
 .../node_modules/@node-red/editor-client/src/js/ui/view.js    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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 e19387a1e..bffc6fd85 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
@@ -1751,7 +1751,6 @@ RED.view = (function() {
             }
         }
         if (mouse_mode == RED.state.IMPORT_DRAGGING) {
-            RED.keyboard.remove("escape");
             updateActiveNodes();
             RED.nodes.dirty(true);
         }
@@ -1786,6 +1785,9 @@ RED.view = (function() {
     }
 
     function selectNone() {
+        if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
+            return;
+        }
         if (mouse_mode === RED.state.IMPORT_DRAGGING) {
             clearSelection();
             RED.history.pop();

From 70433f3d0597212e61d69982bb4833723f329af0 Mon Sep 17 00:00:00 2001
From: Hiroyasu Nishiyama <hiroyasu.nishiyama.uq@hitachi.com>
Date: Wed, 28 Apr 2021 21:40:17 +0900
Subject: [PATCH 36/48] fix grunt fail on exec node test

---
 test/nodes/core/function/90-exec_spec.js | 40 +++++++++++++++++++-----
 1 file changed, 32 insertions(+), 8 deletions(-)

diff --git a/test/nodes/core/function/90-exec_spec.js b/test/nodes/core/function/90-exec_spec.js
index e89465c3e..73d8674dd 100644
--- a/test/nodes/core/function/90-exec_spec.js
+++ b/test/nodes/core/function/90-exec_spec.js
@@ -541,13 +541,17 @@ describe('exec node', function() {
                 var n2 = helper.getNode("n2");
                 var n3 = helper.getNode("n3");
                 var n4 = helper.getNode("n4");
+                var payload = "";
                 n2.on("input", function(msg) {
                     //console.log(msg);
                     try {
                         msg.should.have.property("payload");
                         msg.payload.should.be.a.String();
-                        msg.payload.should.equal(expected);
-                        done();
+                        payload += msg.payload;
+                        if (payload.endsWith("\n")) {
+                            payload.should.equal(expected);
+                            done();
+                        }
                     }
                     catch(err) { done(err); }
                 });
@@ -567,6 +571,7 @@ describe('exec node', function() {
                         {id:"n2", type:"helper"},{id:"n3", type:"helper"},{id:"n4", type:"helper"}];
                 expected = "12345 deg C\n";
             }
+            var payload = "";
 
             helper.load(execNode, flow, function() {
                 var n1 = helper.getNode("n1");
@@ -578,8 +583,11 @@ describe('exec node', function() {
                     try {
                         msg.should.have.property("payload");
                         msg.payload.should.be.a.String();
-                        msg.payload.should.equal(expected);
-                        done();
+                        payload += msg.payload;
+                        if (payload.endsWith("\n")) {
+                            payload.should.equal(expected);
+                            done();
+                        }
                     }
                     catch(err) { done(err); }
                 });
@@ -661,8 +669,16 @@ describe('exec node', function() {
                 };
 
                 n2.on("input", function(msg) {
-                    messages[0] = msg;
-                    completeTest();
+                    var payload = msg.payload;
+                    if (messages[0]) {
+                        messages[0].payload += payload;
+                    }
+                    else {
+                        messages[0] = msg;
+                    }
+                    if (payload.endsWith("\n")) {
+                        completeTest();
+                    }
                 });
                 n4.on("input", function(msg) {
                     messages[1] = msg;
@@ -869,8 +885,16 @@ describe('exec node', function() {
                 };
 
                 n2.on("input", function(msg) {
-                    messages[0] = msg;
-                    completeTest();
+                    var payload = msg.payload;
+                    if (messages[0]) {
+                        messages[0].payload += payload;
+                    }
+                    else {
+                        messages[0] = msg;
+                    }
+                    if (payload.endsWith("\n")) {
+                        completeTest();
+                    }
                 });
                 n4.on("input", function(msg) {
                     messages[1] = msg;

From d47a8aa562de5369462fcc2730e6f106fa74761d Mon Sep 17 00:00:00 2001
From: GitHub <hanc2006@gmail.com>
Date: Wed, 28 Apr 2021 17:25:26 +0200
Subject: [PATCH 37/48] Fix remove item when depth=0 and wrong gutter calc

---
 .../src/js/ui/common/treeList.js              | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
index a6db02ffb..5c9bdb5fe 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
@@ -312,7 +312,7 @@
             }
             if (child.depth !== parent.depth+1) {
                 child.depth = parent.depth+1;
-                var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20));
+                var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
                 child.treeList.labelPadding.width(labelPaddingWidth+'px');
                 if (child.element) {
                     $(child.element).css({
@@ -348,6 +348,16 @@
                 that._selected.delete(item);
                 delete item.treeList;
                 delete that._items[item.id];
+                if(item.depth === 0) {
+                    for(var key in that._items) {
+                        var child = that._items[key];
+                        if(child.parent && child.parent.id === item.id) {
+                            delete that._items[key].treeList;
+                            delete that._items[key];
+                        }
+                    }
+                    that._data = that._data.filter(data => data.id !== item.id)
+                }
             }
             item.treeList.insertChildAt = function(newItem,position,select) {
                 newItem.parent = item;
@@ -480,7 +490,10 @@
                     if (item.treeList.container) {
                         $(item.element).remove();
                         $(element).appendTo(item.treeList.label);
-                        var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(item.depth*20);
+                        // using the JQuery Object, the gutter width will
+                        // be wrong when the element is reattached the second time
+                        var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (item.depth * 20);
+
                         $(element).css({
                             width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
                         })
@@ -516,7 +529,7 @@
                 }).appendTo(label)
 
             }
-            var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
+            var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (depth * 20)
             item.treeList.labelPadding = $('<span>').css({
                 display: "inline-block",
                 width:  labelPaddingWidth+'px'

From 91f5542a57e04d6f6d417cd36e088f37cc6c1ce7 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Wed, 28 Apr 2021 20:54:31 +0100
Subject: [PATCH 38/48] Fix importing node to currently flow rather than match
 its old z value

If you import a node whose z value is a known existing tab, it is getting
imported to that tab, rather than the expected behaviour of being imported
to the current tab.

This commit fixes that by checked if the node is being imported to a tab
that was included in the import, rather than pre-existing.
---
 .../node_modules/@node-red/editor-client/src/js/nodes.js    | 6 ++++--
 1 file changed, 4 insertions(+), 2 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 174892d43..9b580f9a0 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
@@ -1424,6 +1424,8 @@ RED.nodes = (function() {
                     nid = getID();
                     workspace_map[n.id] = nid;
                     n.id = nid;
+                } else {
+                    workspace_map[n.id] = n.id;
                 }
                 addWorkspace(n);
                 RED.workspaces.add(n);
@@ -1523,7 +1525,7 @@ RED.nodes = (function() {
                         }
                     }
                 } else {
-                    if (n.z && !workspaces[n.z] && !subflow_map[n.z]) {
+                    if (n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
                         n.z = activeWorkspace;
                     }
                 }
@@ -1621,7 +1623,7 @@ RED.nodes = (function() {
                         node.id = getID();
                     } else {
                         node.id = n.id;
-                        if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
+                        if (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z])) {
                             if (createMissingWorkspace) {
                                 if (missingWorkspace === null) {
                                     missingWorkspace = RED.workspaces.add(null,true);

From 7df1a03b4b348fdbbba2336d7cfadda2a2485f76 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Wed, 28 Apr 2021 21:49:32 +0100
Subject: [PATCH 39/48] Handle subflow modules that contain subflows

---
 .../@node-red/runtime/lib/flows/util.js       |  5 +-
 .../runtime/lib/nodes/credentials.js          |  6 +-
 .../runtime/lib/nodes/credentials_spec.js     | 87 +++++++++----------
 3 files changed, 51 insertions(+), 47 deletions(-)

diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js
index edb348ac4..79c70fb02 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/util.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js
@@ -95,6 +95,9 @@ function createNode(flow,config) {
         } else if (nodeTypeConstructor) {
             // console.log(nodeTypeConstructor)
             var subflowConfig = parseConfig([nodeTypeConstructor.subflow].concat(nodeTypeConstructor.subflow.flow));
+            var subflowInstanceConfig = subflowConfig.subflows[nodeTypeConstructor.subflow.id];
+            delete subflowConfig.subflows[nodeTypeConstructor.subflow.id];
+            subflowInstanceConfig.subflows = subflowConfig.subflows;
             var instanceConfig = clone(config);
             instanceConfig.env = clone(nodeTypeConstructor.subflow.env);
 
@@ -124,7 +127,7 @@ function createNode(flow,config) {
                 nodeTypeConstructor.type,
                 flow,
                 flow.global,
-                subflowConfig.subflows[nodeTypeConstructor.subflow.id],
+                subflowInstanceConfig,
                 instanceConfig
             );
             subflow.start();
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
index 86ea1d3a1..30b2ccdb0 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js
@@ -343,7 +343,11 @@ var api = module.exports = {
         if (newCreds) {
             delete node.credentials;
             var savedCredentials = credentialCache[nodeID] || {};
-            if (/^subflow(:|$)/.test(nodeType)) {
+            // Need to check the type of constructor for this node.
+            // - Function : regular node
+            // - !Function: subflow module
+
+            if (/^subflow(:|$)/.test(nodeType) || typeof runtime.nodes.getType(nodeType) !== 'function') {
                 for (cred in newCreds) {
                     if (newCreds.hasOwnProperty(cred)) {
                         if (newCreds[cred] === "__PWRD__") {
diff --git a/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js b/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js
index 5e4bc8094..6db0b867b 100644
--- a/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js
+++ b/test/unit/@node-red/runtime/lib/nodes/credentials_spec.js
@@ -36,102 +36,96 @@ describe('red/runtime/nodes/credentials', function() {
         index.clearRegistry();
     });
 
-    it('loads provided credentials',function(done) {
+    it('loads provided credentials',function() {
         credentials.init({
             log: log,
             settings: encryptionDisabledSettings
         });
 
-        credentials.load({"a":{"b":1,"c":2}}).then(function() {
-
+        return credentials.load({"a":{"b":1,"c":2}}).then(function() {
             credentials.get("a").should.have.property('b',1);
             credentials.get("a").should.have.property('c',2);
-
-            done();
         });
     });
-    it('adds a new credential',function(done) {
+    it('adds a new credential',function() {
         credentials.init({
             log: log,
             settings: encryptionDisabledSettings
         });
-        credentials.load({"a":{"b":1,"c":2}}).then(function() {
+        return credentials.load({"a":{"b":1,"c":2}}).then(function() {
             credentials.dirty().should.be.false();
             should.not.exist(credentials.get("b"));
-            credentials.add("b",{"foo":"bar"}).then(function() {
+            return credentials.add("b",{"foo":"bar"}).then(function() {
                 credentials.get("b").should.have.property("foo","bar");
                 credentials.dirty().should.be.true();
-                done();
             });
         });
     });
-    it('deletes an existing credential',function(done) {
+    it('deletes an existing credential',function() {
         credentials.init({
             log: log,
             settings: encryptionDisabledSettings
         });
-        credentials.load({"a":{"b":1,"c":2}}).then(function() {
+        return credentials.load({"a":{"b":1,"c":2}}).then(function() {
             credentials.dirty().should.be.false();
             credentials.delete("a");
             should.not.exist(credentials.get("a"));
             credentials.dirty().should.be.true();
-            done();
         });
     });
 
-    it('exports the credentials, clearing dirty flag', function(done) {
+    it('exports the credentials, clearing dirty flag', function() {
         credentials.init({
             log: log,
             settings: encryptionDisabledSettings
         });
         var creds = {"a":{"b":1,"c":2}};
-        credentials.load(creds).then(function() {
-            credentials.add("b",{"foo":"bar"}).then(function() {
-                credentials.dirty().should.be.true();
-                credentials.export().then(function(exported) {
-                    exported.should.eql(creds);
-                    credentials.dirty().should.be.false();
-                    done();
-                })
-            });
+        return credentials.load(creds).then(function() {
+            return credentials.add("b",{"foo":"bar"})
+        }).then(function() {
+            credentials.dirty().should.be.true();
+            return credentials.export().then(function(exported) {
+                exported.should.eql(creds);
+                credentials.dirty().should.be.false();
+            })
         });
     })
 
     describe("#clean",function() {
-        it("removes credentials of unknown nodes",function(done) {
+        it("removes credentials of unknown nodes",function() {
             credentials.init({
                 log: log,
-                settings: encryptionDisabledSettings
+                settings: encryptionDisabledSettings,
+                nodes: { getType: () => function(){} }
             });
             var creds = {"a":{"b":1,"c":2},"b":{"d":3}};
-            credentials.load(creds).then(function() {
+            return credentials.load(creds).then(function() {
                 credentials.dirty().should.be.false();
                 should.exist(credentials.get("a"));
                 should.exist(credentials.get("b"));
-                credentials.clean([{id:"b"}]).then(function() {
+                return credentials.clean([{id:"b"}]).then(function() {
                     credentials.dirty().should.be.true();
                     should.not.exist(credentials.get("a"));
                     should.exist(credentials.get("b"));
-                    done();
                 });
             });
         });
-        it("extracts credentials of known nodes",function(done) {
+        it("extracts credentials of known nodes",function() {
             credentials.init({
                 log: log,
-                settings: encryptionDisabledSettings
+                settings: encryptionDisabledSettings,
+                nodes: { getType: () => function(){} }
             });
             credentials.register("testNode",{"b":"text","c":"password"})
             var creds = {"a":{"b":1,"c":2}};
             var newConfig = [{id:"a",type:"testNode",credentials:{"b":"newBValue","c":"newCValue"}}];
-            credentials.load(creds).then(function() {
+            return credentials.load(creds).then(function() {
                 credentials.dirty().should.be.false();
-                credentials.clean(newConfig).then(function() {
+                return credentials.clean(newConfig).then(function() {
                     credentials.dirty().should.be.true();
                     credentials.get("a").should.have.property('b',"newBValue");
                     credentials.get("a").should.have.property('c',"newCValue");
                     should.not.exist(newConfig[0].credentials);
-                    done();
                 });
             });
         });
@@ -139,12 +133,13 @@ describe('red/runtime/nodes/credentials', function() {
 
     });
 
-    it('warns if a node has no credential definition', function(done) {
+    it('warns if a node has no credential definition', function() {
         credentials.init({
             log: log,
-            settings: encryptionDisabledSettings
+            settings: encryptionDisabledSettings,
+            nodes: { getType: () => function(){} }
         });
-        credentials.load({}).then(function() {
+        return credentials.load({}).then(function() {
             var node = {id:"node",type:"test",credentials:{
                 user1:"newUser",
                 password1:"newPassword"
@@ -154,14 +149,14 @@ describe('red/runtime/nodes/credentials', function() {
             log.warn.called.should.be.true();
             should.not.exist(node.credentials);
             log.warn.restore();
-            done();
         });
     })
 
     it('extract credential updates in the provided node', function(done) {
         credentials.init({
             log: log,
-            settings: encryptionDisabledSettings
+            settings: encryptionDisabledSettings,
+            nodes: { getType: () => function(){} }
         });
         var defintion = {
             user1:{type:"text"},
@@ -205,7 +200,8 @@ describe('red/runtime/nodes/credentials', function() {
     it('extract ignores node without credentials', function(done) {
         credentials.init({
             log: log,
-            settings: encryptionDisabledSettings
+            settings: encryptionDisabledSettings,
+            nodes: { getType: () => function(){} }
         });
         credentials.load({"node":{user1:"abc",password1:"123"}}).then(function() {
             var node = {id:"node",type:"test"};
@@ -233,7 +229,8 @@ describe('red/runtime/nodes/credentials', function() {
                     delete settings[key];
                     return Promise.resolve();
                 }
-            }
+            },
+            nodes: { getType: () => function(){} }
         }
         it('migrates to encrypted and generates default key', function(done) {
             settings = {};
@@ -341,7 +338,7 @@ describe('red/runtime/nodes/credentials', function() {
                 });
             });
         });
-        it('migrates from default key to user key', function(done) {
+        it('migrates from default key to user key', function() {
             settings = {
                 _credentialSecret: "e3a36f47f005bf2aaa51ce3fc6fcaafd79da8d03f2b1a9281f8fb0a285e6255a",
                 credentialSecret:  "aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbcccccccccccccddddddddddddeeeee"
@@ -349,21 +346,20 @@ describe('red/runtime/nodes/credentials', function() {
             // {"node":{user1:"abc",password1:"123"}}
             var cryptedFlows = {"$":"5b89d8209b5158a3c313675561b1a5b5phN1gDBe81Zv98KqS/hVDmc9EKvaKqRIvcyXYvBlFNzzzJtvN7qfw06i"};
             credentials.init(runtime);
-            credentials.load(cryptedFlows).then(function() {
+            return credentials.load(cryptedFlows).then(function() {
                 credentials.dirty().should.be.true();
                 should.exist(credentials.get("node"));
-                credentials.export().then(function(result) {
+                return credentials.export().then(function(result) {
                     result.should.have.a.property("$");
                     settings.should.not.have.a.property("_credentialSecret");
 
                     // reset everything - but with _credentialSecret still set
                     credentials.init(runtime);
                     // load the freshly encrypted version
-                    credentials.load(result).then(function() {
+                    return credentials.load(result).then(function() {
                         should.exist(credentials.get("node"));
                         credentials.get("node").should.have.a.property("user1","abc");
                         credentials.get("node").should.have.a.property("password1","123");
-                        done();
                     })
                 });
             });
@@ -459,7 +455,8 @@ describe('red/runtime/nodes/credentials', function() {
                     set: function(key,value) {
                         throw new Error();
                     }
-                }
+                },
+                nodes: { getType: () => function(){} }
             }
             // {"node":{user1:"abc",password1:"123"}}
             credentials.init(runtime);

From 6da8e92f20cd429bb284c56385894c4a222dc39c Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Wed, 28 Apr 2021 22:01:39 +0100
Subject: [PATCH 40/48] Fix inject node output tooltip extra property count

---
 .../node_modules/@node-red/nodes/core/common/20-inject.html     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html
index 22b98cc9c..717752377 100644
--- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html
+++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html
@@ -214,7 +214,7 @@
                 for (var i=0,l=props.length; i<l; i++) {
                     if (i > 0) lab += "\n";
                     if (i === 5) {
-                        lab += " + "+(props.length-4);
+                        lab += "... +"+(props.length-5);
                         break;
                     }
                     lab += props[i].p+": ";

From a150d8e2899f494781730123b369feedbaa3e762 Mon Sep 17 00:00:00 2001
From: Daniele <hanc2006@gmail.com>
Date: Wed, 28 Apr 2021 23:37:26 +0200
Subject: [PATCH 41/48] Update
 packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js

Thanks for the tip, I'll remember next time.

Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
---
 .../@node-red/editor-client/src/js/ui/common/treeList.js        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
index 5c9bdb5fe..df9592508 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
@@ -356,7 +356,7 @@
                             delete that._items[key];
                         }
                     }
-                    that._data = that._data.filter(data => data.id !== item.id)
+                    that._data = that._data.filter(function(data) { return data.id !== item.id})
                 }
             }
             item.treeList.insertChildAt = function(newItem,position,select) {

From cd3aba2b89e0d570c99210820e703aab2a360a32 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Thu, 29 Apr 2021 10:17:07 +0100
Subject: [PATCH 42/48] Allow nodes to access resolved theme files Fixes #2968

---
 .../@node-red/editor-api/lib/editor/theme.js  | 10 +++++++
 .../@node-red/nodes/core/common/21-debug.js   | 28 +++++++++++++++++--
 .../nodes/core/common/lib/debug/view.html     |  1 +
 3 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js
index 3ea4ede73..fc65753bf 100644
--- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js
+++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js
@@ -129,6 +129,14 @@ module.exports = {
             }
 
             themeContext.page.title = theme.page.title || themeContext.page.title;
+
+            // Store the resolved urls to these resources so nodes (such as Debug)
+            // can access them
+            theme.page._ = {
+                css: themeContext.page.css,
+                scripts: themeContext.page.scripts,
+                favicon: themeContext.page.favicon
+            }
         }
 
         if (theme.header) {
@@ -223,6 +231,7 @@ module.exports = {
                         themePlugin.path
                     );
                     themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
+                    theme.page._.css = cssFiles.concat(theme.page._.css || [])
                 }
                 if (themePlugin.scripts) {
                     const scriptFiles = serveFilesFromTheme(
@@ -232,6 +241,7 @@ module.exports = {
                         themePlugin.path
                     )
                     themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
+                    theme.page._.scripts = cssFiles.concat(theme.page._.scripts || [])
                 }
             }
             activeThemeInitialised = true;
diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.js b/packages/node_modules/@node-red/nodes/core/common/21-debug.js
index 0e00a8fff..8d2121580 100644
--- a/packages/node_modules/@node-red/nodes/core/common/21-debug.js
+++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.js
@@ -2,7 +2,8 @@ module.exports = function(RED) {
     "use strict";
     var util = require("util");
     var events = require("events");
-    //var path = require("path");
+    const fs = require("fs-extra");
+    const path = require("path");
     var debuglength = RED.settings.debugMaxLength || 1000;
     var useColors = RED.settings.debugUseColors || false;
     util.inspect.styles.boolean = "red";
@@ -249,11 +250,34 @@ module.exports = function(RED) {
         }
     });
 
+    let cachedDebugView;
+    RED.httpAdmin.get("/debug/view/view.html", function(req,res) {
+        if (!cachedDebugView) {
+            fs.readFile(path.join(__dirname,"lib","debug","view.html")).then(data => {
+                let customStyles = "";
+                try {
+                    let customStyleList = RED.settings.editorTheme.page._.css || [];
+                    customStyleList.forEach(style => {
+                        customStyles += `<link rel="stylesheet" href="../../${style}">\n`
+                    })
+                } catch(err) {}
+                cachedDebugView = data.toString().replace("<!-- INSERT-THEME-CSS -->",customStyles)
+                res.set('Content-Type', 'text/html');
+                res.send(cachedDebugView).end();
+            }).catch(err => {
+                res.sendStatus(404);
+            })
+        } else {
+            res.send(cachedDebugView).end();
+        }
+
+    });
+
     // As debug/view/debug-utils.js is loaded via <script> tag, it won't get
     // the auth header attached. So do not use RED.auth.needsPermission here.
     RED.httpAdmin.get("/debug/view/*",function(req,res) {
         var options = {
-            root: __dirname + '/lib/debug/',
+            root: path.join(__dirname,"lib","debug"),
             dotfiles: 'deny'
         };
         res.sendFile(req.params[0], options);
diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/view.html b/packages/node_modules/@node-red/nodes/core/common/lib/debug/view.html
index 3f0bdcfa1..5a07e3779 100644
--- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/view.html
+++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/view.html
@@ -2,6 +2,7 @@
 <head>
     <link rel="stylesheet" href="../../red/style.min.css">
     <link rel="stylesheet" href="../../vendor/font-awesome/css/font-awesome.min.css">
+    <!-- INSERT-THEME-CSS -->
     <title>Node-RED Debug Tools</title>
 </head>
 <body class="red-ui-editor red-ui-debug-window">

From 9ee8c1c7916b34430876ba392e33f9302241d735 Mon Sep 17 00:00:00 2001
From: Dave Conway-Jones <conway@uk.ibm.com>
Date: Thu, 29 Apr 2021 10:36:13 +0100
Subject: [PATCH 43/48] Give delay node random mina nd max more space so you
 can see complete value

---
 .../@node-red/nodes/core/function/89-delay.html             | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.html b/packages/node_modules/@node-red/nodes/core/function/89-delay.html
index f157c6414..3aabded7c 100644
--- a/packages/node_modules/@node-red/nodes/core/function/89-delay.html
+++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.html
@@ -45,9 +45,9 @@
         </div>
         <div id="random-details" class="form-row">
             <label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
-            <input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:30px !important">
-            <span data-i18n="delay.and"></span>
-            <input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:30px !important">
+            <input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:50px !important">
+            &nbsp;<span data-i18n="delay.and"></span>&nbsp;
+            <input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:50px !important">
             <select id="node-input-randomUnits" style="width:140px !important">
               <option value="milliseconds" data-i18n="delay.milisecs"></option>
               <option value="seconds" data-i18n="delay.secs"></option>

From b0e349b215afba75c255c98b037874a798bb8920 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Thu, 29 Apr 2021 11:22:22 +0100
Subject: [PATCH 44/48] Update for 1.3.4

---
 CHANGELOG.md                                  | 25 +++++++++++++++++++
 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, 42 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 744ff06e7..3e92b17cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+### 1.3.4 Maintenance Release
+
+Editor
+ - Allow nodes to access resolved theme files Fixes #2968
+ - Fix importing node to currently flow rather than match its old z value
+ - Don't let 'escape' whilst moving nodes interrupt things Fixes #2960
+ - Sort context stores in TypedInput and ensure default first Fixes #2954
+ - Fix margin between nodes on palette (#2947) @kazuhitoyokoi
+ - Ensure typedInput option is selected in dropdown menu Part of #2945
+ - Ensure typedInput without value has focus class removed Closes #2945
+
+Runtime
+ - Handle subflow modules that contain subflows
+ - Timeout http upgrade requests that are not otherwise handled Fixes #2956
+ - Fix error on auto commit for no flow change (#2957) @HiroyasuNishiyama
+
+Nodes
+
+ - CSV: Fix CSV handling of special chars as separators
+ - Delay: Give delay node random mina nd max more space so you can see complete value
+ - Exec: fix grunt fail on exec node test (#2964) @HiroyasuNishiyama
+ - Function: Ensure function expand button is above vertical scrollbar Fixes #2955
+ - Inject: Fix inject node output tooltip extra property count
+
+
 ### 1.3.3: Maintenance Release
 
 Editor
diff --git a/package.json b/package.json
index 9c336d690..e6162302c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "node-red",
-    "version": "1.3.3",
+    "version": "1.3.4",
     "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 25b217c58..185e1e36a 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.3.3",
+    "version": "1.3.4",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,8 +16,8 @@
         }
     ],
     "dependencies": {
-        "@node-red/util": "1.3.3",
-        "@node-red/editor-client": "1.3.3",
+        "@node-red/util": "1.3.4",
+        "@node-red/editor-client": "1.3.4",
         "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 785f2ad2c..44f9a4fe8 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.3.3",
+    "version": "1.3.4",
     "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 524f0d2e8..52dfeb8f2 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.3.3",
+    "version": "1.3.4",
     "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 b0f943ab6..cb59f50be 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.3.3",
+    "version": "1.3.4",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,7 +16,7 @@
         }
     ],
     "dependencies": {
-        "@node-red/util": "1.3.3",
+        "@node-red/util": "1.3.4",
         "semver": "6.3.0",
         "tar": "6.1.0",
         "uglify-js": "3.13.3"
diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json
index 5d7e44b2e..abfcaa1e8 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.3.3",
+    "version": "1.3.4",
     "license": "Apache-2.0",
     "main": "./lib/index.js",
     "repository": {
@@ -16,8 +16,8 @@
         }
     ],
     "dependencies": {
-        "@node-red/registry": "1.3.3",
-        "@node-red/util": "1.3.3",
+        "@node-red/registry": "1.3.4",
+        "@node-red/util": "1.3.4",
         "async-mutex": "0.3.1",
         "clone": "2.1.2",
         "express": "4.17.1",
diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json
index 64603d084..15d457309 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.3.3",
+    "version": "1.3.4",
     "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 c42a97fe6..a770e68e2 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.3.3",
+    "version": "1.3.4",
     "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.3.3",
-        "@node-red/runtime": "1.3.3",
-        "@node-red/util": "1.3.3",
-        "@node-red/nodes": "1.3.3",
+        "@node-red/editor-api": "1.3.4",
+        "@node-red/runtime": "1.3.4",
+        "@node-red/util": "1.3.4",
+        "@node-red/nodes": "1.3.4",
         "basic-auth": "2.0.1",
         "bcryptjs": "2.4.3",
         "express": "4.17.1",

From 1eb8f9ad9751f97c0d3ca4db76f240fbe822fba4 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Thu, 29 Apr 2021 14:01:40 +0100
Subject: [PATCH 45/48] Update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e92b17cb..cc175a79b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ Editor
  - Fix margin between nodes on palette (#2947) @kazuhitoyokoi
  - Ensure typedInput option is selected in dropdown menu Part of #2945
  - Ensure typedInput without value has focus class removed Closes #2945
+ - TreeList: Fix remove item when depth=0 and wrong gutter calc (#2967) @hanc2006
 
 Runtime
  - Handle subflow modules that contain subflows

From 9886af3cec24cc2ff4b10182dc274a546915ae1e Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Thu, 29 Apr 2021 14:09:21 +0100
Subject: [PATCH 46/48] Fix jshint error in treeList

---
 .../editor-client/src/js/ui/common/treeList.js         | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
index df9592508..7f7eba1ba 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js
@@ -350,10 +350,12 @@
                 delete that._items[item.id];
                 if(item.depth === 0) {
                     for(var key in that._items) {
-                        var child = that._items[key];
-                        if(child.parent && child.parent.id === item.id) {
-                            delete that._items[key].treeList;
-                            delete that._items[key];
+                        if (that._items.hasOwnProperty(key)) {
+                            var child = that._items[key];
+                            if(child.parent && child.parent.id === item.id) {
+                                delete that._items[key].treeList;
+                                delete that._items[key];
+                            }
                         }
                     }
                     that._data = that._data.filter(function(data) { return data.id !== item.id})

From 1af21735a9c185df8595830a34473077c201e5ff Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Thu, 29 Apr 2021 15:32:26 +0100
Subject: [PATCH 47/48] Fix theme handling when no editorTheme.page setting

---
 packages/node_modules/@node-red/editor-api/lib/editor/theme.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js
index fc65753bf..de0106dbe 100644
--- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js
+++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js
@@ -231,6 +231,7 @@ module.exports = {
                         themePlugin.path
                     );
                     themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
+                    theme.page = theme.page || {_:{}}
                     theme.page._.css = cssFiles.concat(theme.page._.css || [])
                 }
                 if (themePlugin.scripts) {
@@ -241,6 +242,7 @@ module.exports = {
                         themePlugin.path
                     )
                     themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
+                    theme.page = theme.page || {_:{}}
                     theme.page._.scripts = cssFiles.concat(theme.page._.scripts || [])
                 }
             }

From db0ff748576212d32ab46b47b560126ffa57f4e2 Mon Sep 17 00:00:00 2001
From: Nick O'Leary <nick.oleary@gmail.com>
Date: Tue, 4 May 2021 11:12:55 +0100
Subject: [PATCH 48/48] Reduce code duplication around node/label generation

---
 .../editor-client/src/js/ui/clipboard.js      | 28 +-----------
 .../editor-client/src/js/ui/tab-help.js       |  5 +--
 .../src/js/ui/tab-info-outliner.js            | 33 ++------------
 .../editor-client/src/js/ui/utils.js          | 20 +++++++--
 .../editor-client/src/sass/palette.scss       | 44 +++++++++++++++++++
 .../editor-client/src/sass/tab-info.scss      |  8 +++-
 6 files changed, 74 insertions(+), 64 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 5b831a942..caffe5793 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
@@ -1186,22 +1186,6 @@ RED.clipboard = (function() {
         }
     }
 
-    function getNodeLabelText(n) {
-        var label = n.name || n.type+": "+n.id;
-        if (n._def.label) {
-            try {
-                label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
-            } catch(err) {
-                console.log("Definition error: "+n.type+".label",err);
-            }
-        }
-        var newlineIndex = label.indexOf("\\n");
-        if (newlineIndex > -1) {
-            label = label.substring(0,newlineIndex)+"...";
-        }
-        return label;
-    }
-
     function getFlowLabel(n) {
         n = JSON.parse(JSON.stringify(n));
         n._def = RED.nodes.getType(n.type) || {};
@@ -1227,16 +1211,8 @@ RED.clipboard = (function() {
         if (n._def) {
             n._ = n._def._;
         }
-        var div = $('<div>',{class:"red-ui-info-outline-item"});
-        RED.utils.createNodeIcon(n).appendTo(div);
-        var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
-        var labelText = getNodeLabelText(n);
-        var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
-        if (labelText) {
-            label.text(labelText)
-        } else {
-            label.html(n.type)
-        }
+        var div = $('<div>',{class:"red-ui-node-list-item"});
+        RED.utils.createNodeIcon(n,true).appendTo(div);
         return div;
     }
 
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 cc86de358..4852ed359 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
@@ -230,10 +230,9 @@ RED.sidebar.help = (function() {
     }
 
     function getNodeLabel(n) {
-        var div = $('<div>',{class:"red-ui-info-outline-item"});
+        var div = $('<div>',{class:"red-ui-node-list-item"});
         RED.utils.createNodeIcon(n).appendTo(div);
-        var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
-        $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).text(n.name||n.type).appendTo(contentDiv);
+        $('<div>',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(div);
         return div;
     }
 
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js
index 891f9f7dc..a1ae6e48b 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js
@@ -73,36 +73,11 @@ RED.sidebar.info.outliner = (function() {
         return item;
     }
 
-    function getNodeLabelText(n) {
-        var label = n.name || n.type+": "+n.id;
-        if (n._def.label) {
-            try {
-                label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
-            } catch(err) {
-                console.log("Definition error: "+n.type+".label",err);
-            }
-        }
-        var newlineIndex = label.indexOf("\\n");
-        if (newlineIndex > -1) {
-            label = label.substring(0,newlineIndex)+"...";
-        }
-        return label;
-    }
-
     function getNodeLabel(n) {
-        var div = $('<div>',{class:"red-ui-info-outline-item"});
-        RED.utils.createNodeIcon(n).appendTo(div);
-        var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
-        var labelText = getNodeLabelText(n);
-        var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
-        if (labelText) {
-            label.text(labelText)
-        } else {
-            label.html("&nbsp;")
-        }
-
+        var div = $('<div>',{class:"red-ui-node-list-item red-ui-info-outline-item"});
+        RED.utils.createNodeIcon(n, true).appendTo(div);
+        div.find(".red-ui-node-label").addClass("red-ui-info-outline-item-label")
         addControls(n, div);
-
         return div;
     }
 
@@ -430,7 +405,7 @@ RED.sidebar.info.outliner = (function() {
         var existingObject = objects[n.id];
         var parent = n.g||n.z||"__global__";
 
-        var nodeLabelText = getNodeLabelText(n);
+        var nodeLabelText = RED.utils.getNodeLabel(n,n.name || (n.type+": "+n.id));
         if (nodeLabelText) {
             existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText);
         } else {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
index f81c98748..bb17ea395 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
@@ -1125,9 +1125,9 @@ RED.utils = (function() {
         imageIconElement.css("backgroundImage", "url("+iconUrl+")");
     }
 
-    function createNodeIcon(node) {
+    function createNodeIcon(node, includeLabel) {
         var def = node._def;
-        var nodeDiv = $('<div>',{class:"red-ui-search-result-node"})
+        var nodeDiv = $('<div>',{class:"red-ui-node-icon"})
         if (node.type === "_selection_") {
             nodeDiv.addClass("red-ui-palette-icon-selection");
         } else if (node.type === "group") {
@@ -1147,8 +1147,20 @@ RED.utils = (function() {
         }
 
         var icon_url = RED.utils.getNodeIcon(def,node);
-        var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
-        RED.utils.createIconElement(icon_url, iconContainer, true);
+        RED.utils.createIconElement(icon_url, nodeDiv, true);
+
+        if (includeLabel) {
+            var container = $('<span>');
+            nodeDiv.appendTo(container);
+            var labelText = RED.utils.getNodeLabel(node,node.name || (node.type+": "+node.id));
+            var label = $('<div>',{class:"red-ui-node-label"}).appendTo(container);
+            if (labelText) {
+                label.text(labelText)
+            } else {
+                label.html("&nbsp;")
+            }
+            return container;
+        }
         return nodeDiv;
     }
 
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 2855ee494..1b27f7a2a 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
@@ -229,3 +229,47 @@
         left: 1px;
     }
 }
+
+////////////////
+
+.red-ui-node-list-item {
+    display: inline-block;
+    padding: 0;
+    font-size: 13px;
+    border: none;
+}
+.red-ui-node-icon {
+    display: inline-block;
+    float:left;
+    width: 24px;
+    height: 20px;
+    margin-top: 1px;
+    // width: 30px;
+    // height: 25px;
+    border-radius: 3px;
+    border: 1px solid $node-border;
+    background-position: 5% 50%;
+    background-repeat: no-repeat;
+    background-size: contain;
+    position: relative;
+    background-color: $node-icon-background-color;
+    text-align: center;
+
+    .red-ui-palette-icon {
+        width: 20px;
+    }
+
+    .red-ui-palette-icon-fa {
+        font-size: 14px;
+        position: relative;
+        top: -1px;
+        left: 0px;
+    }
+}
+
+.red-ui-node-label {
+    margin-left: 32px;
+    line-height: 23px;
+    white-space: nowrap;
+    color: $secondary-text-color;
+}
diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss
index 42d5462f7..07bbbae1c 100644
--- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss
+++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss
@@ -326,13 +326,17 @@ div.red-ui-info-table {
         border-bottom: 1px solid $secondary-border-color;
     }
 }
-.red-ui-info-outline,.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list, #red-ui-clipboard-dialog-export-tab-clipboard-preview {
+.red-ui-info-outline,
+// TODO: remove these classes for 2.0. Keeping in 1.x for backwards compatibility
+// of theme generators.
+.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list, #red-ui-clipboard-dialog-export-tab-clipboard-preview 
+ {
     .red-ui-info-outline-item {
         display: inline-block;
         padding: 0;
         font-size: 13px;
         border: none;
-        .red-ui-palette-icon-fa {
+        &:not(.red-ui-node-list-item) .red-ui-palette-icon-fa {
             position: relative;
             top: 1px;
             left: 0px;