mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge branch 'dev' into continuous-search
This commit is contained in:
		
							
								
								
									
										32
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,35 @@ | ||||
| #### 2.2.2: Maintenance Release | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Fix "close timed out" error when performing full deploy or modifying broker node. (#3451) @Steve-Mcl | ||||
|  | ||||
|  | ||||
| #### 2.2.1: Maintenance Release | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - Handle mixed-cased filter terms in keyboard shortcut dialog (#3444) @knolleary | ||||
|  - Prevent duplicate links being added between nodes (#3442) @knolleary | ||||
|  - Fix to hide tooltip after removing subflow tab (#3391) @HiroyasuNishiyama | ||||
|  - Fix node validation to be applied to config node (#3397) @HiroyasuNishiyama | ||||
|  - Fix: Dont add wires to undo buffer twice (#3437) @Steve-Mcl | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Improve module location parsing (of stack info) when adding hook (#3447) @Steve-Mcl | ||||
|  - Fix substitution of NR_NODE_PATH (#3445) @HiroyasuNishiyama | ||||
|  - Remove console.log when ignoring disabled module (#3439) @knolleary | ||||
|  - Improve "Unexpected Node Error" logging (#3446) @Steve-Mcl | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Debug: Fix no-prototype-builtins bug in debug node and utils (#3394) @Alkarex | ||||
|  - Delay: Fix Japanese message of delay node (#3434) | ||||
|  - Allow nbRateUnits to be undefined when validating (#3443) @knolleary | ||||
|  - Coding help for recently added node-red Predefined Environment Variables (#3440) @Steve-Mcl | ||||
|  | ||||
|  | ||||
| #### 2.2.0: Milestone Release | ||||
|  | ||||
| Editor | ||||
|   | ||||
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "http://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -28,7 +28,7 @@ | ||||
|     "dependencies": { | ||||
|         "acorn": "8.7.0", | ||||
|         "acorn-walk": "8.2.0", | ||||
|         "ajv": "8.9.0", | ||||
|         "ajv": "8.10.0", | ||||
|         "async-mutex": "0.3.2", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
| @@ -36,7 +36,7 @@ | ||||
|         "cheerio": "1.0.0-rc.10", | ||||
|         "clone": "2.1.2", | ||||
|         "content-type": "1.0.4", | ||||
|         "cookie": "0.4.1", | ||||
|         "cookie": "0.4.2", | ||||
|         "cookie-parser": "1.4.6", | ||||
|         "cors": "2.8.5", | ||||
|         "cronosjs": "1.7.1", | ||||
| @@ -50,32 +50,32 @@ | ||||
|         "hash-sum": "2.0.0", | ||||
|         "hpagent": "0.1.2", | ||||
|         "https-proxy-agent": "5.0.0", | ||||
|         "i18next": "21.6.10", | ||||
|         "i18next": "21.6.11", | ||||
|         "iconv-lite": "0.6.3", | ||||
|         "is-utf8": "0.2.1", | ||||
|         "js-yaml": "3.14.1", | ||||
|         "json-stringify-safe": "5.0.1", | ||||
|         "jsonata": "1.8.5", | ||||
|         "jsonata": "1.8.6", | ||||
|         "lodash.clonedeep": "^4.5.0", | ||||
|         "media-typer": "1.1.0", | ||||
|         "memorystore": "1.6.6", | ||||
|         "memorystore": "1.6.7", | ||||
|         "mime": "3.0.0", | ||||
|         "moment-timezone": "0.5.34", | ||||
|         "mqtt": "4.3.4", | ||||
|         "mqtt": "4.3.5", | ||||
|         "multer": "1.4.4", | ||||
|         "mustache": "4.2.0", | ||||
|         "node-red-admin": "^2.2.2", | ||||
|         "node-red-admin": "^2.2.3", | ||||
|         "nopt": "5.0.0", | ||||
|         "oauth2orize": "1.11.1", | ||||
|         "on-headers": "1.0.2", | ||||
|         "passport": "0.5.2", | ||||
|         "passport-http-bearer": "1.0.1", | ||||
|         "passport-oauth2-client-password": "0.1.2", | ||||
|         "raw-body": "2.4.2", | ||||
|         "raw-body": "2.4.3", | ||||
|         "semver": "7.3.5", | ||||
|         "tar": "6.1.11", | ||||
|         "tough-cookie": "4.0.0", | ||||
|         "uglify-js": "3.15.0", | ||||
|         "uglify-js": "3.15.1", | ||||
|         "uuid": "8.3.2", | ||||
|         "ws": "7.5.6", | ||||
|         "xml2js": "0.4.23" | ||||
| @@ -84,7 +84,7 @@ | ||||
|         "bcrypt": "5.0.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "dompurify": "2.3.4", | ||||
|         "dompurify": "2.3.5", | ||||
|         "grunt": "1.4.1", | ||||
|         "grunt-chmod": "~1.1.1", | ||||
|         "grunt-cli": "~1.4.3", | ||||
| @@ -113,11 +113,11 @@ | ||||
|         "node-red-node-test-helper": "^0.2.7", | ||||
|         "nodemon": "2.0.15", | ||||
|         "proxy": "^1.0.2", | ||||
|         "sass": "1.49.0", | ||||
|         "sass": "1.49.7", | ||||
|         "should": "13.2.3", | ||||
|         "sinon": "11.1.2", | ||||
|         "stoppable": "^1.1.0", | ||||
|         "supertest": "6.2.1" | ||||
|         "supertest": "6.2.2" | ||||
|     }, | ||||
|     "engines": { | ||||
|         "node": ">=12" | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-api", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,15 +16,15 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "2.2.0", | ||||
|         "@node-red/editor-client": "2.2.0", | ||||
|         "@node-red/util": "2.2.2", | ||||
|         "@node-red/editor-client": "2.2.2", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.19.1", | ||||
|         "clone": "2.1.2", | ||||
|         "cors": "2.8.5", | ||||
|         "express-session": "1.17.2", | ||||
|         "express": "4.17.2", | ||||
|         "memorystore": "1.6.6", | ||||
|         "memorystore": "1.6.7", | ||||
|         "mime": "3.0.0", | ||||
|         "multer": "1.4.4", | ||||
|         "mustache": "4.2.0", | ||||
|   | ||||
| @@ -634,14 +634,7 @@ | ||||
|             "empty": "leer", | ||||
|             "globalConfig": "Globale Konfigurations-Nodes", | ||||
|             "triggerAction": "Auslösen", | ||||
|             "find": "Suche im Arbeitsbereich", | ||||
|             "search": { | ||||
|                 "configNodes": "Konfigurations-Nodes", | ||||
|                 "unusedConfigNodes": "Unbenutzte Konfigurations-Nodes", | ||||
|                 "invalidNodes": "Ungültige Nodes", | ||||
|                 "uknownNodes": "Unbekannte Nodes", | ||||
|                 "unusedSubflows": "Unbenutzte Subflows" | ||||
|             } | ||||
|             "find": "Suche im Arbeitsbereich" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "Hilfe", | ||||
| @@ -863,7 +856,14 @@ | ||||
|     }, | ||||
|     "search": { | ||||
|         "empty": "Keine Übereinstimmungen gefunden", | ||||
|         "addNode": "Node hinzufügen ..." | ||||
|         "addNode": "Node hinzufügen ...", | ||||
|         "options": { | ||||
|             "configNodes": "Konfigurations-Nodes", | ||||
|             "unusedConfigNodes": "Unbenutzte Konfigurations-Nodes", | ||||
|             "invalidNodes": "Ungültige Nodes", | ||||
|             "uknownNodes": "Unbekannte Nodes", | ||||
|             "unusedSubflows": "Unbenutzte Subflows" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "Funktionen", | ||||
|   | ||||
| @@ -501,7 +501,8 @@ | ||||
|         "redoChange": "Redo", | ||||
|         "searchBox": "Open search box", | ||||
|         "managePalette": "Manage palette", | ||||
|         "actionList":"Action list" | ||||
|         "actionList": "Action list", | ||||
|         "splitWiresWithLinks": "Split selection with Link nodes" | ||||
|     }, | ||||
|     "library": { | ||||
|         "library": "Library", | ||||
| @@ -671,15 +672,7 @@ | ||||
|             "empty": "empty", | ||||
|             "globalConfig": "Global Configuration Nodes", | ||||
|             "triggerAction": "Trigger action", | ||||
|             "find": "Find in workspace", | ||||
|             "search": { | ||||
|                 "configNodes": "Configuration nodes", | ||||
|                 "unusedConfigNodes": "Unused configuration nodes", | ||||
|                 "invalidNodes": "Invalid nodes", | ||||
|                 "uknownNodes": "Unknown nodes", | ||||
|                 "unusedSubflows": "Unused subflows", | ||||
|                 "hiddenFlows": "Hidden flows" | ||||
|             } | ||||
|             "find": "Find in workspace" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "Help", | ||||
| @@ -903,7 +896,16 @@ | ||||
|         "history": "Search history", | ||||
|         "clear": "clear all", | ||||
|         "empty": "No matches found", | ||||
|         "addNode": "add a node..." | ||||
|         "addNode": "add a node...", | ||||
|         "options": { | ||||
|             "configNodes": "Configuration nodes", | ||||
|             "unusedConfigNodes": "Unused configuration nodes", | ||||
|             "invalidNodes": "Invalid nodes", | ||||
|             "uknownNodes": "Unknown nodes", | ||||
|             "unusedSubflows": "Unused subflows", | ||||
|             "hiddenFlows": "Hidden flows", | ||||
|             "modifiedNodes": "Modified nodes and flows" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "Functions", | ||||
|   | ||||
| @@ -667,15 +667,7 @@ | ||||
|             "empty": "空", | ||||
|             "globalConfig": "グローバル設定ノード", | ||||
|             "triggerAction": "アクションを実行", | ||||
|             "find": "ワークスペース内を検索", | ||||
|             "search": { | ||||
|                 "configNodes": "設定ノード", | ||||
|                 "unusedConfigNodes": "未使用の設定ノード", | ||||
|                 "invalidNodes": "不正なノード", | ||||
|                 "uknownNodes": "未知のノード", | ||||
|                 "unusedSubflows": "未使用のサブフロー", | ||||
|                 "hiddenFlows": "非表示のフロー" | ||||
|             } | ||||
|             "find": "ワークスペース内を検索" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "ヘルプ", | ||||
| @@ -899,7 +891,15 @@ | ||||
|         "history": "検索履歴", | ||||
|         "clear": "全て削除", | ||||
|         "empty": "一致したものが見つかりませんでした", | ||||
|         "addNode": "ノードを追加..." | ||||
|         "addNode": "ノードを追加...", | ||||
|         "options": { | ||||
|             "configNodes": "設定ノード", | ||||
|             "unusedConfigNodes": "未使用の設定ノード", | ||||
|             "invalidNodes": "不正なノード", | ||||
|             "uknownNodes": "未知のノード", | ||||
|             "unusedSubflows": "未使用のサブフロー", | ||||
|             "hiddenFlows": "非表示のフロー" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "関数", | ||||
|   | ||||
| @@ -650,14 +650,7 @@ | ||||
|             "empty": "пусто", | ||||
|             "globalConfig": "Глобальные конфиг узлы", | ||||
|             "triggerAction": "Вызвать действие", | ||||
|             "find": "Найти в рабочей области", | ||||
|             "search": { | ||||
|                 "configNodes": "Узлы конфигурации", | ||||
|                 "unusedConfigNodes": "Неиспользуемые узлы конфигурации", | ||||
|                 "invalidNodes": "Недействительные узлы", | ||||
|                 "uknownNodes": "Неизвестные узлы", | ||||
|                 "unusedSubflows": "Неиспользуемые подпотоки" | ||||
|             } | ||||
|             "find": "Найти в рабочей области" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "Справка", | ||||
| @@ -888,7 +881,14 @@ | ||||
|     }, | ||||
|     "search": { | ||||
|         "empty": "Ничего не найдено", | ||||
|         "addNode": "добавить узел..." | ||||
|         "addNode": "добавить узел...", | ||||
|         "options": { | ||||
|             "configNodes": "Узлы конфигурации", | ||||
|             "unusedConfigNodes": "Неиспользуемые узлы конфигурации", | ||||
|             "invalidNodes": "Недействительные узлы", | ||||
|             "uknownNodes": "Неизвестные узлы", | ||||
|             "unusedSubflows": "Неиспользуемые подпотоки" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "Функции", | ||||
|   | ||||
| @@ -614,14 +614,7 @@ | ||||
|             "empty": "空的", | ||||
|             "globalConfig": "全局配置节点", | ||||
|             "triggerAction": "触发动作", | ||||
|             "find": "在工作区中查找", | ||||
|             "search": { | ||||
|                 "configNodes": "配置节点", | ||||
|                 "unusedConfigNodes": "未使用的配置节点", | ||||
|                 "invalidNodes": "无效的节点", | ||||
|                 "uknownNodes": "未知的节点", | ||||
|                 "unusedSubflows": "未使用的子流程" | ||||
|             } | ||||
|             "find": "在工作区中查找" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "帮助", | ||||
| @@ -842,7 +835,14 @@ | ||||
|     }, | ||||
|     "search": { | ||||
|         "empty": "找不到匹配", | ||||
|         "addNode": "添加一个节点..." | ||||
|         "addNode": "添加一个节点...", | ||||
|         "options": { | ||||
|             "configNodes": "配置节点", | ||||
|             "unusedConfigNodes": "未使用的配置节点", | ||||
|             "invalidNodes": "无效的节点", | ||||
|             "uknownNodes": "未知的节点", | ||||
|             "unusedSubflows": "未使用的子流程" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "功能", | ||||
|   | ||||
| @@ -614,14 +614,7 @@ | ||||
|             "empty": "空的", | ||||
|             "globalConfig": "全局配置節點", | ||||
|             "triggerAction": "觸發動作", | ||||
|             "find": "在工作區中查找", | ||||
|             "search": { | ||||
|                 "configNodes": "配置節點", | ||||
|                 "unusedConfigNodes": "未使用的配置節點", | ||||
|                 "invalidNodes": "無效的節點", | ||||
|                 "uknownNodes": "未知的節點", | ||||
|                 "unusedSubflows": "未使用的子流程" | ||||
|             } | ||||
|             "find": "在工作區中查找" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "幫助", | ||||
| @@ -842,7 +835,14 @@ | ||||
|     }, | ||||
|     "search": { | ||||
|         "empty": "找不到匹配", | ||||
|         "addNode": "添加一個節點..." | ||||
|         "addNode": "添加一個節點...", | ||||
|         "options": { | ||||
|             "configNodes": "配置節點", | ||||
|             "unusedConfigNodes": "未使用的配置節點", | ||||
|             "invalidNodes": "無效的節點", | ||||
|             "uknownNodes": "未知的節點", | ||||
|             "unusedSubflows": "未使用的子流程" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "功能", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-client", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -13,6 +13,11 @@ | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| /**  | ||||
|  * An API for undo / redo history buffer | ||||
|  * @namespace RED.history | ||||
| */ | ||||
| RED.history = (function() { | ||||
|     var undoHistory = []; | ||||
|     var redoHistory = []; | ||||
|   | ||||
| @@ -92,6 +92,8 @@ | ||||
|         "alt-a h": "core:distribute-selection-horizontally", | ||||
|         "alt-a v": "core:distribute-selection-vertically", | ||||
|         "shift-f": "core:search-previous", | ||||
|         "f": "core:search-next" | ||||
|         "f": "core:search-next", | ||||
|         "alt-l l": "core:split-wire-with-link-nodes" | ||||
|  | ||||
|      } | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,11 @@ | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| /**  | ||||
|  * An Interface to nodes and utility functions for creating/adding/deleting nodes and links | ||||
|  * @namespace RED.nodes | ||||
| */ | ||||
| RED.nodes = (function() { | ||||
|  | ||||
|     var PORT_TYPE_INPUT = 1; | ||||
| @@ -600,6 +605,14 @@ RED.nodes = (function() { | ||||
|         RED.events.emit('nodes:add',n); | ||||
|     } | ||||
|     function addLink(l) { | ||||
|         if (nodeLinks[l.source.id]) { | ||||
|             const isUnique = nodeLinks[l.source.id].out.every(function(link) { | ||||
|                 return link.sourcePort !== l.sourcePort || link.target.id !== l.target.id | ||||
|             }) | ||||
|             if (!isUnique) { | ||||
|                 return | ||||
|             } | ||||
|         } | ||||
|         links.push(l); | ||||
|         if (l.source) { | ||||
|             // Possible the node hasn't been added yet | ||||
| @@ -2676,7 +2689,6 @@ RED.nodes = (function() { | ||||
|         getType: registry.getNodeType, | ||||
|         getNodeHelp: getNodeHelp, | ||||
|         convertNode: convertNode, | ||||
|  | ||||
|         add: addNode, | ||||
|         remove: removeNode, | ||||
|         clear: clear, | ||||
|   | ||||
| @@ -602,7 +602,10 @@ var RED = (function() { | ||||
|             null, | ||||
|             {id: "menu-item-edit-select-all", label:RED._("keyboard.selectAll"), onselect: "core:select-all-nodes"}, | ||||
|             {id: "menu-item-edit-select-connected", label:RED._("keyboard.selectAllConnected"), onselect: "core:select-connected-nodes"}, | ||||
|             {id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"} | ||||
|             {id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"}, | ||||
|             null, | ||||
|             {id: "menu-item-edit-split-wire-with-links", label:RED._("keyboard.splitWireWithLinks"), onselect: "core:split-wire-with-link-nodes"}, | ||||
|  | ||||
|         ]}); | ||||
|  | ||||
|         menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[ | ||||
|   | ||||
| @@ -350,6 +350,15 @@ RED.popover = (function() { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         target.on("remove", function (ev) { | ||||
|             if (timer) { | ||||
|                 clearTimeout(timer); | ||||
|             } | ||||
|             if (active) { | ||||
|                 active = false; | ||||
|                 setTimeout(closePopup,delay.hide); | ||||
|             } | ||||
|         }); | ||||
|         if (trigger === 'hover') { | ||||
|             target.on('mouseenter',function(e) { | ||||
|                 clearTimeout(timer); | ||||
|   | ||||
| @@ -334,6 +334,9 @@ RED.deploy = (function() { | ||||
|                 var invalidNodes = []; | ||||
|  | ||||
|                 RED.nodes.eachConfig(function(node) { | ||||
|                     if (node.valid === undefined) { | ||||
|                         RED.editor.validateNode(node); | ||||
|                     } | ||||
|                     if (!node.valid && !node.d) { | ||||
|                         invalidNodes.push(getNodeInfo(node)); | ||||
|                     } | ||||
|   | ||||
| @@ -577,7 +577,7 @@ RED.editor.codeEditor.monaco = (function() { | ||||
|                     createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range), | ||||
|                     createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range), | ||||
|                     createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range), | ||||
|                     createMonacoCompletionItem("get (env)", 'env.get("${1:name}");','Get env variable value',range), | ||||
|                     createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range), | ||||
|                     createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});', | ||||
|                         ["```typescript", | ||||
|                         "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T", | ||||
|   | ||||
| @@ -625,7 +625,7 @@ RED.keyboard = (function() { | ||||
|         pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({ | ||||
|             delay: 100, | ||||
|             change: function() { | ||||
|                 var filterValue = $(this).val().trim(); | ||||
|                 var filterValue = $(this).val().trim().toLowerCase(); | ||||
|                 if (filterValue === "") { | ||||
|                     shortcutList.editableList('filter', null); | ||||
|                 } else { | ||||
|   | ||||
| @@ -121,6 +121,7 @@ RED.search = (function() { | ||||
|         val = extractFlag(val,"config",flags); | ||||
|         val = extractFlag(val,"subflow",flags); | ||||
|         val = extractFlag(val,"hidden",flags); | ||||
|         val = extractFlag(val,"modified",flags); | ||||
|         // uses:<node-id> | ||||
|         val = extractValue(val,"uses",flags); | ||||
|  | ||||
| @@ -166,6 +167,11 @@ RED.search = (function() { | ||||
|                                 continue; | ||||
|                             } | ||||
|                         } | ||||
|                         if (flags.hasOwnProperty("modified")) { | ||||
|                             if (!node.node.changed && !node.node.moved) { | ||||
|                                 continue; | ||||
|                             } | ||||
|                         } | ||||
|                         if (flags.hasOwnProperty("hidden")) { | ||||
|                             // Only tabs can be hidden | ||||
|                             if (node.node.type !== 'tab') { | ||||
| @@ -263,9 +269,8 @@ RED.search = (function() { | ||||
|                 } else { | ||||
|                     searchResults.editableList('addItem',{}); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|             } | ||||
|             }, | ||||
|             options: getSearchOptions() | ||||
|         }); | ||||
|         var copySearchContainer = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-caret-right"></button>').appendTo(searchDiv).on('click', function(evt) { | ||||
|             evt.preventDefault(); | ||||
| @@ -564,6 +569,17 @@ RED.search = (function() { | ||||
|         addItemToIndex(item); | ||||
|     } | ||||
|  | ||||
|     function getSearchOptions() { | ||||
|         return [ | ||||
|             {label:RED._("search.options.configNodes"), value:"is:config"}, | ||||
|             {label:RED._("search.options.unusedConfigNodes"), value:"is:config is:unused"}, | ||||
|             {label:RED._("search.options.modifiedNodes"), value:"is:modified"}, | ||||
|             {label:RED._("search.options.invalidNodes"), value: "is:invalid"}, | ||||
|             {label:RED._("search.options.uknownNodes"), value: "type:unknown"}, | ||||
|             {label:RED._("search.options.unusedSubflows"), value:"is:subflow is:unused"}, | ||||
|             {label:RED._("search.options.hiddenFlows"), value:"is:hidden"}, | ||||
|         ] | ||||
|     } | ||||
|  | ||||
|     function init() { | ||||
|         RED.actions.add("core:search",show); | ||||
| @@ -615,7 +631,8 @@ RED.search = (function() { | ||||
|         init: init, | ||||
|         show: show, | ||||
|         hide: hide, | ||||
|         search: search | ||||
|         search: search, | ||||
|         getSearchOptions: getSearchOptions | ||||
|     }; | ||||
|  | ||||
| })(); | ||||
|   | ||||
| @@ -268,14 +268,7 @@ RED.sidebar.info.outliner = (function() { | ||||
|  | ||||
|                 } | ||||
|             }, | ||||
|             options: [ | ||||
|                 {label:RED._("sidebar.info.search.configNodes"), value:"is:config"}, | ||||
|                 {label:RED._("sidebar.info.search.unusedConfigNodes"), value:"is:config is:unused"}, | ||||
|                 {label:RED._("sidebar.info.search.invalidNodes"), value: "is:invalid"}, | ||||
|                 {label:RED._("sidebar.info.search.uknownNodes"), value: "type:unknown"}, | ||||
|                 {label:RED._("sidebar.info.search.unusedSubflows"), value:"is:subflow is:unused"}, | ||||
|                 {label:RED._("sidebar.info.search.hiddenFlows"), value:"is:hidden"}, | ||||
|             ] | ||||
|             options: RED.search.getSearchOptions() | ||||
|         }); | ||||
|  | ||||
|         projectInfo = $('<div class="red-ui-treeList-label red-ui-info-outline-project"><span class="red-ui-treeList-icon"><i class="fa fa-archive"></i></span></div>').hide().appendTo(container) | ||||
| @@ -287,15 +280,18 @@ RED.sidebar.info.outliner = (function() { | ||||
|             data:getFlowData() | ||||
|         }) | ||||
|         treeList.on('treelistselect', function(e,item) { | ||||
|             var node = RED.nodes.node(item.id) || RED.nodes.group(item.id); | ||||
|             var node = RED.nodes.node(item.id) || RED.nodes.group(item.id) || RED.nodes.workspace(item.id) || RED.nodes.subflow(item.id); | ||||
|             if (node) { | ||||
|                 if (node.type === 'group' || node._def.category !== "config") { | ||||
|                     // RED.view.select({nodes:[node]}) | ||||
|                 } else if (node._def.category === "config") { | ||||
|                     RED.sidebar.info.refresh(node); | ||||
|                 } else { | ||||
|                     // RED.view.select({nodes:[]}) | ||||
|                 } | ||||
|                 RED.sidebar.info.refresh(node); | ||||
|                 // if (node.type === 'group' || node._def.category !== "config") { | ||||
|                 //     // RED.view.select({nodes:[node]}) | ||||
|                 // } else if (node._def.category === "config") { | ||||
|                 //     RED.sidebar.info.refresh(node); | ||||
|                 // } else { | ||||
|                 //     // RED.view.select({nodes:[]}) | ||||
|                 // } | ||||
|             } else { | ||||
|                 RED.sidebar.info.refresh(null); | ||||
|             } | ||||
|         }) | ||||
|         treeList.on('treelistconfirm', function(e,item) { | ||||
|   | ||||
| @@ -163,6 +163,7 @@ RED.sidebar.info = (function() { | ||||
|         }); | ||||
|         return el; | ||||
|     } | ||||
|  | ||||
|     function refresh(node) { | ||||
|         if (node === undefined) { | ||||
|             refreshSelection(); | ||||
| @@ -271,7 +272,7 @@ RED.sidebar.info = (function() { | ||||
|                 objectType = "group"; | ||||
|             } | ||||
|             $(propRow.children()[0]).text(RED._("sidebar.info."+objectType)) | ||||
|             RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); | ||||
|             RED.utils.createObjectElement(node.id,{sourceId: node.id}).appendTo(propRow.children()[1]); | ||||
|  | ||||
|             if (node.type === "tab" || node.type === "subflow") { | ||||
|                 // If nothing is selected, but we're on a flow or subflow tab. | ||||
| @@ -365,7 +366,7 @@ RED.sidebar.info = (function() { | ||||
|  | ||||
|                                     } | ||||
|                                 } else { | ||||
|                                     RED.utils.createObjectElement(val).appendTo(propRow.children()[1]); | ||||
|                                     RED.utils.createObjectElement(val,{sourceId: node.id}).appendTo(propRow.children()[1]); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
| @@ -431,6 +432,7 @@ RED.sidebar.info = (function() { | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     function setInfoText(infoText,target) { | ||||
|         var info = addTargetToExternalLinks($('<div class="red-ui-help"><span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(infoText)+'">'+infoText+'</span></div>')).appendTo(target); | ||||
|         info.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" ); | ||||
| @@ -447,6 +449,7 @@ RED.sidebar.info = (function() { | ||||
|                 $(this).toggleClass('expanded',!isExpanded); | ||||
|             }) | ||||
|     } | ||||
|  | ||||
|     var tips = (function() { | ||||
|         var enabled = true; | ||||
|         var startDelay = 1000; | ||||
|   | ||||
| @@ -365,7 +365,16 @@ RED.utils = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function buildMessageElement(obj,options) { | ||||
|     /** | ||||
|      * Create a DOM element representation of obj - as used by Debug sidebar etc | ||||
|      * | ||||
|      * @params obj - the data to display | ||||
|      * @params options - a bag of options | ||||
|      * | ||||
|      * - If you want the Copy Value button, then set `sourceId` | ||||
|      * - If you want the Copy Path button, also set `path` to the value to be copied | ||||
|      */ | ||||
|     function createObjectElement(obj,options) { | ||||
|         options = options || {}; | ||||
|         var key = options.key; | ||||
|         var typeHint = options.typeHint; | ||||
| @@ -555,7 +564,7 @@ RED.utils = (function() { | ||||
|                     if (fullLength <= 10) { | ||||
|                         for (i=0;i<fullLength;i++) { | ||||
|                             row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(arrayRows); | ||||
|                             subElements[path+"["+i+"]"] = buildMessageElement( | ||||
|                             subElements[path+"["+i+"]"] = createObjectElement( | ||||
|                                 data[i], | ||||
|                                 { | ||||
|                                     key: ""+i, | ||||
| @@ -585,7 +594,7 @@ RED.utils = (function() { | ||||
|                                 return function() { | ||||
|                                     for (var i=min;i<=max;i++) { | ||||
|                                         var row = $('<div class="red-ui-debug-msg-object-entry collapsed"></div>').appendTo(parent); | ||||
|                                         subElements[path+"["+i+"]"] = buildMessageElement( | ||||
|                                         subElements[path+"["+i+"]"] = createObjectElement( | ||||
|                                             data[i], | ||||
|                                             { | ||||
|                                                 key: ""+i, | ||||
| @@ -641,7 +650,7 @@ RED.utils = (function() { | ||||
|                                 newPath += "[\""+keys[i].replace(/"/,"\\\"")+"\"]" | ||||
|                             } | ||||
|                         } | ||||
|                         subElements[newPath] = buildMessageElement( | ||||
|                         subElements[newPath] = createObjectElement( | ||||
|                             data[keys[i]], | ||||
|                             { | ||||
|                                 key: keys[i], | ||||
| @@ -1369,7 +1378,7 @@ RED.utils = (function() { | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         createObjectElement: buildMessageElement, | ||||
|         createObjectElement: createObjectElement, | ||||
|         getMessageProperty: getMessageProperty, | ||||
|         setMessageProperty: setMessageProperty, | ||||
|         normalisePropertyExpression: normalisePropertyExpression, | ||||
|   | ||||
| @@ -809,6 +809,167 @@ RED.view.tools = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Splits selected wires and re-joins them with link-out+link-in | ||||
|      * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.   | ||||
|      */ | ||||
|     function splitWiresWithLinkNodes(wires) { | ||||
|         let wiresToSplit = wires || RED.view.selection().links; | ||||
|         if (!Array.isArray(wiresToSplit)) { | ||||
|             wiresToSplit = [wiresToSplit]; | ||||
|         } | ||||
|         if (wiresToSplit.length < 1) { | ||||
|             return; //nothing selected | ||||
|         } | ||||
|  | ||||
|         const history = { | ||||
|             t: 'multi', | ||||
|             events: [], | ||||
|             dirty: RED.nodes.dirty() | ||||
|         } | ||||
|         const nodeSrcMap = {}; | ||||
|         const nodeTrgMap = {}; | ||||
|         const _gridSize = RED.view.gridSize(); | ||||
|  | ||||
|         for (let wireIdx = 0; wireIdx < wiresToSplit.length; wireIdx++) { | ||||
|             const wire = wiresToSplit[wireIdx]; | ||||
|  | ||||
|             //get source and target nodes of this wire link | ||||
|             const nSrc = wire.source; | ||||
|             const nTrg = wire.target; | ||||
|  | ||||
|             var updateNewNodePosXY = function (origNode, newNode, alignLeft, snap, yOffset) { | ||||
|                 const nnSize = RED.view.calculateNodeDimensions(newNode); | ||||
|                 newNode.w = nnSize[0]; | ||||
|                 newNode.h = nnSize[1]; | ||||
|                 const coords = { x: origNode.x || 0, y: origNode.y || 0, w: origNode.w || RED.view.node_width, h: origNode.h || RED.view.node_height }; | ||||
|                 const x = coords.x - (coords.w/2.0); | ||||
|                 if (alignLeft) { | ||||
|                     coords.x = x - _gridSize - (newNode.w/2.0); | ||||
|                 } else { | ||||
|                     coords.x = x + coords.w + _gridSize + (newNode.w/2.0); | ||||
|                 } | ||||
|                 newNode.x = coords.x; | ||||
|                 newNode.y = coords.y; | ||||
|                 if (snap !== false) { | ||||
|                     const offsets = RED.view.tools.calculateGridSnapOffsets(newNode); | ||||
|                     newNode.x -= offsets.x; | ||||
|                     newNode.y -= offsets.y; | ||||
|                 } | ||||
|                 newNode.y += (yOffset || 0); | ||||
|             } | ||||
|             const srcPort = (wire.sourcePort || 0); | ||||
|             let linkOutMapId = nSrc.id + ':' + srcPort; | ||||
|             let nnLinkOut = nodeSrcMap[linkOutMapId]; | ||||
|             //Create a Link Out if one is not already present | ||||
|             if(!nnLinkOut) { | ||||
|                 const nLinkOut = RED.view.createNode("link out"); //create link node | ||||
|                 nnLinkOut = nLinkOut.node; | ||||
|                 nodeSrcMap[linkOutMapId] = nnLinkOut; | ||||
|                 let yOffset = 0; | ||||
|                 if(nSrc.outputs > 1) { | ||||
|                      | ||||
|                     const CENTER_PORT = (((nSrc.outputs-1) / 2) + 1); | ||||
|                     const offsetCount = Math.abs(CENTER_PORT - (srcPort + 1)); | ||||
|                     yOffset = (_gridSize * 2 * offsetCount); | ||||
|                     if((srcPort + 1) < CENTER_PORT) { | ||||
|                         yOffset = -yOffset; | ||||
|                     } | ||||
|                     updateNewNodePosXY(nSrc, nnLinkOut, false, false, yOffset); | ||||
|                 } else { | ||||
|                     updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset); | ||||
|                 } | ||||
|                 //add created node | ||||
|                 RED.nodes.add(nnLinkOut); | ||||
|                 RED.editor.validateNode(nnLinkOut); | ||||
|                 history.events.push(nLinkOut.historyEvent); | ||||
|                 //connect node to link node | ||||
|                 const link = { | ||||
|                     source: nSrc, | ||||
|                     sourcePort: wire.sourcePort || 0, | ||||
|                     target: nnLinkOut | ||||
|                 }; | ||||
|                 RED.nodes.addLink(link); | ||||
|                 history.events.push({ | ||||
|                     t: 'add', | ||||
|                     links: [link], | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             let nnLinkIn = nodeTrgMap[nTrg.id]; | ||||
|             //Create a Link In if one is not already present | ||||
|             if(!nnLinkIn) { | ||||
|                 const nLinkIn = RED.view.createNode("link in"); //create link node | ||||
|                 nnLinkIn = nLinkIn.node; | ||||
|                 nodeTrgMap[nTrg.id] = nnLinkIn; | ||||
|                 updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0); | ||||
|                 //add created node | ||||
|                 RED.nodes.add(nnLinkIn); | ||||
|                 RED.editor.validateNode(nnLinkIn); | ||||
|                 history.events.push(nLinkIn.historyEvent); | ||||
|                 //connect node to link node | ||||
|                 const link = { | ||||
|                     source: nnLinkIn, | ||||
|                     sourcePort: 0, | ||||
|                     target: nTrg | ||||
|                 }; | ||||
|                 RED.nodes.addLink(link); | ||||
|                 history.events.push({ | ||||
|                     t: 'add', | ||||
|                     links: [link], | ||||
|                 }); | ||||
|             }  | ||||
|  | ||||
|             //connect the link out/link in virtual wires | ||||
|             if(nnLinkIn.links.indexOf(nnLinkOut.id) == -1) { | ||||
|                 nnLinkIn.links.push(nnLinkOut.id); | ||||
|             } | ||||
|             if(nnLinkOut.links.indexOf(nnLinkIn.id) == -1) { | ||||
|                 nnLinkOut.links.push(nnLinkIn.id); | ||||
|             } | ||||
|  | ||||
|             //delete the original wire | ||||
|             RED.nodes.removeLink(wire); | ||||
|             history.events.push({ | ||||
|                 t: "delete", | ||||
|                 links: [wire] | ||||
|             }); | ||||
|         } | ||||
|         //add all history events to stack | ||||
|         RED.history.push(history); | ||||
|          | ||||
|         //select all downstream of new link-in nodes so user can drag to new location | ||||
|         RED.view.clearSelection();  | ||||
|         RED.view.select({nodes: Object.values(nodeTrgMap) }); | ||||
|         selectConnected("down"); | ||||
|  | ||||
|         //update the view | ||||
|         RED.nodes.dirty(true); | ||||
|         RED.view.redraw(true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Calculate the required offsets to snap a node | ||||
|      * @param {Object} node The node to calculate grid snap offsets for | ||||
|      * @param {Object} [options] Options: `align` can be "nearest", "left" or "right" | ||||
|      * @returns `{x:number, y:number}`  as the offsets to deduct from `x` and `y` | ||||
|      */ | ||||
|     function calculateGridSnapOffsets(node, options) { | ||||
|         options = options || { align: "nearest" }; | ||||
|         const gridOffset = { x: 0, y: 0 }; | ||||
|         const gridSize = RED.view.gridSize(); | ||||
|         const offsetLeft = node.x - (gridSize * Math.round((node.x - node.w / 2) / gridSize) + node.w / 2); | ||||
|         const offsetRight = node.x - (gridSize * Math.round((node.x + node.w / 2) / gridSize) - node.w / 2); | ||||
|         gridOffset.x = offsetRight; | ||||
|         if (options.align === "right") { | ||||
|             //skip - already set to right | ||||
|         } else if (options.align === "left" || Math.abs(offsetLeft) < Math.abs(offsetRight)) { | ||||
|             gridOffset.x = offsetLeft; | ||||
|         } | ||||
|         gridOffset.y = node.y - (gridSize * Math.round(node.y / gridSize)); | ||||
|         return gridOffset; | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         init: function() { | ||||
|             RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) | ||||
| @@ -870,6 +1031,8 @@ RED.view.tools = (function() { | ||||
|             RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() }) | ||||
|             RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() }) | ||||
|  | ||||
|             RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() }); | ||||
|  | ||||
|             // RED.actions.add("core:add-node", function() { addNode() }) | ||||
|         }, | ||||
|         /** | ||||
| @@ -881,7 +1044,8 @@ RED.view.tools = (function() { | ||||
|          * @param  {Number} dx | ||||
|          * @param  {Number} dy | ||||
|          */ | ||||
|         moveSelection: moveSelection | ||||
|         moveSelection: moveSelection, | ||||
|         calculateGridSnapOffsets: calculateGridSnapOffsets | ||||
|     } | ||||
|  | ||||
| })(); | ||||
|   | ||||
| @@ -479,7 +479,7 @@ RED.view = (function() { | ||||
|             drop: function( event, ui ) { | ||||
|                 d3.event = event; | ||||
|                 var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); | ||||
|                 var result = addNode(selected_tool); | ||||
|                 var result = createNode(selected_tool); | ||||
|                 if (!result) { | ||||
|                     return; | ||||
|                 } | ||||
| @@ -523,15 +523,7 @@ RED.view = (function() { | ||||
|                 nn.y = mousePos[1]; | ||||
|  | ||||
|                 if (snapGrid) { | ||||
|                     var gridOffset = [0,0]; | ||||
|                     var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2); | ||||
|                     var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2); | ||||
|                     if (Math.abs(offsetLeft) < Math.abs(offsetRight)) { | ||||
|                         gridOffset[0] = offsetLeft | ||||
|                     } else { | ||||
|                         gridOffset[0] = offsetRight | ||||
|                     } | ||||
|                     gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize)); | ||||
|                     var gridOffset = calculateGridSnapOffsets(nn); | ||||
|                     nn.x -= gridOffset[0]; | ||||
|                     nn.y -= gridOffset[1]; | ||||
|                 } | ||||
| @@ -957,81 +949,6 @@ RED.view = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function addNode(type,x,y) { | ||||
|         var m = /^subflow:(.+)$/.exec(type); | ||||
|  | ||||
|         if (activeSubflow && m) { | ||||
|             var subflowId = m[1]; | ||||
|             if (subflowId === activeSubflow.id) { | ||||
|                 RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error"); | ||||
|                 return; | ||||
|             } | ||||
|             if (RED.nodes.subflowContains(m[1],activeSubflow.id)) { | ||||
|                 RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error"); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var nn = { id:RED.nodes.id(),z:RED.workspaces.active()}; | ||||
|  | ||||
|         nn.type = type; | ||||
|         nn._def = RED.nodes.getType(nn.type); | ||||
|  | ||||
|         if (!m) { | ||||
|             nn.inputs = nn._def.inputs || 0; | ||||
|             nn.outputs = nn._def.outputs; | ||||
|  | ||||
|             for (var d in nn._def.defaults) { | ||||
|                 if (nn._def.defaults.hasOwnProperty(d)) { | ||||
|                     if (nn._def.defaults[d].value !== undefined) { | ||||
|                         nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (nn._def.onadd) { | ||||
|                 try { | ||||
|                     nn._def.onadd.call(nn); | ||||
|                 } catch(err) { | ||||
|                     console.log("Definition error: "+nn.type+".onadd:",err); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             var subflow = RED.nodes.subflow(m[1]); | ||||
|             nn.name = ""; | ||||
|             nn.inputs = subflow.in.length; | ||||
|             nn.outputs = subflow.out.length; | ||||
|         } | ||||
|  | ||||
|         nn.changed = true; | ||||
|         nn.moved = true; | ||||
|  | ||||
|         nn.w = node_width; | ||||
|         nn.h = Math.max(node_height,(nn.outputs||0) * 15); | ||||
|         nn.resize = true; | ||||
|  | ||||
|         var historyEvent = { | ||||
|             t:"add", | ||||
|             nodes:[nn.id], | ||||
|             dirty:RED.nodes.dirty() | ||||
|         } | ||||
|         if (activeSubflow) { | ||||
|             var subflowRefresh = RED.subflow.refresh(true); | ||||
|             if (subflowRefresh) { | ||||
|                 historyEvent.subflow = { | ||||
|                     id:activeSubflow.id, | ||||
|                     changed: activeSubflow.changed, | ||||
|                     instances: subflowRefresh.instances | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return { | ||||
|             node: nn, | ||||
|             historyEvent: historyEvent | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     function canvasMouseDown() { | ||||
|         if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); } | ||||
|         var point; | ||||
| @@ -1220,7 +1137,7 @@ RED.view = (function() { | ||||
|                     keepAdding = false; | ||||
|                     resetMouseVars(); | ||||
|                 } | ||||
|                 var result = addNode(type); | ||||
|                 var result = createNode(type); | ||||
|                 if (!result) { | ||||
|                     return; | ||||
|                 } | ||||
| @@ -1673,16 +1590,9 @@ RED.view = (function() { | ||||
|                     gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2; | ||||
|                     gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2; | ||||
|                 } else { | ||||
|                     var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); | ||||
|                     var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2); | ||||
|                     // gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); | ||||
|                     if (Math.abs(offsetLeft) < Math.abs(offsetRight)) { | ||||
|                         gridOffset[0] = offsetLeft | ||||
|                     } else { | ||||
|                         gridOffset[0] = offsetRight | ||||
|                     } | ||||
|                     gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize)); | ||||
|                     // console.log(offsetLeft, offsetRight); | ||||
|                     const snapOffsets = RED.view.tools.calculateGridSnapOffsets(node.n); | ||||
|                     gridOffset[0] = snapOffsets.x; | ||||
|                     gridOffset[1] = snapOffsets.y; | ||||
|                 } | ||||
|                 if (gridOffset[0] !== 0 || gridOffset[1] !== 0) { | ||||
|                     for (i = 0; i<movingSet.length(); i++) { | ||||
| @@ -2371,14 +2281,21 @@ RED.view = (function() { | ||||
|             var removedSubflowStatus; | ||||
|             var subflowInstances = []; | ||||
|             var historyEvents = []; | ||||
|  | ||||
|             var addToRemovedLinks = function(links) { | ||||
|                 if(!links) { return; } | ||||
|                 var _links = Array.isArray(links) ? links : [links]; | ||||
|                 _links.forEach(function(l) { | ||||
|                     removedLinks.push(l); | ||||
|                     selectedLinks.remove(l); | ||||
|                 }) | ||||
|             } | ||||
|             if (reconnectWires) { | ||||
|                 var reconnectResult = RED.nodes.detachNodes(movingSet.nodes()) | ||||
|                 var addedLinks = reconnectResult.newLinks; | ||||
|                 if (addedLinks.length > 0) { | ||||
|                     historyEvents.push({ t:'add', links: addedLinks }) | ||||
|                 } | ||||
|                 removedLinks = removedLinks.concat(reconnectResult.removedLinks) | ||||
|                 addToRemovedLinks(reconnectResult.removedLinks) | ||||
|             } | ||||
|  | ||||
|             var startDirty = RED.nodes.dirty(); | ||||
| @@ -2410,7 +2327,7 @@ RED.view = (function() { | ||||
|                         var removedEntities = RED.nodes.remove(node.id); | ||||
|                         removedNodes.push(node); | ||||
|                         removedNodes = removedNodes.concat(removedEntities.nodes); | ||||
|                         removedLinks = removedLinks.concat(removedEntities.links); | ||||
|                         addToRemovedLinks(removedNodes.removedLinks); | ||||
|                         if (node.g) { | ||||
|                             var group = RED.nodes.group(node.g); | ||||
|                             if (selectedGroups.indexOf(group) === -1) { | ||||
| @@ -2443,20 +2360,20 @@ RED.view = (function() { | ||||
|                 if (removedSubflowOutputs.length > 0) { | ||||
|                     result = RED.subflow.removeOutput(removedSubflowOutputs); | ||||
|                     if (result) { | ||||
|                         removedLinks = removedLinks.concat(result.links); | ||||
|                         addToRemovedLinks(result.links); | ||||
|                     } | ||||
|                 } | ||||
|                 // Assume 0/1 inputs | ||||
|                 if (removedSubflowInputs.length == 1) { | ||||
|                     result = RED.subflow.removeInput(); | ||||
|                     if (result) { | ||||
|                         removedLinks = removedLinks.concat(result.links); | ||||
|                         addToRemovedLinks(result.links); | ||||
|                     } | ||||
|                 } | ||||
|                 if (removedSubflowStatus) { | ||||
|                     result = RED.subflow.removeStatus(); | ||||
|                     if (result) { | ||||
|                         removedLinks = removedLinks.concat(result.links); | ||||
|                         addToRemovedLinks(result.links); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| @@ -5534,6 +5451,93 @@ RED.view = (function() { | ||||
|         return selection; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Create a node from a type string.   | ||||
|      * **NOTE:** Can throw on error - use `try` `catch` block when calling | ||||
|      * @param {string} type The node type to create | ||||
|      * @param {number} [x] (optional) The horizontal position on the workspace | ||||
|      * @param {number} [y] (optional)The vertical on the workspace | ||||
|      * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace. | ||||
|      * @returns An object containing the `node` and a `historyEvent` | ||||
|      * @private | ||||
|      */ | ||||
|      function createNode(type, x, y, z) { | ||||
|         var m = /^subflow:(.+)$/.exec(type); | ||||
|         var activeSubflow = z ? RED.nodes.subflow(z) : null; | ||||
|         if (activeSubflow && m) { | ||||
|             var subflowId = m[1]; | ||||
|             if (subflowId === activeSubflow.id) { | ||||
|                 throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) | ||||
|             } | ||||
|             if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { | ||||
|                 throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() }; | ||||
|  | ||||
|         nn.type = type; | ||||
|         nn._def = RED.nodes.getType(nn.type); | ||||
|  | ||||
|         if (!m) { | ||||
|             nn.inputs = nn._def.inputs || 0; | ||||
|             nn.outputs = nn._def.outputs; | ||||
|  | ||||
|             for (var d in nn._def.defaults) { | ||||
|                 if (nn._def.defaults.hasOwnProperty(d)) { | ||||
|                     if (nn._def.defaults[d].value !== undefined) { | ||||
|                         nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (nn._def.onadd) { | ||||
|                 try { | ||||
|                     nn._def.onadd.call(nn); | ||||
|                 } catch (err) { | ||||
|                     console.log("Definition error: " + nn.type + ".onadd:", err); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             var subflow = RED.nodes.subflow(m[1]); | ||||
|             nn.name = ""; | ||||
|             nn.inputs = subflow.in.length; | ||||
|             nn.outputs = subflow.out.length; | ||||
|         } | ||||
|  | ||||
|         nn.changed = true; | ||||
|         nn.moved = true; | ||||
|  | ||||
|         nn.w = RED.view.node_width; | ||||
|         nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15); | ||||
|         nn.resize = true; | ||||
|         if (x != null && typeof x == "number" && x >= 0) { | ||||
|             nn.x = x; | ||||
|         } | ||||
|         if (y != null && typeof y == "number" && y >= 0) { | ||||
|             nn.y = y; | ||||
|         } | ||||
|         var historyEvent = { | ||||
|             t: "add", | ||||
|             nodes: [nn.id], | ||||
|             dirty: RED.nodes.dirty() | ||||
|         } | ||||
|         if (activeSubflow) { | ||||
|             var subflowRefresh = RED.subflow.refresh(true); | ||||
|             if (subflowRefresh) { | ||||
|                 historyEvent.subflow = { | ||||
|                     id: activeSubflow.id, | ||||
|                     changed: activeSubflow.changed, | ||||
|                     instances: subflowRefresh.instances | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return { | ||||
|             node: nn, | ||||
|             historyEvent: historyEvent | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function calculateNodeDimensions(node) { | ||||
|         var result = [node_width,node_height]; | ||||
|         try { | ||||
| @@ -5648,7 +5652,21 @@ RED.view = (function() { | ||||
|             redraw(true); | ||||
|         }, | ||||
|         selection: getSelection, | ||||
|  | ||||
|         clearSelection: clearSelection, | ||||
|         createNode: createNode, | ||||
|         /** default node width */ | ||||
|         get node_width() { | ||||
|             return node_width; | ||||
|         }, | ||||
|         /** default node height */ | ||||
|         get node_height() { | ||||
|             return node_height; | ||||
|         }, | ||||
|         /** snap to grid option state */ | ||||
|         get snapGrid() { | ||||
|             return snapGrid; | ||||
|         }, | ||||
|         /** gets the current scale factor */ | ||||
|         scale: function() { | ||||
|             return scaleFactor; | ||||
|         }, | ||||
|   | ||||
| @@ -431,6 +431,9 @@ RED.workspaces = (function() { | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|         RED.actions.add("core:list-modified-nodes",function() { | ||||
|             RED.actions.invoke("core:search","is:modified "); | ||||
|         }) | ||||
|         RED.actions.add("core:list-hidden-flows",function() { | ||||
|             RED.actions.invoke("core:search","is:hidden "); | ||||
|         }) | ||||
|   | ||||
| @@ -263,6 +263,20 @@ declare class global { | ||||
|     static keys(store: string, callback: Function); | ||||
| } | ||||
| declare class env { | ||||
|     /** Get an environment variable value */ | ||||
|     static get(name:string); | ||||
|     /**  | ||||
|      * Get an environment variable value   | ||||
|      *  | ||||
|      * Predefined node-red variables...   | ||||
|      *   * `NR_NODE_ID` - the ID of the node | ||||
|      *   * `NR_NODE_NAME` - the Name of the node | ||||
|      *   * `NR_NODE_PATH` - the Path of the node | ||||
|      *   * `NR_GROUP_ID` - the ID of the containing group | ||||
|      *   * `NR_GROUP_NAME` - the Name of the containing group | ||||
|      *   * `NR_FLOW_ID` - the ID of the flow the node is on | ||||
|      *   * `NR_FLOW_NAME` - the Name of the flow the node is on | ||||
|      * @param name Name of the environment variable to get | ||||
|      * @example  | ||||
|      * ```const flowName = env.get("NR_FLOW_NAME");``` | ||||
|      */ | ||||
|     static get(name:string) :string; | ||||
| } | ||||
|   | ||||
| @@ -55,6 +55,7 @@ module.exports = function(RED) { | ||||
|                 catch(e) { return false;} | ||||
|             } | ||||
|             else if (b === "null") { return a === null; } | ||||
|             else if (b === "number") { return typeof a === b && !isNaN(a) } | ||||
|             else { return typeof a === b && !Array.isArray(a) && !Buffer.isBuffer(a) && a !== null; } | ||||
|         }, | ||||
|         'head': function(a, b, c, d, parts) { | ||||
|   | ||||
| @@ -115,7 +115,7 @@ | ||||
|             timeoutUnits: {value:"seconds"}, | ||||
|             rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, | ||||
|             nbRateUnits: {value:"1", required:false, | ||||
|                           validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, | ||||
|                           validate:function(v) { return v === undefined || (RED.validators.number(v) && (v >= 0)); }}, | ||||
|             rateUnits: {value: "second"}, | ||||
|             randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, | ||||
|             randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }}, | ||||
|   | ||||
| @@ -637,24 +637,8 @@ module.exports = function(RED) { | ||||
|  | ||||
|         node.deregister = function(mqttNode,done) { | ||||
|             delete node.users[mqttNode.id]; | ||||
|             if (node.closing) { | ||||
|                 return done(); | ||||
|             } | ||||
|             if (Object.keys(node.users).length === 0) { | ||||
|                 if (node.client && node.client.connected) { | ||||
|                     // Send close message | ||||
|                     if (node.closeMessage) { | ||||
|                         node.publish(node.closeMessage,function(err) { | ||||
|                             node.client.end(done); | ||||
|                         }); | ||||
|                     } else { | ||||
|                         node.client.end(done); | ||||
|                     } | ||||
|                     return; | ||||
|                 } else { | ||||
|                     if (node.client) { node.client.end(); } | ||||
|                     return done(); | ||||
|                 } | ||||
|             if (!node.closing && node.connected && Object.keys(node.users).length === 0) { | ||||
|                 node.disconnect(); | ||||
|             } | ||||
|             done(); | ||||
|         }; | ||||
| @@ -663,6 +647,7 @@ module.exports = function(RED) { | ||||
|         } | ||||
|         node.connect = function (callback) { | ||||
|             if (node.canConnect()) { | ||||
|                 node.closing = false; | ||||
|                 node.connecting = true; | ||||
|                 setStatusConnecting(node, true); | ||||
|                 try { | ||||
| @@ -672,6 +657,7 @@ module.exports = function(RED) { | ||||
|                     let callbackDone = false; //prevent re-connects causing node.client.on('connect' firing callback multiple times | ||||
|                     // Register successful connect or reconnect handler | ||||
|                     node.client.on('connect', function (connack) { | ||||
|                         node.closing = false; | ||||
|                         node.connecting = false; | ||||
|                         node.connected = true; | ||||
|                         if(!callbackDone && typeof callback == "function") { | ||||
| @@ -740,6 +726,7 @@ module.exports = function(RED) { | ||||
|                             reasonCode: rc, | ||||
|                             reasonString: rs | ||||
|                         } | ||||
|                         node.connected = false; | ||||
|                         node.log(RED._("mqtt.state.broker-disconnected", details)); | ||||
|                         setStatusDisconnected(node, true); | ||||
|                     }); | ||||
| @@ -764,25 +751,31 @@ module.exports = function(RED) { | ||||
|             } | ||||
|         }; | ||||
|         node.disconnect = function (callback) { | ||||
|             const _callback = function () { | ||||
|             const _callback = function (resetNodeConnectedState) { | ||||
|                 setStatusDisconnected(node, true); | ||||
|                 node.connecting = false; | ||||
|                 node.connected = false; | ||||
|                 if(resetNodeConnectedState) { | ||||
|                     node.closing = true; | ||||
|                     node.connecting = false; | ||||
|                     node.connected = false; | ||||
|                 } | ||||
|                 callback && typeof callback == "function" && callback(); | ||||
|             }; | ||||
|  | ||||
|             if(node.client) { | ||||
|                 if(node.client.connected && node.closeMessage) { | ||||
|                     node.publish(node.closeMessage, function (err) { | ||||
|                         node.client.end(_callback); | ||||
|                     }); | ||||
|                 } else if(node.client.connected || node.client.reconnecting) { | ||||
|                     node.client.end(_callback); | ||||
|                 } else if(node.client.disconnecting || node.client.connected === false) { | ||||
|                     _callback(); | ||||
|                 } | ||||
|             if(node.closing) { | ||||
|                 return _callback(false); | ||||
|             } | ||||
|             var endCallBack = function endCallBack() { | ||||
|             } | ||||
|             if(node.connected && node.closeMessage) { | ||||
|                 node.publish(node.closeMessage, function (err) { | ||||
|                     node.client.end(endCallBack); | ||||
|                     _callback(true); | ||||
|                 }); | ||||
|             } else if(node.connected) { | ||||
|                 node.client.end(endCallBack); | ||||
|                 _callback(true); | ||||
|             } else { | ||||
|                 _callback(); | ||||
|                 _callback(false); | ||||
|             } | ||||
|         } | ||||
|         node.subscriptionIds = {}; | ||||
| @@ -1074,6 +1067,8 @@ module.exports = function(RED) { | ||||
|                         node.brokerConn.unsubscribe(node.topic,node.id, removed); | ||||
|                     } | ||||
|                     node.brokerConn.deregister(node, done); | ||||
|                 } else { | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
| @@ -1134,7 +1129,11 @@ module.exports = function(RED) { | ||||
|             } | ||||
|             node.brokerConn.register(node); | ||||
|             node.on('close', function(done) { | ||||
|                 node.brokerConn.deregister(node,done); | ||||
|                 if (node.brokerConn) { | ||||
|                     node.brokerConn.deregister(node,done); | ||||
|                 } else { | ||||
|                     done(); | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             node.error(RED._("mqtt.errors.missing-config")); | ||||
|   | ||||
| @@ -291,8 +291,8 @@ | ||||
|         "hour": "時間", | ||||
|         "days": "日", | ||||
|         "day": "日", | ||||
|         "between": "頻度", | ||||
|         "and": "回/", | ||||
|         "between": "範囲", | ||||
|         "and": "〜", | ||||
|         "rate": "流量", | ||||
|         "msgper": "メッセージ/", | ||||
|         "queuemsg": "中間メッセージをキューに追加", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/nodes", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
| @@ -17,12 +17,12 @@ | ||||
|     "dependencies": { | ||||
|         "acorn": "8.7.0", | ||||
|         "acorn-walk": "8.2.0", | ||||
|         "ajv": "8.9.0", | ||||
|         "ajv": "8.10.0", | ||||
|         "body-parser": "1.19.1", | ||||
|         "cheerio": "1.0.0-rc.10", | ||||
|         "content-type": "1.0.4", | ||||
|         "cookie-parser": "1.4.6", | ||||
|         "cookie": "0.4.1", | ||||
|         "cookie": "0.4.2", | ||||
|         "cors": "2.8.5", | ||||
|         "cronosjs": "1.7.1", | ||||
|         "denque": "2.0.1", | ||||
| @@ -36,11 +36,11 @@ | ||||
|         "is-utf8": "0.2.1", | ||||
|         "js-yaml": "3.14.1", | ||||
|         "media-typer": "1.1.0", | ||||
|         "mqtt": "4.3.4", | ||||
|         "mqtt": "4.3.5", | ||||
|         "multer": "1.4.4", | ||||
|         "mustache": "4.2.0", | ||||
|         "on-headers": "1.0.2", | ||||
|         "raw-body": "2.4.2", | ||||
|         "raw-body": "2.4.3", | ||||
|         "tough-cookie": "4.0.0", | ||||
|         "uuid": "8.3.2", | ||||
|         "ws": "7.5.6", | ||||
|   | ||||
| @@ -353,7 +353,6 @@ async function loadPluginConfig(fileInfo) { | ||||
|  */ | ||||
| function loadNodeSet(node) { | ||||
|     if (!node.enabled) { | ||||
|         console.log("BAIL ON",node.id) | ||||
|         return Promise.resolve(node); | ||||
|     } else { | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/registry", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,11 +16,11 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "2.2.0", | ||||
|         "@node-red/util": "2.2.2", | ||||
|         "clone": "2.1.2", | ||||
|         "fs-extra": "10.0.0", | ||||
|         "semver": "7.3.5", | ||||
|         "tar": "6.1.11", | ||||
|         "uglify-js": "3.15.0" | ||||
|         "uglify-js": "3.15.1" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -77,15 +77,16 @@ function createNode(flow,config) { | ||||
|         if (typeof nodeTypeConstructor === "function") { | ||||
|             var conf = clone(config); | ||||
|             delete conf.credentials; | ||||
|             for (var p in conf) { | ||||
|                 if (conf.hasOwnProperty(p)) { | ||||
|                     mapEnvVarProperties(conf,p,flow,conf); | ||||
|                 } | ||||
|             } | ||||
|             try { | ||||
|                 Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true }) | ||||
|                 Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true }) | ||||
|                 Object.defineProperty(conf,'_path', {value: `${flow.path}/${config._alias||config.id}`, enumerable: false, writable: true }) | ||||
|  | ||||
|                 for (var p in conf) { | ||||
|                     if (conf.hasOwnProperty(p)) { | ||||
|                         mapEnvVarProperties(conf,p,flow,conf); | ||||
|                     } | ||||
|                 } | ||||
|                 newNode = new nodeTypeConstructor(conf); | ||||
|             } catch (err) { | ||||
|                 Log.log({ | ||||
|   | ||||
| @@ -503,10 +503,25 @@ function log_helper(self, level, msg) { | ||||
|         o.name = self.name; | ||||
|     } | ||||
|     // See https://github.com/node-red/node-red/issues/3327 | ||||
|     // See https://github.com/node-red/node-red/issues/3389 | ||||
|  | ||||
|     let srcError; | ||||
|     if (msg instanceof Error) { | ||||
|         srcError = msg;//use existing err object for actual stack | ||||
|     } else { | ||||
|         srcError = new Error(msg);//generate a new error for generate a stack | ||||
|     } | ||||
|     try { | ||||
|         self._flow.log(o); | ||||
|         if(self instanceof Node && self._flow) { | ||||
|             self._flow.log(o); | ||||
|         } else { | ||||
|             //if self._flow is not present, this is not a node-red Node | ||||
|             //Set info to "Node object is not a node-red Node" to point out the `Node type` problem in log | ||||
|             logUnexpectedError(self, srcError, "Node object is not a node-red Node") | ||||
|         } | ||||
|     } catch(err) { | ||||
|         logUnexpectedError(self, err) | ||||
|         //build an unexpected error report indicating using the original error (for better stack trace) | ||||
|         logUnexpectedError(self, srcError, `An error occured attempting to make a log entry: ${err}`) | ||||
|     } | ||||
| } | ||||
| /** | ||||
| @@ -531,7 +546,7 @@ Node.prototype.error = function(logMessage,msg) { | ||||
|         logMessage = logMessage || ""; | ||||
|     } | ||||
|     var handled = false; | ||||
|     if (msg && typeof msg === 'object') { | ||||
|     if (this._flow && msg && typeof msg === 'object') { | ||||
|         handled = this._flow.handleError(this,logMessage,msg); | ||||
|     } | ||||
|     if (!handled) { | ||||
| @@ -619,27 +634,34 @@ function inspectObject(flow) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function logUnexpectedError(node, error) { | ||||
|     let moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined" | ||||
|     Log.error(` | ||||
| function logUnexpectedError(node, error, info) { | ||||
|     const header = ` | ||||
| ******************************************************************** | ||||
| Unexpected Node Error | ||||
| ${error.stack} | ||||
| Node: | ||||
|  Type: ${node.type} | ||||
|  Module: ${moduleInfo} | ||||
|  ID: ${node._alias||node.id} | ||||
|  Properties: | ||||
|   ${inspectObject(node)} | ||||
| Flow: ${node._flow?node._flow.path:'undefined'} | ||||
|  Type: ${node._flow?node._flow.TYPE:'undefined'} | ||||
|  Properties: | ||||
|   ${node._flow?inspectObject(node._flow):'undefined'} | ||||
| ********************************************************************`; | ||||
|  | ||||
|     const footer = ` | ||||
| Please report this issue, including the information logged above: | ||||
| https://github.com/node-red/node-red/issues/ | ||||
| ******************************************************************** | ||||
| `) | ||||
| ********************************************************************`; | ||||
|  | ||||
|     let detail = [`Info:\n ${info || 'No additional info'}`]; | ||||
|  | ||||
|     //Include Error info? | ||||
|     if(error && error.stack){ | ||||
|         detail.push(`Stack:\n ${error.stack}`) | ||||
|     } | ||||
|     //Include Node info? | ||||
|     if(node && (node._module || node.type)){ | ||||
|         const moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined"; | ||||
|         const id = node._alias||node.id||"undefined"; | ||||
|         detail.push(`Node:\n Type: ${node.type}\n Module: ${moduleInfo}\n ID: ${id}\n Properties:\n  ${inspectObject(node)}`) | ||||
|     } | ||||
|     //Include Flow info? | ||||
|     if(node && node._flow){ | ||||
|         detail.push(`Flow: ${node._flow.path}\n Type: ${node._flow.TYPE}\n Properties:\n  ${inspectObject(node._flow)}`) | ||||
|     } | ||||
|     Log.error(`${header}\n${detail.join("\n")}\n${footer}`); | ||||
| } | ||||
|  | ||||
| module.exports = Node; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/runtime", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,8 +16,8 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/registry": "2.2.0", | ||||
|         "@node-red/util": "2.2.0", | ||||
|         "@node-red/registry": "2.2.2", | ||||
|         "@node-red/util": "2.2.2", | ||||
|         "async-mutex": "0.3.2", | ||||
|         "clone": "2.1.2", | ||||
|         "express": "4.17.2", | ||||
|   | ||||
| @@ -67,8 +67,25 @@ function add(hookId, callback) { | ||||
|         throw new Error("Hook "+hookId+" already registered") | ||||
|     } | ||||
|     // Get location of calling code | ||||
|     let callModule; | ||||
|     const stack = new Error().stack; | ||||
|     const callModule = stack.split("\n")[2].split("(")[1].slice(0,-1); | ||||
|     const stackEntries = stack.split("\n").slice(1);//drop 1st line (error message) | ||||
|     const stackEntry2 = stackEntries[1];//get 2nd stack entry | ||||
|     if (stackEntry2) { | ||||
|         try { | ||||
|             if (stackEntry2.indexOf(" (") >= 0) { | ||||
|                 callModule = stackEntry2.split("(")[1].slice(0, -1); | ||||
|             } else { | ||||
|                 callModule = stackEntry2.split(" ").slice(-1)[0]; | ||||
|             } | ||||
|         } catch (error) { | ||||
|             Log.debug(`Unable to determined module when adding hook '${hookId}'. Stack:\n${stackEntries.join("\n")}`); | ||||
|             callModule = "unknown:0:0"; | ||||
|         } | ||||
|     } else { | ||||
|         Log.debug(`Unable to determined module when adding hook '${hookId}'. Stack:\n${stackEntries.join("\n")}`); | ||||
|         callModule = "unknown:0:0"; | ||||
|     } | ||||
|     Log.debug(`Adding hook '${hookId}' from ${callModule}`); | ||||
|  | ||||
|     const hookItem = {cb:callback, location: callModule, previousHook: null, nextHook: null } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/util", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
| @@ -16,9 +16,9 @@ | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "fs-extra": "10.0.0", | ||||
|         "i18next": "21.6.10", | ||||
|         "i18next": "21.6.11", | ||||
|         "json-stringify-safe": "5.0.1", | ||||
|         "jsonata": "1.8.5", | ||||
|         "jsonata": "1.8.6", | ||||
|         "lodash.clonedeep": "^4.5.0", | ||||
|         "moment-timezone": "0.5.34" | ||||
|     } | ||||
|   | ||||
							
								
								
									
										12
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "2.2.0", | ||||
|     "version": "2.2.2", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "http://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -31,15 +31,15 @@ | ||||
|         "flow" | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/editor-api": "2.2.0", | ||||
|         "@node-red/runtime": "2.2.0", | ||||
|         "@node-red/util": "2.2.0", | ||||
|         "@node-red/nodes": "2.2.0", | ||||
|         "@node-red/editor-api": "2.2.2", | ||||
|         "@node-red/runtime": "2.2.2", | ||||
|         "@node-red/util": "2.2.2", | ||||
|         "@node-red/nodes": "2.2.2", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "express": "4.17.2", | ||||
|         "fs-extra": "10.0.0", | ||||
|         "node-red-admin": "^2.2.2", | ||||
|         "node-red-admin": "^2.2.3", | ||||
|         "nopt": "5.0.0", | ||||
|         "semver": "7.3.5" | ||||
|     }, | ||||
|   | ||||
| @@ -242,6 +242,142 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     it('inject name of node as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "NAME"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('inject id of node as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "n1"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('inject path of node as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "flow/n1"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     it('inject name of flow as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "flow", type: "tab", label: "FLOW" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "FLOW"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('inject id of flow as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "flow", type: "tab", name: "FLOW" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "flow"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('inject name of group as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "GROUP"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('inject id of group as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 try { | ||||
|                     msg.should.have.property("payload", "g0"); | ||||
|                     done(); | ||||
|                 } catch (err) { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     it('sets the value of flow context property', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", topic: "t1", payload: "flowValue", payloadType: "flow", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|   | ||||
| @@ -310,6 +310,12 @@ describe('switch Node', function() { | ||||
|     it('should check if payload if of type number 0', function(done) { | ||||
|         genericSwitchTest("istype", "number", true, true, 0, done); | ||||
|     }); | ||||
|     it('should check if payload if of type number NaN', function(done) { | ||||
|         genericSwitchTest("istype", "number", true, false, parseInt("banana"), done); | ||||
|     }); | ||||
|     it('should check if payload if of type number Infinity', function(done) { | ||||
|         genericSwitchTest("istype", "number", true, true, 1/0, done); | ||||
|     }); | ||||
|     it('should check if payload if of type boolean true', function(done) { | ||||
|         genericSwitchTest("istype", "boolean", true, true, true, done); | ||||
|     }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user