diff --git a/.travis.yml b/.travis.yml index e99bc9270..323f0948a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ addons: language: node_js matrix: include: + - node_js: "16" + script: + - ./node_modules/.bin/grunt no-coverage - node_js: "14" script: - ./node_modules/.bin/grunt && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb1464cc..cc175a79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,69 @@ +### 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 + - TreeList: Fix remove item when depth=0 and wrong gutter calc (#2967) @hanc2006 + +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 + + - 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 + +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 diff --git a/Gruntfile.js b/Gruntfile.js index 742b564da..5d9b8ba5c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,7 @@ var path = require("path"); var fs = require("fs-extra"); -var sass = require("node-sass"); +var sass = require("sass"); module.exports = function(grunt) { @@ -137,6 +137,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js", "packages/node_modules/@node-red/editor-client/src/js/red.js", "packages/node_modules/@node-red/editor-client/src/js/events.js", + "packages/node_modules/@node-red/editor-client/src/js/hooks.js", "packages/node_modules/@node-red/editor-client/src/js/i18n.js", "packages/node_modules/@node-red/editor-client/src/js/settings.js", "packages/node_modules/@node-red/editor-client/src/js/user.js", @@ -487,7 +488,8 @@ module.exports = function(grunt) { ], options: { destination: 'docs', - configure: './jsdoc.json' + configure: './jsdoc.json', + fred: "hi there" } }, _editor: { diff --git a/package.json b/package.json index 573c76ee1..249ea0bb0 100644 --- a/package.json +++ b/package.json @@ -106,8 +106,8 @@ "minami": "1.2.3", "mocha": "8.3.2", "node-red-node-test-helper": "^0.2.7", - "node-sass": "^5.0.0", "nodemon": "2.0.7", + "sass": "1.32.12", "should": "13.2.3", "sinon": "10.0.1", "stoppable": "^1.1.0", diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js index 3d49a7e8a..ac6c6f701 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js @@ -17,9 +17,8 @@ module.exports = { }) } else { opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); - if (/[^a-z\-\*]/i.test(opts.lang)) { - res.json({}); - return; + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; } runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) { res.send(configs); @@ -32,9 +31,8 @@ module.exports = { lang: req.query.lng, req: apiUtils.getRequestLogObject(req) } - if (/[^a-z\-\*]/i.test(opts.lang)) { - res.json({}); - return; + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; } runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) { res.json(result); 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); 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 8fb7df9d4..0953a0adc 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 @@ -130,6 +130,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) { @@ -224,6 +232,8 @@ 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) { const scriptFiles = serveFilesFromTheme( @@ -233,6 +243,8 @@ module.exports = { themePlugin.path ) themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || []) + theme.page = theme.page || {_:{}} + theme.page._.scripts = cssFiles.concat(theme.page._.scripts || []) } } activeThemeInitialised = true; 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/editor-client/src/js/hooks.js b/packages/node_modules/@node-red/editor-client/src/js/hooks.js new file mode 100644 index 000000000..096c8e5b5 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/hooks.js @@ -0,0 +1,156 @@ +RED.hooks = (function() { + + var VALID_HOOKS = [ + + ] + + var hooks = { } + var labelledHooks = { } + + function add(hookId, callback) { + var parts = hookId.split("."); + var id = parts[0], label = parts[1]; + + // if (VALID_HOOKS.indexOf(id) === -1) { + // throw new Error("Invalid hook '"+id+"'"); + // } + if (label && labelledHooks[label] && labelledHooks[label][id]) { + throw new Error("Hook "+hookId+" already registered") + } + var hookItem = {cb:callback, previousHook: null, nextHook: null } + + var tailItem = hooks[id]; + if (tailItem === undefined) { + hooks[id] = hookItem; + } else { + while(tailItem.nextHook !== null) { + tailItem = tailItem.nextHook + } + tailItem.nextHook = hookItem; + hookItem.previousHook = tailItem; + } + + if (label) { + labelledHooks[label] = labelledHooks[label]||{}; + labelledHooks[label][id] = hookItem; + } + } + function remove(hookId) { + var parts = hookId.split("."); + var id = parts[0], label = parts[1]; + if ( !label) { + throw new Error("Cannot remove hook without label: "+hookId) + } + if (labelledHooks[label]) { + if (id === "*") { + // Remove all hooks for this label + var hookList = Object.keys(labelledHooks[label]); + for (var i=0;i -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 = $('
',{class:"red-ui-info-outline-item"}); - RED.utils.createNodeIcon(n).appendTo(div); - var contentDiv = $('
',{class:"red-ui-search-result-description"}).appendTo(div); - var labelText = getNodeLabelText(n); - var label = $('
',{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 = $('
',{class:"red-ui-node-list-item"}); + RED.utils.createNodeIcon(n,true).appendTo(div); return div; } @@ -1284,22 +1260,27 @@ RED.clipboard = (function() { hideDropTarget(); }) .on("drop",function(event) { - if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { - var data = event.originalEvent.dataTransfer.getData("text/plain"); - data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); - importNodes(data); - } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { - var files = event.originalEvent.dataTransfer.files; - if (files.length === 1) { - var file = files[0]; - var reader = new FileReader(); - reader.onload = (function(theFile) { - return function(e) { - importNodes(e.target.result); - }; - })(file); - reader.readAsText(file); + try { + if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { + var data = event.originalEvent.dataTransfer.getData("text/plain"); + data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); + importNodes(data); + } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { + var files = event.originalEvent.dataTransfer.files; + if (files.length === 1) { + var file = files[0]; + var reader = new FileReader(); + reader.onload = (function(theFile) { + return function(e) { + importNodes(e.target.result); + }; + })(file); + reader.readAsText(file); + } } + } catch(err) { + // Ensure any errors throw above doesn't stop the drop target from + // being hidden. } hideDropTarget(); event.preventDefault(); 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($("").text(" " + button.label)); } }); - + if (this.element.css("position") === "absolute") { ["top","left","bottom","right"].forEach(function(s) { var v = that.element.css(s); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index baaac7a8b..ee6ea1960 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -100,7 +100,22 @@ RED.tabs = (function() { if (options.scrollable) { wrapper.addClass("red-ui-tabs-scrollable"); scrollContainer.addClass("red-ui-tabs-scroll-container"); - scrollContainer.on("scroll",updateScroll); + scrollContainer.on("scroll",function(evt) { + // Generated by trackpads - not mousewheel + updateScroll(evt); + }); + scrollContainer.on("wheel", function(evt) { + if (evt.originalEvent.deltaX === 0) { + // Prevent the scroll event from firing + evt.preventDefault(); + + // Assume this is wheel event which might not trigger + // the scroll event, so do things manually + var sl = scrollContainer.scrollLeft(); + sl -= evt.originalEvent.deltaY; + scrollContainer.scrollLeft(sl); + } + }) scrollLeft = $('
').appendTo(wrapper).find("a"); scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();}); scrollRight = $('
').appendTo(wrapper).find("a"); @@ -770,6 +785,9 @@ RED.tabs = (function() { count: function() { return ul.find("li.red-ui-tab").length; }, + activeIndex: function() { + return ul.find("li.active").index() + }, contains: function(id) { return ul.find("a[href='#"+id+"']").length > 0; }, 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..6619ad76e 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,6 +312,7 @@ } if (child.depth !== parent.depth+1) { child.depth = parent.depth+1; + // var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20)); var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20)); child.treeList.labelPadding.width(labelPaddingWidth+'px'); if (child.element) { @@ -348,6 +349,18 @@ that._selected.delete(item); delete item.treeList; delete that._items[item.id]; + if(item.depth === 0) { + for(var key in that._items) { + 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}) + } } item.treeList.insertChildAt = function(newItem,position,select) { newItem.parent = item; @@ -480,7 +493,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)" }) @@ -517,6 +533,7 @@ } 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 = $('').css({ display: "inline-block", width: labelPaddingWidth+'px' 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..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:''} + }).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 = []; @@ -389,6 +397,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' @@ -438,7 +451,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"); } @@ -669,6 +686,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) { @@ -768,6 +790,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 { @@ -1004,16 +1031,17 @@ this.uiSelect.hide(); }, disable: function(val) { - if(val === true) { + if(val === undefined || !!val ) { 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); 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") { 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+' '+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 { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 0cb202051..09f4758f1 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -320,12 +320,12 @@ RED.palette = (function() { var paletteNode = getPaletteNode(nt); ui.originalPosition.left = paletteNode.offset().left; mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); - mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); + mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10; if (!groupTimer) { groupTimer = setTimeout(function() { - mouseX /= RED.view.scale(); - mouseY /= RED.view.scale(); - var group = RED.view.getGroupAtPoint(mouseX,mouseY); + var mx = mouseX / RED.view.scale(); + var my = mouseY / RED.view.scale(); + var group = RED.view.getGroupAtPoint(mx,my); if (group !== hoverGroup) { if (hoverGroup) { document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); @@ -357,23 +357,20 @@ RED.palette = (function() { svgRect.width = 1; svgRect.height = 1; nodes = chartSVG.getIntersectionList(svgRect,chartSVG); - mouseX /= RED.view.scale(); - mouseY /= RED.view.scale(); } else { // Firefox doesn't do getIntersectionList and that // makes us sad - mouseX /= RED.view.scale(); - mouseY /= RED.view.scale(); nodes = RED.view.getLinksAtPoint(mouseX,mouseY); } - + var mx = mouseX / RED.view.scale(); + var my = mouseY / RED.view.scale(); for (var i=0;i',{class:"red-ui-info-outline-item"}); + var div = $('
',{class:"red-ui-node-list-item"}); RED.utils.createNodeIcon(n).appendTo(div); - var contentDiv = $('
',{class:"red-ui-search-result-description"}).appendTo(div); - $('
',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).text(n.name||n._def.paletteLabel||n.type).appendTo(contentDiv); + var label = n.name; + if (!label && n._def.paletteLabel) { + try { + label = (typeof n._def.paletteLabel === "function" ? n._def.paletteLabel.call(n._def) : n._def.paletteLabel)||""; + } catch (err) { + } + } + label = label || n.type; + $('
',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(div); return div; } @@ -250,6 +257,13 @@ RED.sidebar.help = (function() { helpText = RED.nodes.getNodeHelp(nodeType)||(''+RED._("sidebar.info.none")+''); var _def = RED.nodes.registry.getNodeType(nodeType); title = (_def && _def.paletteLabel)?_def.paletteLabel:nodeType; + if (typeof title === "function") { + try { + title = _def.paletteLabel.call(_def); + } catch(err) { + title = nodeType; + } + } } setInfoText(title, helpText, helpSection); 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 = $('
',{class:"red-ui-info-outline-item"}); - RED.utils.createNodeIcon(n).appendTo(div); - var contentDiv = $('
',{class:"red-ui-search-result-description"}).appendTo(div); - var labelText = getNodeLabelText(n); - var label = $('
',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv); - if (labelText) { - label.text(labelText) - } else { - label.html(" ") - } - + var 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/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index 48d867554..15b705448 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -477,7 +477,7 @@ RED.sidebar.info = (function() { return; } } - while ((m=/(\[(.*?)\])/.exec(tip))) { + while ((m=/(\[([a-z]*?)\])/.exec(tip))) { tip = tip.replace(m[1],RED.keyboard.formatKey(m[2])); } tipBox.html(tip).fadeIn(200); 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 35e938e49..c85905c82 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 @@ -178,8 +178,14 @@ RED.utils = (function() { RED.popover.tooltip(pinPath,RED._("node-red:debug.sidebar.pinPath")); } if (extraTools) { - extraTools.addClass("red-ui-debug-msg-tools-other"); - extraTools.appendTo(tools); + var t = extraTools; + if (typeof t === 'function') { + t = t(key,msg); + } + if (t) { + t.addClass("red-ui-debug-msg-tools-other"); + t.appendTo(tools); + } } } function checkExpanded(strippedKey,expandPaths,minRange,maxRange) { @@ -476,7 +482,8 @@ RED.utils = (function() { rootPath: rootPath, expandPaths: expandPaths, ontoggle: ontoggle, - exposeApi: exposeApi + exposeApi: exposeApi, + tools: tools } ).appendTo(row); } @@ -504,8 +511,8 @@ RED.utils = (function() { rootPath: rootPath, expandPaths: expandPaths, ontoggle: ontoggle, - exposeApi: exposeApi - + exposeApi: exposeApi, + tools: tools } ).appendTo(row); } @@ -553,8 +560,8 @@ RED.utils = (function() { rootPath: rootPath, expandPaths: expandPaths, ontoggle: ontoggle, - exposeApi: exposeApi - + exposeApi: exposeApi, + tools: tools } ).appendTo(row); } @@ -875,6 +882,7 @@ RED.utils = (function() { } function getDefaultNodeIcon(def,node) { + def = def || {}; var icon_url; if (node && node.type === "subflow") { icon_url = "node-red/subflow.svg"; @@ -912,6 +920,7 @@ RED.utils = (function() { } function getNodeIcon(def,node) { + def = def || {}; if (node && node.type === '_selection_') { return "font-awesome/fa-object-ungroup"; } else if (node && node.type === 'group') { @@ -999,6 +1008,7 @@ RED.utils = (function() { } function getNodeColor(type, def) { + def = def || {}; var result = def.color; var paletteTheme = RED.settings.theme('palette.theme') || []; if (paletteTheme.length > 0) { @@ -1125,9 +1135,11 @@ RED.utils = (function() { imageIconElement.css("backgroundImage", "url("+iconUrl+")"); } - function createNodeIcon(node) { + function createNodeIcon(node, includeLabel) { + var container = $(''); + var def = node._def; - var nodeDiv = $('
',{class:"red-ui-search-result-node"}) + var nodeDiv = $('
',{class:"red-ui-node-icon"}) if (node.type === "_selection_") { nodeDiv.addClass("red-ui-palette-icon-selection"); } else if (node.type === "group") { @@ -1147,9 +1159,20 @@ RED.utils = (function() { } var icon_url = RED.utils.getNodeIcon(def,node); - var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); - RED.utils.createIconElement(icon_url, iconContainer, true); - return nodeDiv; + RED.utils.createIconElement(icon_url, nodeDiv, true); + + nodeDiv.appendTo(container); + + if (includeLabel) { + var labelText = RED.utils.getNodeLabel(node,node.name || (node.type+": "+node.id)); + var label = $('
',{class:"red-ui-node-label"}).appendTo(container); + if (labelText) { + label.text(labelText) + } else { + label.html(" ") + } + } + return container; } function getDarkerColor(c) { 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..ac6457531 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 @@ -1476,15 +1476,15 @@ RED.view = (function() { var mouseY = node.n.y; if (outer[0][0].getIntersectionList) { var svgRect = outer[0][0].createSVGRect(); - svgRect.x = mouseX; - svgRect.y = mouseY; + svgRect.x = mouseX*scaleFactor; + svgRect.y = mouseY*scaleFactor; svgRect.width = 1; svgRect.height = 1; nodes = outer[0][0].getIntersectionList(svgRect, outer[0][0]); } else { // Firefox doesn"t do getIntersectionList and that // makes us sad - nodes = RED.view.getLinksAtPoint(mouseX,mouseY); + nodes = RED.view.getLinksAtPoint(mouseX*scaleFactor,mouseY*scaleFactor); } for (var i=0;i numOutputs) { var port = this.__outputs__.pop(); + RED.hooks.trigger("viewRemovePort",{ + node:d, + el:this, + port:port, + portType: "output", + portIndex: this.__outputs__.length + }) port.remove(); } for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { @@ -4126,6 +4152,7 @@ RED.view = (function() { portPort.setAttribute("class","red-ui-flow-port"); } portGroup.appendChild(portPort); + portGroup.__port__ = portPort; portPort.__data__ = this.__data__; portPort.__portType__ = PORT_TYPE_OUTPUT; portPort.__portIndex__ = portIndex; @@ -4138,6 +4165,7 @@ RED.view = (function() { this.appendChild(portGroup); this.__outputs__.push(portGroup); + RED.hooks.trigger("viewAddPort",{node:d,el: this, port: portGroup, portType: "output", portIndex: portIndex}) } else { portGroup = this.__outputs__[portIndex]; } @@ -4226,6 +4254,8 @@ RED.view = (function() { // }); } + RED.hooks.trigger("viewAddNode",{node:d,el:this}) + if (d.dirtyStatus) { redrawStatus(d,this); } @@ -5079,6 +5109,9 @@ RED.view = (function() { return scaleFactor; }, getLinksAtPoint: function(x,y) { + // x,y must be in SVG co-ordinate space + // if they come from a node.x/y, they will need to be scaled using + // scaleFactor first. var result = []; var links = outer.selectAll(".red-ui-flow-link-background")[0]; for (var i=0;i 0) lab += "\n"; if (i === 5) { - lab += " + "+(props.length-4); + lab += "... +"+(props.length-5); break; } lab += props[i].p+": "; @@ -636,7 +636,7 @@ url: "inject/"+this.id, type:"POST", success: function(resp) { - RED.notify(node._("inject.success",{label:label}),{type:"success",id:"inject"}); + RED.notify(node._("inject.success",{label:label}),{type:"success",id:"inject", timeout: 2000}); }, error: function(jqXHR,textStatus,errorThrown) { if (jqXHR.status == 404) { diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index 8691dfbea..886ed68fa 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -129,9 +129,9 @@ RED.history.push(historyEvent); RED.view.redraw(); if (xhr.status == 200) { - RED.notify(node._("debug.notification.activated",{label:label}),"success"); + RED.notify(node._("debug.notification.activated",{label:label}),{type: "success", timeout: 2000}); } else if (xhr.status == 201) { - RED.notify(node._("debug.notification.deactivated",{label:label}),"success"); + RED.notify(node._("debug.notification.deactivated",{label:label}),{type: "success", timeout: 2000}); } }); } 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 += `\n` + }) + } catch(err) {} + cachedDebugView = data.toString().replace("",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