Repository: node-red/node-red (js)

Issue:
Function Node Editor Bg gone to White (again)
### Current Behavior

After update 3.0 the Function Node Editor background is White (not dark)
![image](https://user-images.githubusercontent.com/51385971/179417327-dc3a39b4-ae34-4eb1-8b61-e1a59c3b09f7.png)


### Expected Behavior

_No response_

### Steps To Reproduce

_No response_

### Example flow

```
paste your flow here
```


### Environment

- Node-RED version: 3.0
- Node.js version: v16.15.0
- npm version:
- Platform/OS: Home Assistant OS 8.2
- Browser: Chrome 103.0.5060.114


Pull request description:
Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js
fixes #3779

## Types of changes

- [x] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)

## Proposed changes

[Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js](https://github.com/node-red/node-red/commit/bc7852c1cc0889ec9f4686d0efca00592a99a0d0)

## Checklist

- [x] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [x] I have run `grunt` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality


Patch:
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 7be8868d3..c21a7e6e7 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
@@ -327,9 +327,8 @@ module.exports = {
                         themeContext.header.url = themePlugin.header.url || themeContext.header.url
                     }
                 }
-                if(theme.codeEditor) {
-                    theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
-                }
+                theme.codeEditor = theme.codeEditor || {}
+                theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options);
             }
             activeThemeInitialised = true;
         }
diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js
index 7544434b7..55446418b 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/red.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/red.js
@@ -766,7 +766,7 @@ var RED = (function() {
         $('<div id="red-ui-header-shade" class="hide"></div>').appendTo(header);
         $('<div id="red-ui-main-container" class="red-ui-sidebar-closed hide">'+
             '<div id="red-ui-workspace"></div>'+
-            '<div id="red-ui-editor-stack" tabindex="-1"></div>'+
+            '<div id="red-ui-editor-stack"></div>'+
             '<div id="red-ui-palette"></div>'+
             '<div id="red-ui-sidebar"></div>'+
             '<div id="red-ui-sidebar-separator"></div>'+
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 ef1be05bc..549479d85 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
@@ -90,10 +90,10 @@
                         optEl.append(generateSpans(srcMatch));
                         optEl.appendTo(element);
                     }
-                    matches.push({
-                        value: optVal,
-                        label: element,
-                        i: (valMatch.found ? valMatch.index : srcMatch.index)
+                    matches.push({ 
+                        value: optVal, 
+                        label: element, 
+                        i: (valMatch.found ? valMatch.index : srcMatch.index) 
                     });
                 }
             })
@@ -501,7 +501,7 @@
                 this.options.types = this.options.types||Object.keys(allOptions);
             }
 
-            this.selectTrigger = $('<button type="button" class="red-ui-typedInput-type-select" tabindex="0"></button>').prependTo(this.uiSelect);
+            this.selectTrigger = $('<button class="red-ui-typedInput-type-select" tabindex="0"></button>').prependTo(this.uiSelect);
             $('<i class="red-ui-typedInput-icon fa fa-caret-down"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
 
             this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger);
@@ -570,7 +570,7 @@
             })
 
             // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
-            this.optionSelectTrigger = $('<button type="button" tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
+            this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
             this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
             // RED.popover.tooltip(this.optionSelectLabel,function() {
             //     return that.optionValue;
@@ -591,7 +591,7 @@
                 that.uiSelect.addClass('red-ui-typedInput-focus');
             });
 
-            this.optionExpandButton = $('<button type="button" tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
+            this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
             this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
 
             this.type(this.typeField.val() || this.options.default||this.typeList[0].value);
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js
index a1fdb402a..66ae2b943 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js
@@ -44,10 +44,14 @@ RED.contextMenu = (function () {
         const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
         const offset = $("#red-ui-workspace-chart").offset()
 
-        // addX/addY must be the position in the workspace accounting for both scroll and scale
-        // The +5 is because we display the contextMenu -5,-5 to actual click position
-        let addX = (options.x + 5 - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / RED.view.scale()
-        let addY = (options.y + 5 - offset.top + $("#red-ui-workspace-chart").scrollTop()) / RED.view.scale()
+        let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
+        let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
+
+        if (RED.view.snapGrid) {
+            const gridSize = RED.view.gridSize()
+            addX = gridSize * Math.floor(addX / gridSize)
+            addY = gridSize * Math.floor(addY / gridSize)
+        }
 
         const menuItems = [
             { onselect: 'core:show-action-list', onpostselect: function () { } },
@@ -63,10 +67,6 @@ RED.contextMenu = (function () {
                                 splice: isSingleLink ? selection.links[0] : undefined,
                                 // spliceMultiple: isMultipleLinks
                             })
-                        },
-                        onpostselect: function() {
-                            // ensure quick add dialog search input has focus
-                            $('#red-ui-type-search-input').trigger('focus')
                         }
                     },
                     (hasLinks) ? { // has least 1 wire selected
@@ -144,7 +144,7 @@ RED.contextMenu = (function () {
             ($(window).width() -MENU_WIDTH)) {
             direction = "left";
         }
-
+        
         menu = RED.menu.init({
             direction: direction,
             onpreselect: function() {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
index be114c4a8..fb4c200f5 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
@@ -1105,10 +1105,6 @@ RED.editor = (function() {
                 if (editing_node) {
                     RED.sidebar.info.refresh(editing_node);
                     RED.sidebar.help.show(editing_node.type, false);
-                    //ensure focused element is NOT body (for keyboard scope to operate correctly)
-                    if (document.activeElement.tagName === 'BODY') {
-                        $('#red-ui-editor-stack').trigger('focus')
-                    }
                 }
             }
         }
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
index 497116125..701e3da44 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
@@ -764,7 +764,7 @@ RED.editor.codeEditor.monaco = (function() {
 
         
         if(!options.stateId && options.stateId !== false) {
-            options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title || "").split("/").pop());
+            options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop());
         }
         var el = options.element || $("#"+options.id)[0];
         var toolbarRow = $("<div>").appendTo(el);
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js
index f9cc79f08..217fb5a4e 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js
@@ -187,7 +187,7 @@ RED.search = (function() {
                         }
                         if (flags.hasOwnProperty("unused")) {
                             var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
-                                           (isConfigNode && node.node.users.length === 0 && node.node._def.hasUsers !== false)
+                                           (isConfigNode && node.node.users.length === 0)
                             if (flags.unused !== isUnused) {
                                 continue;
                             }
@@ -538,7 +538,7 @@ RED.search = (function() {
                 $(previousActiveElement).trigger("focus");
             }
             previousActiveElement = null;
-        }
+        } 
         if(!keepSearchToolbar) {
             clearActiveSearch();
         }
@@ -630,7 +630,7 @@ RED.search = (function() {
         $("#red-ui-sidebar-shade").on('mousedown',hide);
 
         $("#red-ui-view-searchtools-close").on("click", function close() {
-            clearActiveSearch();
+            clearActiveSearch();            
             updateSearchToolbar();
         });
         $("#red-ui-view-searchtools-close").trigger("click");
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js
index 989cb78ab..fc5b8e99e 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js
@@ -269,8 +269,8 @@ RED.typeSearch = (function() {
         moveCallback = opts.move;
         RED.events.emit("type-search:open");
         //shade.show();
-        if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
-            opts.y = opts.y - 275;
+        if ($("#red-ui-main-container").height() - opts.y - 150 < 0) {
+            opts.y = opts.y - 235;
         }
         dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
         searchResultsDiv.slideDown(300);
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 17e1e87e3..8c7ea22a3 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
@@ -1071,15 +1071,12 @@ RED.view = (function() {
             RED.view.redraw();
         }
 
-        // `point` is the place in the workspace the mouse has clicked.
-        //  This takes into account scrolling and scaling of the workspace.
         var ox = point[0];
         var oy = point[1];
 
-        // Need to map that to browser location to position the pop-up
         const offset = $("#red-ui-workspace-chart").offset()
-        var clientX = (ox * scaleFactor) + offset.left - $("#red-ui-workspace-chart").scrollLeft()
-        var clientY = (oy * scaleFactor) + offset.top - $("#red-ui-workspace-chart").scrollTop()
+        var clientX = ox + offset.left - $("#red-ui-workspace-chart").scrollLeft()
+        var clientY = oy + offset.top - $("#red-ui-workspace-chart").scrollTop()
 
         if (RED.settings.get("editor").view['view-snap-grid']) {
             // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red')
@@ -4909,7 +4906,7 @@ RED.view = (function() {
                         if (d._def.button) {
                             var buttonEnabled = isButtonEnabled(d);
                             this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled);
-                            if (RED.runtime && RED.runtime.started !== undefined) {
+                            if (RED.runtime && Object.hasOwn(RED.runtime,'started')) {
                                 this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started);
                             }
 
diff --git a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js
index b0debf96e..cbf9fc9d7 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js
@@ -100,13 +100,9 @@ function buildDiagnosticReport(scope, callback) {
             version: os.version(),
         },
         runtime: {
-            version: runtime.settings.version,
             isStarted: runtime.isStarted(),
-            flows: {
-                state: runtime.flows && runtime.flows.state(),
-                started: runtime.flows && runtime.flows.started,
-            },
             modules: modules,
+            version: runtime.settings.version,
             settings: {
                 available: runtime.settings.available(),
                 apiMaxLength: runtime.settings.apiMaxLength || "UNSET",
@@ -118,11 +114,6 @@ function buildDiagnosticReport(scope, callback) {
                 flowFile: runtime.settings.flowFile || "UNSET",
                 mqttReconnectTime: runtime.settings.mqttReconnectTime || "UNSET",
                 serialReconnectTime: runtime.settings.serialReconnectTime || "UNSET",
-                socketReconnectTime: runtime.settings.socketReconnectTime || "UNSET",
-                socketTimeout: runtime.settings.socketTimeout || "UNSET",
-                tcpMsgQueueSize: runtime.settings.tcpMsgQueueSize || "UNSET",
-                inboundWebSocketTimeout: runtime.settings.inboundWebSocketTimeout || "UNSET",
-                runtimeState: runtime.settings.runtimeState || "UNSET",
 
                 adminAuth: runtime.settings.adminAuth ? "SET" : "UNSET",
 
@@ -140,7 +131,6 @@ function buildDiagnosticReport(scope, callback) {
                 uiHost: runtime.settings.uiHost ? "SET" : "UNSET",
                 uiPort: runtime.settings.uiPort ? "SET" : "UNSET",
                 userDir: runtime.settings.userDir ? "SET" : "UNSET",
-                nodesDir: runtime.settings.nodesDir && runtime.settings.nodesDir.length ? "SET" : "UNSET",
             }
         }
     }


Test patch:
diff --git a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
index 136e4826c..07e499344 100644
--- a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
+++ b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
@@ -33,11 +33,6 @@ describe("runtime-api/diagnostics", function() {
                     flowFile: "flows.json",
                     mqttReconnectTime: 321,
                     serialReconnectTime: 432,
-                    socketReconnectTime: 2222,
-                    socketTimeout: 3333,
-                    tcpMsgQueueSize: 4444,
-                    inboundWebSocketTimeout: 5555,
-                    runtimeState: {enabled: true, ui: false},
                     adminAuth: {},//should be sanitised to "SET"
                     httpAdminRoot: "/admin/root/",
                     httpAdminCors: {},//should be sanitised to "SET"
@@ -50,7 +45,6 @@ describe("runtime-api/diagnostics", function() {
                     uiHost: "something.secret.com",//should be sanitised to "SET"
                     uiPort: 1337,//should be sanitised to "SET"
                     userDir: "/var/super/secret/",//should be sanitised to "SET",
-                    nodesDir: "/var/super/secret/",//should be sanitised to "SET",
                     contextStorage: {
                         default    : { module: "memory" },
                         file: { module: "localfilesystem" },
@@ -79,9 +73,8 @@ describe("runtime-api/diagnostics", function() {
 
                 //result.runtime.xxxxx
                 const runtimeCount = Object.keys(result.runtime).length;
-                runtimeCount.should.eql(5);//ensure 5 keys are present in runtime 
+                runtimeCount.should.eql(4);//ensure no more than 4 keys are present in runtime 
                 result.runtime.should.have.property('isStarted',true)
-                result.runtime.should.have.property('flows')
                 result.runtime.should.have.property('modules').type("object");
                 result.runtime.should.have.property('settings').type("object");
                 result.runtime.should.have.property('version','7.7.7');
@@ -94,7 +87,7 @@ describe("runtime-api/diagnostics", function() {
 
                 //result.runtime.settings.xxxxx
                 const settingsCount = Object.keys(result.runtime.settings).length;
-                settingsCount.should.eql(27);//ensure no more than the 21 settings listed below are present in the settings object
+                settingsCount.should.eql(21);//ensure no more than the 21 settings listed below are present in the settings object
                 result.runtime.settings.should.have.property('available',true);
                 result.runtime.settings.should.have.property('apiMaxLength', "UNSET");//deliberately disabled to ensure UNSET is returned
                 result.runtime.settings.should.have.property('debugMaxLength', 1111);
@@ -103,11 +96,6 @@ describe("runtime-api/diagnostics", function() {
                 result.runtime.settings.should.have.property('flowFile', "flows.json");
                 result.runtime.settings.should.have.property('mqttReconnectTime', 321);
                 result.runtime.settings.should.have.property('serialReconnectTime', 432);
-                result.runtime.settings.should.have.property('socketReconnectTime', 2222);
-                result.runtime.settings.should.have.property('socketTimeout', 3333);
-                result.runtime.settings.should.have.property('tcpMsgQueueSize', 4444);
-                result.runtime.settings.should.have.property('inboundWebSocketTimeout', 5555);
-                result.runtime.settings.should.have.property('runtimeState', {enabled: true, ui: false});
                 result.runtime.settings.should.have.property("adminAuth", "SET"); //should be sanitised to "SET"
                 result.runtime.settings.should.have.property("httpAdminCors", "SET"); //should be sanitised to "SET"
                 result.runtime.settings.should.have.property('httpAdminRoot', "/admin/root/");
@@ -121,7 +109,6 @@ describe("runtime-api/diagnostics", function() {
                 result.runtime.settings.should.have.property("uiPort", "SET"); //should be sanitised to "SET"
                 result.runtime.settings.should.have.property("userDir", "SET"); //should be sanitised to "SET"
                 result.runtime.settings.should.have.property('contextStorage').type("object");
-                result.runtime.settings.should.have.property('nodesDir', "SET")
 
                 //result.runtime.settings.contextStorage.xxxxx
                 const contextCount = Object.keys(result.runtime.settings.contextStorage).length;