Compare commits
	
		
			21 Commits
		
	
	
		
			310-tour
			...
			fix-link-t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					24178beafc | ||
| 
						 | 
					6044871438 | ||
| 
						 | 
					e612bb6a38 | ||
| 
						 | 
					81ea67d6da | ||
| 
						 | 
					06e35baeaa | ||
| 
						 | 
					c9d72d7a1d | ||
| 
						 | 
					62e9572070 | ||
| 
						 | 
					fea68c8acc | ||
| 
						 | 
					929f0e90ac | ||
| 
						 | 
					81331e68d2 | ||
| 
						 | 
					4fda59a585 | ||
| 
						 | 
					ede3ac4282 | ||
| 
						 | 
					16f8b78b39 | ||
| 
						 | 
					892d21fb77 | ||
| 
						 | 
					adfc5b3e98 | ||
| 
						 | 
					f3d7016ab2 | ||
| 
						 | 
					95a7980ada | ||
| 
						 | 
					281e9d1357 | ||
| 
						 | 
					742f05f59d | ||
| 
						 | 
					79db4f8aa1 | ||
| 
						 | 
					f4d7b71984 | 
							
								
								
									
										4
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -19,9 +19,9 @@ jobs:
 | 
			
		||||
      matrix:
 | 
			
		||||
        node-version: [14, 16]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
    - uses: actions/checkout@v3
 | 
			
		||||
    - name: Use Node.js ${{ matrix.node-version }}
 | 
			
		||||
      uses: actions/setup-node@v2
 | 
			
		||||
      uses: actions/setup-node@v3
 | 
			
		||||
      with:
 | 
			
		||||
        node-version: ${{ matrix.node-version }}
 | 
			
		||||
    - name: Install Dependencies
 | 
			
		||||
 
 | 
			
		||||
@@ -15,5 +15,5 @@
 | 
			
		||||
    "shadow": true,     // allow variable shadowing (re-use of names...)
 | 
			
		||||
    "sub": true,        // don't warn that foo['bar'] should be written as foo.bar
 | 
			
		||||
    "proto": true,      // allow setting of __proto__ in node < v0.12,
 | 
			
		||||
    "esversion": 11      // allow es11(ES2020)
 | 
			
		||||
    "esversion": 6      // allow es6
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										91
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,94 +1,3 @@
 | 
			
		||||
#### 3.1.0-beta.1: Beta Release
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 | 
			
		||||
 - NEW: Locking Flows (#3938) @knolleary
 | 
			
		||||
 - NEW: Improve UX around hiding flows via context menu (#3930) @knolleary
 | 
			
		||||
 - NEW: Add support for inline image in markdown editor by drag and drop of an image file (#4006) @HiroyasuNishiyama
 | 
			
		||||
 - NEW: Add support for mermaid diagram to markdown editor (#4007) @HiroyasuNishiyama
 | 
			
		||||
 - NEW: Support uri fragments for nodes and groups including edit support (#3870) @knolleary
 | 
			
		||||
 - NEW: Add global environment variable feature (#3941) @HiroyasuNishiyama
 | 
			
		||||
 | 
			
		||||
 - Remember compact/pretty flow export user choice (#3974) @Steve-Mcl
 | 
			
		||||
 - fix .red-ui-notification class (#4035) @xiaobinqt
 | 
			
		||||
 - Fix border radius on Modules list header (#4038) @bonanitech
 | 
			
		||||
 - fix workspace reference error in case of empty tabs (#4029) @HiroyasuNishiyama
 | 
			
		||||
 - Disable delete tab menu when single tab exists (#4030) @HiroyasuNishiyama
 | 
			
		||||
 - Disable hide all menu if all tabs hidden (#4031) @HiroyasuNishiyama
 | 
			
		||||
 - fix hide subflow tooltip (#4033) @HiroyasuNishiyama
 | 
			
		||||
 - Fix disabled menu items in project feature (#4027) @kazuhitoyokoi
 | 
			
		||||
 - Let themes change radialMenu text colors (#3995) @bonanitech
 | 
			
		||||
 - Add Japanese translations for v3.0.3 (#4012) @kazuhitoyokoi
 | 
			
		||||
 - Add Japanese translation for v3.1.0-beta.0 (#3997) @kazuhitoyokoi
 | 
			
		||||
 - Add Japanese translation for v3.1.0-beta.0 (#3916) @kazuhitoyokoi
 | 
			
		||||
 - Hide subflow category after deleting subflow (#3980) @kazuhitoyokoi
 | 
			
		||||
 - Prevent dbl-click opening node edit dialog with text selected (#3970) @knolleary
 | 
			
		||||
 - Handle replacing unknown node inside group or subflow (#3921) @knolleary
 | 
			
		||||
 - Fix #3939, red border red-ui-typedInput-container (#3949) @Steveorevo
 | 
			
		||||
 - i18n item URL copy notification & add Japanese message (#3946) @HiroyasuNishiyama
 | 
			
		||||
 - add Japanese message for item url copy actions (#3947) @HiroyasuNishiyama
 | 
			
		||||
 - Fix autocomplete entry for responseUrl (#3884) @knolleary
 | 
			
		||||
 - Fix Japanese translation for JSONata editor (#3872) @HiroyasuNishiyama
 | 
			
		||||
 - Fix search type with spaces (#3841) @Steve-Mcl
 | 
			
		||||
 - Fix error hanndling of JSONata expression editor for extended functions (#3871) @HiroyasuNishiyama
 | 
			
		||||
 - Add button type to the adding SSH key button (#3866) @kazuhitoyokoi
 | 
			
		||||
 - Check radio button as default in project dialog (#3879) @kazuhitoyokoi
 | 
			
		||||
 - Add $clone as supported function (#3874) @HiroyasuNishiyama
 | 
			
		||||
 - Env var jsonata (#3807) @HiroyasuNishiyama
 | 
			
		||||
 - Add Japanese translation for v3.0.2 (#3852) @kazuhitoyokoi
 | 
			
		||||
 | 
			
		||||
Runtime
 | 
			
		||||
 | 
			
		||||
 - Force IPv4 name resolution to have priority (#4019) @dceejay
 | 
			
		||||
 - Fix async loading of modules containing both nodes and plugins (#3999) @knolleary
 | 
			
		||||
 - Use main branch as default in project feature (#4036) @kazuhitoyokoi
 | 
			
		||||
 - Rename package var to avoid strict mode error (#4020) @knolleary
 | 
			
		||||
 - Fix typos in settings.js (#4013) @ypid
 | 
			
		||||
 - Ensure credentials object is removed before returning node in getFlow request (#3971) @knolleary
 | 
			
		||||
 - Ignore commit error in project feature (#3987) @kazuhitoyokoi
 | 
			
		||||
 - Update dependencies (#3969) @knolleary
 | 
			
		||||
 - Add check that node sends object rather than primitive type (#3909) @knolleary
 | 
			
		||||
 - Ensure key_path is quoted in GIT_SSH_COMMAND in case of spaces in pathname (#3912) @knolleary
 | 
			
		||||
 - Fix nodesDir scan when node package has js/html in sub dir to package.json (#3867) @Steve-Mcl
 | 
			
		||||
 - Fix file permissions (#3917) @kazuhitoyokoi
 | 
			
		||||
 - ci: add minimum GitHub token permissions for workflows (#3907) @boahc077
 | 
			
		||||
 | 
			
		||||
Nodes
 | 
			
		||||
 | 
			
		||||
 - Catch: fix typo in catch.html (#3965) @we11adam
 | 
			
		||||
 - Change: Fix change node overwriting msg with itself (#3899) @dceejay
 | 
			
		||||
 - Comment node: Clarify where the text will appear (#4004) @dirkjanfaber
 | 
			
		||||
 - CSV: change replace to replaceAll (#3990) @dceejay
 | 
			
		||||
 - CSV node: check header properties for ' and " (#3920) @dceejay
 | 
			
		||||
 - CSV: Fix for CSV undefined property (#3906) @dceejay
 | 
			
		||||
 - Delay: let delay node handle both flush then reset (#3898) @dceejay
 | 
			
		||||
 - Function: Limit number of ports in function node (#3886) @kazuhitoyokoi
 | 
			
		||||
 - Function: Remove dot from variable name for external module in function node (#3880) @kazuhitoyokoi
 | 
			
		||||
 - Function: add function node monaco types util and promisify (#3868) @Steve-Mcl
 | 
			
		||||
 - HTTP In: Ensure msg.req.headers is enumerable (#3908) @knolleary
 | 
			
		||||
 - HTTP Request: Support form-data arrays (#3991) @hardillb
 | 
			
		||||
 - HTTP Request: Fix httprequest tests to be more lenient on error message (#3922) @knolleary
 | 
			
		||||
 - HTTP Request: Add missing property to node object HTTPRequest (#3842) @hardillb
 | 
			
		||||
 - HTTP Request/Response: Support sortable list on property UI of http request and http response nodes (#3857) @kazuhitoyokoi
 | 
			
		||||
 - HTTP Response: Ensure statusCode is a number (#3894) @hardillb
 | 
			
		||||
 - Inject: Allow Inject node to work with async context stores (#4021) @knolleary
 | 
			
		||||
 - Join/Batch: Add count to join and batch node labels (#4028) @dceejay
 | 
			
		||||
 - MQTT: Fix birth topic handling in MQTT node (#3905) @Steve-Mcl
 | 
			
		||||
 - MQTT: Fix pull-down menus of MQTT configuration node (#3890) @kazuhitoyokoi
 | 
			
		||||
 - MQTT: Prevent invalid mqtt birth topic crashing node-red (#3869) @Steve-Mcl
 | 
			
		||||
 - MQTT: ensure sessionExpiry(Interval) is applied (#3840) @Steve-Mcl
 | 
			
		||||
 - MQTT: Fix mqtt nodes not reconnecting on modified-flows deploy (#3992) @knolleary
 | 
			
		||||
 - MQTT: fix single subscription mqtt node status (#3966) @Steve-Mcl
 | 
			
		||||
 - Range: Add drop mode to range node (#3935) @dceejay
 | 
			
		||||
 - Remove done from describe (#3873) @HiroyasuNishiyama
 | 
			
		||||
 - Split node: avoid duplicate done call for buffer split (#4000) @knolleary
 | 
			
		||||
 - Status: Fix typo in 25-status.html (#3981) @kazuhitoyokoi
 | 
			
		||||
 - TCP Node: ensure newline substitution applies to whole message (#4009) @dceejay
 | 
			
		||||
 - Template: Add information about environment variable to template node (#3882) @kazuhitoyokoi
 | 
			
		||||
 - Trigger: Hide trigger node repeat send option if sending nothing (#4023) @dceejay
 | 
			
		||||
 - Watch: fix watch node test on MacOS/ARM (#3942) @HiroyasuNishiyama
 | 
			
		||||
 | 
			
		||||
#### 3.0.2: Maintenance Release
 | 
			
		||||
 | 
			
		||||
Editor
 | 
			
		||||
 
 | 
			
		||||
@@ -151,7 +151,6 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/history.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/validators.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
 | 
			
		||||
@@ -170,7 +169,6 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/env-var.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js",
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/src/js/ui/view.js",
 | 
			
		||||
@@ -226,7 +224,7 @@ module.exports = function(grunt) {
 | 
			
		||||
                        "node_modules/jsonata/jsonata-es5.min.js",
 | 
			
		||||
                        "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js",
 | 
			
		||||
                        "packages/node_modules/@node-red/editor-client/src/vendor/ace/ace.js",
 | 
			
		||||
                        "packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js"
 | 
			
		||||
                        "packages/node_modules/@node-red/editor-client/src/vendor/ace/ext-language_tools.js",
 | 
			
		||||
                    ],
 | 
			
		||||
                    // "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [
 | 
			
		||||
                    //     // TODO: resolve relative resource paths in
 | 
			
		||||
@@ -235,9 +233,6 @@ module.exports = function(grunt) {
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [
 | 
			
		||||
                        "node_modules/jsonata/jsonata-es5.min.js",
 | 
			
		||||
                        "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/worker-jsonata.js"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "packages/node_modules/@node-red/editor-client/public/vendor/mermaid/mermaid.min.js": [
 | 
			
		||||
                        "node_modules/mermaid/dist/mermaid.min.js"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "node-red",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "description": "Low-code programming for event-driven applications",
 | 
			
		||||
    "homepage": "http://nodered.org",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
@@ -26,13 +26,13 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "acorn": "8.8.1",
 | 
			
		||||
        "acorn": "8.7.1",
 | 
			
		||||
        "acorn-walk": "8.2.0",
 | 
			
		||||
        "ajv": "8.11.2",
 | 
			
		||||
        "async-mutex": "0.4.0",
 | 
			
		||||
        "ajv": "8.11.0",
 | 
			
		||||
        "async-mutex": "0.3.2",
 | 
			
		||||
        "basic-auth": "2.0.1",
 | 
			
		||||
        "bcryptjs": "2.4.3",
 | 
			
		||||
        "body-parser": "1.20.1",
 | 
			
		||||
        "body-parser": "1.20.0",
 | 
			
		||||
        "cheerio": "1.0.0-rc.10",
 | 
			
		||||
        "clone": "2.1.2",
 | 
			
		||||
        "content-type": "1.0.4",
 | 
			
		||||
@@ -41,15 +41,15 @@
 | 
			
		||||
        "cors": "2.8.5",
 | 
			
		||||
        "cronosjs": "1.7.1",
 | 
			
		||||
        "denque": "2.1.0",
 | 
			
		||||
        "express": "4.18.2",
 | 
			
		||||
        "express": "4.18.1",
 | 
			
		||||
        "express-session": "1.17.3",
 | 
			
		||||
        "form-data": "4.0.0",
 | 
			
		||||
        "fs-extra": "10.1.0",
 | 
			
		||||
        "got": "11.8.5",
 | 
			
		||||
        "hash-sum": "2.0.0",
 | 
			
		||||
        "hpagent": "1.2.0",
 | 
			
		||||
        "hpagent": "1.0.0",
 | 
			
		||||
        "https-proxy-agent": "5.0.1",
 | 
			
		||||
        "i18next": "21.10.0",
 | 
			
		||||
        "i18next": "21.8.16",
 | 
			
		||||
        "iconv-lite": "0.6.3",
 | 
			
		||||
        "is-utf8": "0.2.1",
 | 
			
		||||
        "js-yaml": "4.1.0",
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
        "memorystore": "1.6.7",
 | 
			
		||||
        "mime": "3.0.0",
 | 
			
		||||
        "moment": "2.29.4",
 | 
			
		||||
        "moment-timezone": "0.5.39",
 | 
			
		||||
        "moment-timezone": "0.5.34",
 | 
			
		||||
        "mqtt": "4.3.7",
 | 
			
		||||
        "multer": "1.4.5-lts.1",
 | 
			
		||||
        "mustache": "4.2.0",
 | 
			
		||||
@@ -73,19 +73,19 @@
 | 
			
		||||
        "passport-http-bearer": "1.0.1",
 | 
			
		||||
        "passport-oauth2-client-password": "0.1.2",
 | 
			
		||||
        "raw-body": "2.5.1",
 | 
			
		||||
        "semver": "7.3.8",
 | 
			
		||||
        "tar": "6.1.12",
 | 
			
		||||
        "tough-cookie": "4.1.2",
 | 
			
		||||
        "uglify-js": "3.17.4",
 | 
			
		||||
        "semver": "7.3.7",
 | 
			
		||||
        "tar": "6.1.11",
 | 
			
		||||
        "tough-cookie": "4.0.0",
 | 
			
		||||
        "uglify-js": "3.16.3",
 | 
			
		||||
        "uuid": "8.3.2",
 | 
			
		||||
        "ws": "7.5.6",
 | 
			
		||||
        "xml2js": "0.4.23"
 | 
			
		||||
    },
 | 
			
		||||
    "optionalDependencies": {
 | 
			
		||||
        "bcrypt": "5.1.0"
 | 
			
		||||
        "bcrypt": "5.0.1"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "dompurify": "2.4.1",
 | 
			
		||||
        "dompurify": "2.3.10",
 | 
			
		||||
        "grunt": "1.5.3",
 | 
			
		||||
        "grunt-chmod": "~1.1.1",
 | 
			
		||||
        "grunt-cli": "~1.4.3",
 | 
			
		||||
@@ -108,14 +108,13 @@
 | 
			
		||||
        "i18next-http-backend": "1.4.1",
 | 
			
		||||
        "jquery-i18next": "1.2.1",
 | 
			
		||||
        "jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
 | 
			
		||||
        "marked": "4.2.3",
 | 
			
		||||
        "mermaid": "^9.3.0",
 | 
			
		||||
        "marked": "4.0.18",
 | 
			
		||||
        "minami": "1.2.3",
 | 
			
		||||
        "mocha": "9.2.2",
 | 
			
		||||
        "node-red-node-test-helper": "^0.3.0",
 | 
			
		||||
        "nodemon": "2.0.20",
 | 
			
		||||
        "nodemon": "2.0.19",
 | 
			
		||||
        "proxy": "^1.0.2",
 | 
			
		||||
        "sass": "1.56.1",
 | 
			
		||||
        "sass": "1.54.2",
 | 
			
		||||
        "should": "13.2.3",
 | 
			
		||||
        "sinon": "11.1.2",
 | 
			
		||||
        "stoppable": "^1.1.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/editor-api",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "main": "./lib/index.js",
 | 
			
		||||
    "repository": {
 | 
			
		||||
@@ -16,14 +16,14 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@node-red/util": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/editor-client": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/util": "3.0.2",
 | 
			
		||||
        "@node-red/editor-client": "3.0.2",
 | 
			
		||||
        "bcryptjs": "2.4.3",
 | 
			
		||||
        "body-parser": "1.20.1",
 | 
			
		||||
        "body-parser": "1.20.0",
 | 
			
		||||
        "clone": "2.1.2",
 | 
			
		||||
        "cors": "2.8.5",
 | 
			
		||||
        "express-session": "1.17.3",
 | 
			
		||||
        "express": "4.18.2",
 | 
			
		||||
        "express": "4.18.1",
 | 
			
		||||
        "memorystore": "1.6.7",
 | 
			
		||||
        "mime": "3.0.0",
 | 
			
		||||
        "multer": "1.4.5-lts.1",
 | 
			
		||||
@@ -35,6 +35,6 @@
 | 
			
		||||
        "ws": "7.5.6"
 | 
			
		||||
    },
 | 
			
		||||
    "optionalDependencies": {
 | 
			
		||||
        "bcrypt": "5.1.0"
 | 
			
		||||
        "bcrypt": "5.0.1"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -53,30 +53,22 @@
 | 
			
		||||
        "confirmDelete": "Confirm delete",
 | 
			
		||||
        "delete": "Are you sure you want to delete '__label__'?",
 | 
			
		||||
        "dropFlowHere": "Drop the flow here",
 | 
			
		||||
        "dropImageHere": "Drop the image here",
 | 
			
		||||
        "addFlow": "Add flow",
 | 
			
		||||
        "addFlowToRight": "Add flow to the right",
 | 
			
		||||
        "closeFlow": "Close flow",
 | 
			
		||||
        "hideFlow": "Hide flow",
 | 
			
		||||
        "hideOtherFlows": "Hide other flows",
 | 
			
		||||
        "showAllFlows": "Show all flows (__count__ hidden)",
 | 
			
		||||
        "showAllFlows": "Show all flows",
 | 
			
		||||
        "hideAllFlows": "Hide all flows",
 | 
			
		||||
        "hiddenFlows": "List __count__ hidden flow",
 | 
			
		||||
        "hiddenFlows_plural": "List __count__ hidden flows",
 | 
			
		||||
        "showLastHiddenFlow": "Reopen hidden flow",
 | 
			
		||||
        "showLastHiddenFlow": "Show last hidden flow",
 | 
			
		||||
        "listFlows": "List flows",
 | 
			
		||||
        "listSubflows": "List subflows",
 | 
			
		||||
        "status": "Status",
 | 
			
		||||
        "enabled": "Enabled",
 | 
			
		||||
        "disabled": "Disabled",
 | 
			
		||||
        "info": "Description",
 | 
			
		||||
        "selectNodes": "Click nodes to select",
 | 
			
		||||
        "enableFlow": "Enable flow",
 | 
			
		||||
        "disableFlow": "Disable flow",
 | 
			
		||||
        "lockFlow": "Lock flow",
 | 
			
		||||
        "unlockFlow": "Unlock flow",
 | 
			
		||||
        "moveToStart": "Move flow to start",
 | 
			
		||||
        "moveToEnd": "Move flow to end"
 | 
			
		||||
        "selectNodes": "Click nodes to select"
 | 
			
		||||
    },
 | 
			
		||||
    "menu": {
 | 
			
		||||
        "label": {
 | 
			
		||||
@@ -109,7 +101,6 @@
 | 
			
		||||
            "displayStatus": "Show node status",
 | 
			
		||||
            "displayConfig": "Configuration nodes",
 | 
			
		||||
            "import": "Import",
 | 
			
		||||
            "importExample": "Import Example Flow",
 | 
			
		||||
            "export": "Export",
 | 
			
		||||
            "search": "Search flows",
 | 
			
		||||
            "searchInput": "search your flows",
 | 
			
		||||
@@ -506,7 +497,6 @@
 | 
			
		||||
        "addRemoveNode": "Add/remove node from selection",
 | 
			
		||||
        "editSelected": "Edit selected node",
 | 
			
		||||
        "deleteSelected": "Delete selected nodes or link",
 | 
			
		||||
        "deleteReconnect": "Delete and Reconnect",
 | 
			
		||||
        "importNode": "Import nodes",
 | 
			
		||||
        "exportNode": "Export nodes",
 | 
			
		||||
        "nudgeNode": "Move selected nodes (1px)",
 | 
			
		||||
@@ -693,9 +683,9 @@
 | 
			
		||||
            "empty": "empty",
 | 
			
		||||
            "globalConfig": "Global Configuration Nodes",
 | 
			
		||||
            "triggerAction": "Trigger action",
 | 
			
		||||
            "find": "Find in workspace",
 | 
			
		||||
            "copyItemUrl": "Copy item url",
 | 
			
		||||
	    "copyURL2Clipboard": "Copied url to clipboard"
 | 
			
		||||
            "showFlow": "Show",
 | 
			
		||||
            "hideFlow": "Hide",
 | 
			
		||||
            "find": "Find in workspace"
 | 
			
		||||
        },
 | 
			
		||||
        "help": {
 | 
			
		||||
            "name": "Help",
 | 
			
		||||
@@ -996,10 +986,7 @@
 | 
			
		||||
        "quote": "Quote",
 | 
			
		||||
        "link": "Link",
 | 
			
		||||
        "horizontal-rule": "Horizontal rule",
 | 
			
		||||
        "toggle-preview": "Toggle preview",
 | 
			
		||||
        "mermaid": {
 | 
			
		||||
            "summary": "Mermaid Diagram"
 | 
			
		||||
        }
 | 
			
		||||
        "toggle-preview": "Toggle preview"
 | 
			
		||||
    },
 | 
			
		||||
    "bufferEditor": {
 | 
			
		||||
        "title": "Buffer editor",
 | 
			
		||||
@@ -1221,10 +1208,5 @@
 | 
			
		||||
        "node": "Node",
 | 
			
		||||
        "junction": "Junction",
 | 
			
		||||
        "linkNodes": "Link Nodes"
 | 
			
		||||
    },
 | 
			
		||||
    "env-var": {
 | 
			
		||||
        "environment": "Environment",
 | 
			
		||||
        "header": "Global Environment Variables",
 | 
			
		||||
        "revert": "Revert"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -53,10 +53,8 @@
 | 
			
		||||
        "confirmDelete": "削除の確認",
 | 
			
		||||
        "delete": "本当に '__label__' を削除しますか?",
 | 
			
		||||
        "dropFlowHere": "ここにフローをドロップしてください",
 | 
			
		||||
        "dropImageHere": "ここに画像ファイルをドロップしてください",
 | 
			
		||||
        "addFlow": "フローの追加",
 | 
			
		||||
        "addFlowToRight": "右側にフローを追加",
 | 
			
		||||
        "closeFlow": "フローを閉じる",
 | 
			
		||||
        "hideFlow": "フローを非表示",
 | 
			
		||||
        "hideOtherFlows": "他のフローを非表示",
 | 
			
		||||
        "showAllFlows": "全てのフローを表示",
 | 
			
		||||
@@ -70,11 +68,7 @@
 | 
			
		||||
        "enabled": "有効",
 | 
			
		||||
        "disabled": "無効",
 | 
			
		||||
        "info": "詳細",
 | 
			
		||||
        "selectNodes": "ノードをクリックして選択",
 | 
			
		||||
        "enableFlow": "フローを有効化",
 | 
			
		||||
        "disableFlow": "フローを無効化",
 | 
			
		||||
        "moveToStart": "フローを先頭へ移動",
 | 
			
		||||
        "moveToEnd": "フローを最後へ移動"
 | 
			
		||||
        "selectNodes": "ノードをクリックして選択"
 | 
			
		||||
    },
 | 
			
		||||
    "menu": {
 | 
			
		||||
        "label": {
 | 
			
		||||
@@ -689,9 +683,9 @@
 | 
			
		||||
            "empty": "空",
 | 
			
		||||
            "globalConfig": "グローバル設定ノード",
 | 
			
		||||
            "triggerAction": "アクションを実行",
 | 
			
		||||
            "find": "ワークスペース内を検索",
 | 
			
		||||
            "copyItemUrl": "要素のURLをコピー",
 | 
			
		||||
            "copyURL2Clipboard": "URLをクリップボードにコピーしました"
 | 
			
		||||
            "showFlow": "表示",
 | 
			
		||||
            "hideFlow": "非表示",
 | 
			
		||||
            "find": "ワークスペース内を検索"
 | 
			
		||||
        },
 | 
			
		||||
        "help": {
 | 
			
		||||
            "name": "ヘルプ",
 | 
			
		||||
@@ -992,10 +986,7 @@
 | 
			
		||||
        "quote": "引用",
 | 
			
		||||
        "link": "リンク",
 | 
			
		||||
        "horizontal-rule": "区切り線",
 | 
			
		||||
        "toggle-preview": "プレビュー表示切替え",
 | 
			
		||||
        "mermaid": {
 | 
			
		||||
            "summary": "Mermaid図"
 | 
			
		||||
        }
 | 
			
		||||
        "toggle-preview": "プレビュー表示切替え"
 | 
			
		||||
    },
 | 
			
		||||
    "bufferEditor": {
 | 
			
		||||
        "title": "バッファエディタ",
 | 
			
		||||
@@ -1361,15 +1352,6 @@
 | 
			
		||||
        "show-project-settings": "プロジェクト設定を表示",
 | 
			
		||||
        "show-version-control-tab": "バージョンコントロールタブを表示",
 | 
			
		||||
        "start-flows": "フローを開始",
 | 
			
		||||
        "stop-flows": "フローを停止",
 | 
			
		||||
        "copy-item-url": "要素のURLをコピー",
 | 
			
		||||
        "copy-item-edit-url": "要素の編集URLをコピー",
 | 
			
		||||
        "move-flow-to-start": "フローを先頭に移動",
 | 
			
		||||
        "move-flow-to-end": "フローを末尾に移動"
 | 
			
		||||
    },
 | 
			
		||||
    "env-var": {
 | 
			
		||||
        "environment": "環境変数",
 | 
			
		||||
        "header": "大域環境変数",
 | 
			
		||||
        "revert": "破棄"
 | 
			
		||||
        "stop-flows": "フローを停止"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/editor-client",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
 
 | 
			
		||||
@@ -434,9 +434,7 @@ RED.history = (function() {
 | 
			
		||||
 | 
			
		||||
                if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('disabled')) {
 | 
			
		||||
                    $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!ev.node.disabled);
 | 
			
		||||
                }
 | 
			
		||||
                if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('locked')) {
 | 
			
		||||
                    $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!ev.node.locked);
 | 
			
		||||
                    $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!ev.node.disabled);
 | 
			
		||||
                }
 | 
			
		||||
                if (ev.subflow) {
 | 
			
		||||
                    inverseEv.subflow = {};
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
 * @namespace RED.nodes
 | 
			
		||||
*/
 | 
			
		||||
RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    var PORT_TYPE_INPUT = 1;
 | 
			
		||||
    var PORT_TYPE_OUTPUT = 0;
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +63,6 @@ RED.nodes = (function() {
 | 
			
		||||
            defaults: {
 | 
			
		||||
                label: {value:""},
 | 
			
		||||
                disabled: {value: false},
 | 
			
		||||
                locked: {value: false},
 | 
			
		||||
                info: {value: ""},
 | 
			
		||||
                env: {value: []}
 | 
			
		||||
            }
 | 
			
		||||
@@ -575,48 +575,15 @@ RED.nodes = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const nodeProxyHandler = {
 | 
			
		||||
        get(node, prop) {
 | 
			
		||||
            if (prop === '__isProxy__') {
 | 
			
		||||
                return true
 | 
			
		||||
            } else if (prop == '__node__') {
 | 
			
		||||
                return node
 | 
			
		||||
            }
 | 
			
		||||
            return node[prop]
 | 
			
		||||
        },
 | 
			
		||||
        set(node, prop, value) {
 | 
			
		||||
            if (node.z && (RED.nodes.workspace(node.z)?.locked || RED.nodes.subflow(node.z)?.locked)) {
 | 
			
		||||
                if (
 | 
			
		||||
                    node._def.defaults[prop] ||
 | 
			
		||||
                    prop === 'z' ||
 | 
			
		||||
                    prop === 'l' ||
 | 
			
		||||
                    prop === 'd' ||
 | 
			
		||||
                    (prop === 'changed' && (!!node.changed) !== (!!value)) || // jshint ignore:line
 | 
			
		||||
                    ((prop === 'x' || prop === 'y') && !node.resize && node.type !== 'group')
 | 
			
		||||
                ) {
 | 
			
		||||
                    throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            node[prop] = value;
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addNode(n) {
 | 
			
		||||
        let newNode
 | 
			
		||||
        if (!n.__isProxy__) {
 | 
			
		||||
            newNode = new Proxy(n, nodeProxyHandler)
 | 
			
		||||
        } else {
 | 
			
		||||
            newNode = n
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (n.type.indexOf("subflow") !== 0) {
 | 
			
		||||
            n["_"] = n._def._;
 | 
			
		||||
        } else {
 | 
			
		||||
            var subflowId = n.type.substring(8);
 | 
			
		||||
            var sf = RED.nodes.subflow(subflowId);
 | 
			
		||||
            if (sf) {
 | 
			
		||||
                sf.instances.push(newNode);
 | 
			
		||||
                sf.instances.push(sf);
 | 
			
		||||
            }
 | 
			
		||||
            n["_"] = RED._;
 | 
			
		||||
        }
 | 
			
		||||
@@ -633,13 +600,12 @@ RED.nodes = (function() {
 | 
			
		||||
                });
 | 
			
		||||
                n.i = nextId+1;
 | 
			
		||||
            }
 | 
			
		||||
            allNodes.addNode(newNode);
 | 
			
		||||
            allNodes.addNode(n);
 | 
			
		||||
            if (!nodeLinks[n.id]) {
 | 
			
		||||
                nodeLinks[n.id] = {in:[],out:[]};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        RED.events.emit('nodes:add',newNode);
 | 
			
		||||
        return newNode
 | 
			
		||||
        RED.events.emit('nodes:add',n);
 | 
			
		||||
    }
 | 
			
		||||
    function addLink(l) {
 | 
			
		||||
        if (nodeLinks[l.source.id]) {
 | 
			
		||||
@@ -1080,9 +1046,6 @@ RED.nodes = (function() {
 | 
			
		||||
        node.type = n.type;
 | 
			
		||||
        for (var d in n._def.defaults) {
 | 
			
		||||
            if (n._def.defaults.hasOwnProperty(d)) {
 | 
			
		||||
                if (d === 'locked' && !n.locked) {
 | 
			
		||||
                    continue
 | 
			
		||||
                }
 | 
			
		||||
                node[d] = n[d];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -2352,6 +2315,19 @@ RED.nodes = (function() {
 | 
			
		||||
            if (n.g && !new_group_set.has(n.g)) {
 | 
			
		||||
                delete n.g;
 | 
			
		||||
            }
 | 
			
		||||
            n.nodes = n.nodes.map(function(id) {
 | 
			
		||||
                return node_map[id];
 | 
			
		||||
            })
 | 
			
		||||
            // Just in case the group references a node that doesn't exist for some reason
 | 
			
		||||
            n.nodes = n.nodes.filter(function(v) {
 | 
			
		||||
                if (v) {
 | 
			
		||||
                    // Repair any nodes that have forgotten they are in this group
 | 
			
		||||
                    if (v.g !== n.id) {
 | 
			
		||||
                        v.g = n.id;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return !!v
 | 
			
		||||
            });
 | 
			
		||||
            if (!n.g) {
 | 
			
		||||
                groupDepthMap[n.id] = 0;
 | 
			
		||||
            }
 | 
			
		||||
@@ -2374,22 +2350,21 @@ RED.nodes = (function() {
 | 
			
		||||
            return groupDepthMap[A.id] - groupDepthMap[B.id];
 | 
			
		||||
        });
 | 
			
		||||
        for (i=0;i<new_groups.length;i++) {
 | 
			
		||||
            new_groups[i] = addGroup(new_groups[i]);
 | 
			
		||||
            node_map[new_groups[i].id] = new_groups[i]
 | 
			
		||||
            n = new_groups[i];
 | 
			
		||||
            addGroup(n);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (i=0;i<new_junctions.length;i++) {
 | 
			
		||||
            new_junctions[i] = addJunction(new_junctions[i]);
 | 
			
		||||
            node_map[new_junctions[i].id] = new_junctions[i]
 | 
			
		||||
            var junction = new_junctions[i];
 | 
			
		||||
            addJunction(junction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Now the nodes have been fully updated, add them.
 | 
			
		||||
        for (i=0;i<new_nodes.length;i++) {
 | 
			
		||||
            new_nodes[i] = addNode(new_nodes[i])
 | 
			
		||||
            node_map[new_nodes[i].id] = new_nodes[i]
 | 
			
		||||
            var node = new_nodes[i];
 | 
			
		||||
            addNode(node);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Finally validate them all.
 | 
			
		||||
        // This has to be done after everything is added so that any checks for
 | 
			
		||||
        // dependent config nodes will pass
 | 
			
		||||
@@ -2397,39 +2372,6 @@ RED.nodes = (function() {
 | 
			
		||||
            var node = new_nodes[i];
 | 
			
		||||
            RED.editor.validateNode(node);
 | 
			
		||||
        }
 | 
			
		||||
        const lookupNode = (id) => {
 | 
			
		||||
            const mappedNode = node_map[id]
 | 
			
		||||
            if (!mappedNode) {
 | 
			
		||||
                return null
 | 
			
		||||
            }
 | 
			
		||||
            if (mappedNode.__isProxy__) {
 | 
			
		||||
                return mappedNode
 | 
			
		||||
            } else {
 | 
			
		||||
                return node_map[mappedNode.id]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Update groups to reference proxy node objects
 | 
			
		||||
        for (i=0;i<new_groups.length;i++) {
 | 
			
		||||
            n = new_groups[i];
 | 
			
		||||
            // bypass the proxy in case the flow is locked
 | 
			
		||||
            n.__node__.nodes = n.nodes.map(lookupNode)
 | 
			
		||||
            // Just in case the group references a node that doesn't exist for some reason
 | 
			
		||||
            n.__node__.nodes = n.nodes.filter(function(v) {
 | 
			
		||||
                if (v) {
 | 
			
		||||
                    // Repair any nodes that have forgotten they are in this group
 | 
			
		||||
                    if (v.g !== n.id) {
 | 
			
		||||
                        v.g = n.id;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return !!v
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update links to use proxy node objects
 | 
			
		||||
        for (i=0;i<new_links.length;i++) {
 | 
			
		||||
            new_links[i].source = lookupNode(new_links[i].source.id) || new_links[i].source
 | 
			
		||||
            new_links[i].target = lookupNode(new_links[i].target.id) || new_links[i].target
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RED.workspaces.refresh();
 | 
			
		||||
 | 
			
		||||
@@ -2558,17 +2500,11 @@ RED.nodes = (function() {
 | 
			
		||||
        junctions = {};
 | 
			
		||||
        junctionsByZ = {};
 | 
			
		||||
 | 
			
		||||
        var workspaceIds = Object.keys(workspaces);
 | 
			
		||||
        // Ensure all workspaces are unlocked so we don't get any edit-protection
 | 
			
		||||
        // preventing removal
 | 
			
		||||
        workspaceIds.forEach(function(id) {
 | 
			
		||||
            workspaces[id].locked = false
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var subflowIds = Object.keys(subflows);
 | 
			
		||||
        subflowIds.forEach(function(id) {
 | 
			
		||||
            RED.subflow.removeSubflow(id)
 | 
			
		||||
        });
 | 
			
		||||
        var workspaceIds = Object.keys(workspaces);
 | 
			
		||||
        workspaceIds.forEach(function(id) {
 | 
			
		||||
            RED.workspaces.remove(workspaces[id]);
 | 
			
		||||
        });
 | 
			
		||||
@@ -2589,14 +2525,10 @@ RED.nodes = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addGroup(group) {
 | 
			
		||||
        if (!group.__isProxy__) {
 | 
			
		||||
            group = new Proxy(group, nodeProxyHandler)
 | 
			
		||||
        }
 | 
			
		||||
        groupsByZ[group.z] = groupsByZ[group.z] || [];
 | 
			
		||||
        groupsByZ[group.z].push(group);
 | 
			
		||||
        groups[group.id] = group;
 | 
			
		||||
        RED.events.emit("groups:add",group);
 | 
			
		||||
        return group
 | 
			
		||||
    }
 | 
			
		||||
    function removeGroup(group) {
 | 
			
		||||
        var i = groupsByZ[group.z].indexOf(group);
 | 
			
		||||
@@ -2617,9 +2549,6 @@ RED.nodes = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addJunction(junction) {
 | 
			
		||||
        if (!junction.__isProxy__) {
 | 
			
		||||
            junction = new Proxy(junction, nodeProxyHandler)
 | 
			
		||||
        }
 | 
			
		||||
        junctionsByZ[junction.z] = junctionsByZ[junction.z] || []
 | 
			
		||||
        junctionsByZ[junction.z].push(junction)
 | 
			
		||||
        junctions[junction.id] = junction;
 | 
			
		||||
@@ -2627,7 +2556,6 @@ RED.nodes = (function() {
 | 
			
		||||
            nodeLinks[junction.id] = {in:[],out:[]};
 | 
			
		||||
        }
 | 
			
		||||
        RED.events.emit("junctions:add", junction)
 | 
			
		||||
        return junction
 | 
			
		||||
    }
 | 
			
		||||
    function removeJunction(junction) {
 | 
			
		||||
        var i = junctionsByZ[junction.z].indexOf(junction)
 | 
			
		||||
@@ -2922,7 +2850,7 @@ RED.nodes = (function() {
 | 
			
		||||
        },
 | 
			
		||||
        addWorkspace: addWorkspace,
 | 
			
		||||
        removeWorkspace: removeWorkspace,
 | 
			
		||||
        getWorkspaceOrder: function() { return [...workspacesOrder] },
 | 
			
		||||
        getWorkspaceOrder: function() { return workspacesOrder },
 | 
			
		||||
        setWorkspaceOrder: function(order) { workspacesOrder = order; },
 | 
			
		||||
        workspace: getWorkspace,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -249,35 +249,8 @@ var RED = (function() {
 | 
			
		||||
                        RED.nodes.import(nodes.flows);
 | 
			
		||||
                        RED.nodes.dirty(false);
 | 
			
		||||
                        RED.view.redraw(true);
 | 
			
		||||
                        if (/^#(flow|node|group)\/.+$/.test(currentHash)) {
 | 
			
		||||
                            const hashParts = currentHash.split('/')
 | 
			
		||||
                            const showEditDialog = hashParts.length > 2 && hashParts[2] === 'edit'
 | 
			
		||||
                            if (hashParts[0] === '#flow') {
 | 
			
		||||
                                RED.workspaces.show(hashParts[1], true);
 | 
			
		||||
                                if (showEditDialog) {
 | 
			
		||||
                                    RED.workspaces.edit()
 | 
			
		||||
                                }
 | 
			
		||||
                            } else if (hashParts[0] === '#node') {
 | 
			
		||||
                                const nodeToShow = RED.nodes.node(hashParts[1])
 | 
			
		||||
                                if (nodeToShow) {
 | 
			
		||||
                                    setTimeout(() => {
 | 
			
		||||
                                        RED.view.reveal(nodeToShow.id)
 | 
			
		||||
                                        window.location.hash = currentHash
 | 
			
		||||
                                        if (showEditDialog) {
 | 
			
		||||
                                            RED.editor.edit(nodeToShow)
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }, 50)
 | 
			
		||||
                                }
 | 
			
		||||
                            } else if (hashParts[0] === '#group') {
 | 
			
		||||
                                const nodeToShow = RED.nodes.group(hashParts[1])
 | 
			
		||||
                                if (nodeToShow) {
 | 
			
		||||
                                    RED.view.reveal(nodeToShow.id)
 | 
			
		||||
                                    window.location.hash = currentHash
 | 
			
		||||
                                    if (showEditDialog) {
 | 
			
		||||
                                        RED.editor.editGroup(nodeToShow)
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        if (/^#flow\/.+$/.test(currentHash)) {
 | 
			
		||||
                            RED.workspaces.show(currentHash.substring(6),true);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (RED.workspaces.count() > 0) {
 | 
			
		||||
                            const hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
@@ -670,6 +643,11 @@ var RED = (function() {
 | 
			
		||||
        ]});
 | 
			
		||||
 | 
			
		||||
        menuOptions.push({id:"menu-item-arrange-menu", label:RED._("menu.label.arrange"), options: [
 | 
			
		||||
            {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-view-tools-align-left", label:RED._("menu.label.alignLeft"), disabled: true, onselect: "core:align-selection-to-left"},
 | 
			
		||||
            {id: "menu-item-view-tools-align-center", label:RED._("menu.label.alignCenter"), disabled: true, onselect: "core:align-selection-to-center"},
 | 
			
		||||
            {id: "menu-item-view-tools-align-right", label:RED._("menu.label.alignRight"), disabled: true, onselect: "core:align-selection-to-right"},
 | 
			
		||||
@@ -679,12 +657,7 @@ var RED = (function() {
 | 
			
		||||
            {id: "menu-item-view-tools-align-bottom", label:RED._("menu.label.alignBottom"), disabled: true, onselect: "core:align-selection-to-bottom"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-view-tools-distribute-horizontally", label:RED._("menu.label.distributeHorizontally"), disabled: true, onselect: "core:distribute-selection-horizontally"},
 | 
			
		||||
            {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"},
 | 
			
		||||
            null,
 | 
			
		||||
            {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"},
 | 
			
		||||
            {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"}
 | 
			
		||||
            {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"}
 | 
			
		||||
        ]});
 | 
			
		||||
 | 
			
		||||
        menuOptions.push(null);
 | 
			
		||||
@@ -777,7 +750,6 @@ var RED = (function() {
 | 
			
		||||
        RED.deploy.init(RED.settings.theme("deployButton",null));
 | 
			
		||||
 | 
			
		||||
        RED.keyboard.init(buildMainMenu);
 | 
			
		||||
        RED.envVar.init();
 | 
			
		||||
 | 
			
		||||
        RED.nodes.init();
 | 
			
		||||
        RED.runtime.init()
 | 
			
		||||
 
 | 
			
		||||
@@ -423,10 +423,11 @@ RED.clipboard = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showImportNodes(library = 'clipboard') {
 | 
			
		||||
    function showImportNodes(mode) {
 | 
			
		||||
        if (disabled) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        mode = mode || "clipboard";
 | 
			
		||||
 | 
			
		||||
        dialogContainer.empty();
 | 
			
		||||
        dialogContainer.append($(importNodesDialog));
 | 
			
		||||
@@ -503,7 +504,7 @@ RED.clipboard = (function() {
 | 
			
		||||
        $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
 | 
			
		||||
        $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
 | 
			
		||||
 | 
			
		||||
        if (RED.workspaces.active() === 0 || RED.workspaces.isActiveLocked()) {
 | 
			
		||||
        if (RED.workspaces.active() === 0) {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected");
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -532,8 +533,8 @@ RED.clipboard = (function() {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-import-file-upload").trigger("click");
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+library);
 | 
			
		||||
        if (library === 'clipboard') {
 | 
			
		||||
        tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+mode);
 | 
			
		||||
        if (mode === 'clipboard') {
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                $("#red-ui-clipboard-dialog-import-text").trigger("focus");
 | 
			
		||||
            },100)
 | 
			
		||||
@@ -557,16 +558,13 @@ RED.clipboard = (function() {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the export dialog
 | 
			
		||||
     * @params library which export destination to show
 | 
			
		||||
     * @params mode whether to default to 'auto' (default) or 'flow'
 | 
			
		||||
     **/
 | 
			
		||||
    function showExportNodes(library = 'clipboard', mode = 'auto' ) {
 | 
			
		||||
    function showExportNodes(mode) {
 | 
			
		||||
        if (disabled) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mode = mode || "clipboard";
 | 
			
		||||
 | 
			
		||||
        dialogContainer.empty();
 | 
			
		||||
        dialogContainer.append($(exportNodesDialog));
 | 
			
		||||
 | 
			
		||||
@@ -656,12 +654,7 @@ RED.clipboard = (function() {
 | 
			
		||||
        $("#red-ui-clipboard-dialog-tab-library-name").val("flows.json").select();
 | 
			
		||||
 | 
			
		||||
        dialogContainer.i18n();
 | 
			
		||||
        
 | 
			
		||||
        var format = RED.settings.flowFilePretty ? "red-ui-clipboard-dialog-export-fmt-full" : "red-ui-clipboard-dialog-export-fmt-mini";
 | 
			
		||||
        const userFormat = RED.settings.get("editor.dialog.export.pretty")
 | 
			
		||||
        if (userFormat === false || userFormat === true) {
 | 
			
		||||
            format = userFormat ? "red-ui-clipboard-dialog-export-fmt-full" : "red-ui-clipboard-dialog-export-fmt-mini";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $("#red-ui-clipboard-dialog-export-fmt-group > a").on("click", function(evt) {
 | 
			
		||||
            evt.preventDefault();
 | 
			
		||||
@@ -677,8 +670,7 @@ RED.clipboard = (function() {
 | 
			
		||||
                var nodes = JSON.parse(flow);
 | 
			
		||||
 | 
			
		||||
                format = $(this).attr('id');
 | 
			
		||||
                const pretty = format === "red-ui-clipboard-dialog-export-fmt-full";
 | 
			
		||||
                if (pretty) {
 | 
			
		||||
                if (format === 'red-ui-clipboard-dialog-export-fmt-full') {
 | 
			
		||||
                    flow = JSON.stringify(nodes,null,4);
 | 
			
		||||
                } else {
 | 
			
		||||
                    flow = JSON.stringify(nodes);
 | 
			
		||||
@@ -687,7 +679,6 @@ RED.clipboard = (function() {
 | 
			
		||||
                setTimeout(function() { $("#red-ui-clipboard-dialog-export-text").scrollTop(0); },50);
 | 
			
		||||
 | 
			
		||||
                $("#red-ui-clipboard-dialog-export-text").trigger("focus");
 | 
			
		||||
                RED.settings.set("editor.dialog.export.pretty", pretty)
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -775,15 +766,12 @@ RED.clipboard = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (mode === 'flow' && !$("#red-ui-clipboard-dialog-export-rng-flow").hasClass('disabled')) {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-rng-flow").trigger("click");
 | 
			
		||||
        }
 | 
			
		||||
        if (format === "red-ui-clipboard-dialog-export-fmt-full") {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-fmt-full").trigger("click");
 | 
			
		||||
        } else {
 | 
			
		||||
            $("#red-ui-clipboard-dialog-export-fmt-mini").trigger("click");
 | 
			
		||||
        }
 | 
			
		||||
        tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+library);
 | 
			
		||||
        tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+mode);
 | 
			
		||||
 | 
			
		||||
        var dialogHeight = 400;
 | 
			
		||||
        var winHeight = $(window).height();
 | 
			
		||||
@@ -1278,17 +1266,15 @@ RED.clipboard = (function() {
 | 
			
		||||
            RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget);
 | 
			
		||||
 | 
			
		||||
            $('#red-ui-workspace-chart').on("dragenter",function(event) {
 | 
			
		||||
                if (!RED.workspaces.isActiveLocked() && (
 | 
			
		||||
                    $.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
 | 
			
		||||
                     $.inArray("Files",event.originalEvent.dataTransfer.types) != -1)) {
 | 
			
		||||
                if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
 | 
			
		||||
                     $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                    $("#red-ui-drop-target").css({display:'table'}).focus();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $('#red-ui-drop-target').on("dragover",function(event) {
 | 
			
		||||
                if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
 | 
			
		||||
                     $.inArray("Files",event.originalEvent.dataTransfer.types) != -1 ||
 | 
			
		||||
                        RED.workspaces.isActiveLocked()) {
 | 
			
		||||
                     $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
@@ -1296,29 +1282,27 @@ RED.clipboard = (function() {
 | 
			
		||||
                hideDropTarget();
 | 
			
		||||
            })
 | 
			
		||||
            .on("drop",function(event) {
 | 
			
		||||
                if (!RED.workspaces.isActiveLocked()) {
 | 
			
		||||
                    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);
 | 
			
		||||
                            }
 | 
			
		||||
                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.
 | 
			
		||||
                    }
 | 
			
		||||
                } catch(err) {
 | 
			
		||||
                    // Ensure any errors throw above doesn't stop the drop target from
 | 
			
		||||
                    // being hidden.
 | 
			
		||||
                }
 | 
			
		||||
                hideDropTarget();
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
 
 | 
			
		||||
@@ -94,8 +94,8 @@ RED.menu = (function() {
 | 
			
		||||
 | 
			
		||||
            var link = $(linkContent).appendTo(item);
 | 
			
		||||
            opt.link = link;
 | 
			
		||||
            if (typeof opt.onselect === 'string' || opt.shortcut) {
 | 
			
		||||
                var shortcut = opt.shortcut || RED.keyboard.getShortcut(opt.onselect);
 | 
			
		||||
            if (typeof opt.onselect === 'string') {
 | 
			
		||||
                var shortcut = RED.keyboard.getShortcut(opt.onselect);
 | 
			
		||||
                if (shortcut && shortcut.key) {
 | 
			
		||||
                    opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(link.find(".red-ui-menu-label"));
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -141,29 +141,7 @@ RED.tabs = (function() {
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (options.contextmenu) {
 | 
			
		||||
            wrapper.on('contextmenu', function(evt) {
 | 
			
		||||
                let clickedTab
 | 
			
		||||
                let target = evt.target
 | 
			
		||||
                while(target.nodeName !== 'A' && target.nodeName !== 'UL' && target.nodeName !== 'BODY') {
 | 
			
		||||
                    target = target.parentNode
 | 
			
		||||
                }
 | 
			
		||||
                if (target.nodeName === 'A') {
 | 
			
		||||
                    const href = target.getAttribute('href')
 | 
			
		||||
                    if (href) {
 | 
			
		||||
                        clickedTab = tabs[href.slice(1)]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                evt.preventDefault()
 | 
			
		||||
                evt.stopPropagation()
 | 
			
		||||
                RED.contextMenu.show({
 | 
			
		||||
                    x:evt.clientX-5,
 | 
			
		||||
                    y:evt.clientY-5,
 | 
			
		||||
                    options: options.contextmenu(clickedTab)
 | 
			
		||||
                })
 | 
			
		||||
                return false
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var scrollLeft;
 | 
			
		||||
        var scrollRight;
 | 
			
		||||
@@ -829,19 +807,19 @@ RED.tabs = (function() {
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                        removeTab(tab.id);
 | 
			
		||||
                    });
 | 
			
		||||
                    RED.popover.tooltip(closeLink,RED._("workspace.closeFlow"));
 | 
			
		||||
                    RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
 | 
			
		||||
                }
 | 
			
		||||
                if (tab.hideable) {
 | 
			
		||||
                    li.addClass("red-ui-tabs-closeable")
 | 
			
		||||
                    var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
 | 
			
		||||
                    closeLink.append('<i class="fa fa-eye" />');
 | 
			
		||||
                    closeLink.append('<i class="fa fa-eye-slash" />');
 | 
			
		||||
                    closeLink.on("click",function(event) {
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                        hideTab(tab.id);
 | 
			
		||||
                    });
 | 
			
		||||
                    RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
 | 
			
		||||
                }
 | 
			
		||||
                // if (tab.hideable) {
 | 
			
		||||
                //     li.addClass("red-ui-tabs-closeable")
 | 
			
		||||
                //     var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
 | 
			
		||||
                //     closeLink.append('<i class="fa fa-eye" />');
 | 
			
		||||
                //     closeLink.append('<i class="fa fa-eye-slash" />');
 | 
			
		||||
                //     closeLink.on("click",function(event) {
 | 
			
		||||
                //         event.preventDefault();
 | 
			
		||||
                //         hideTab(tab.id);
 | 
			
		||||
                //     });
 | 
			
		||||
                //     RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
 | 
			
		||||
                // }
 | 
			
		||||
 | 
			
		||||
                var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
 | 
			
		||||
                if (options.onselect) {
 | 
			
		||||
@@ -960,9 +938,6 @@ RED.tabs = (function() {
 | 
			
		||||
            activeIndex: function() {
 | 
			
		||||
                return ul.find("li.active").index()
 | 
			
		||||
            },
 | 
			
		||||
            getTabIndex: function (id) {
 | 
			
		||||
                return ul.find("a[href='#"+id+"']").parent().index()
 | 
			
		||||
            },
 | 
			
		||||
            contains: function(id) {
 | 
			
		||||
                return ul.find("a[href='#"+id+"']").length > 0;
 | 
			
		||||
            },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,21 @@
 | 
			
		||||
RED.contextMenu = (function () {
 | 
			
		||||
 | 
			
		||||
    let menu;
 | 
			
		||||
    function createMenu() {
 | 
			
		||||
        // menu = RED.popover.menu({
 | 
			
		||||
        //     options: [
 | 
			
		||||
        //         {
 | 
			
		||||
        //             label: 'delete selection',
 | 
			
		||||
        //             onselect: function() {
 | 
			
		||||
        //                 RED.actions.invoke('core:delete-selection')
 | 
			
		||||
        //                 RED.view.focus()
 | 
			
		||||
        //             }
 | 
			
		||||
        //         },
 | 
			
		||||
        //         { label: 'world' }
 | 
			
		||||
        //     ],
 | 
			
		||||
        //     width: 200,
 | 
			
		||||
        // })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function disposeMenu() {
 | 
			
		||||
        $(document).off("mousedown.red-ui-workspace-context-menu");
 | 
			
		||||
@@ -13,164 +28,114 @@ RED.contextMenu = (function () {
 | 
			
		||||
        if (menu) {
 | 
			
		||||
            menu.remove()
 | 
			
		||||
        }
 | 
			
		||||
        let menuItems = []
 | 
			
		||||
        if (options.options) {
 | 
			
		||||
            menuItems = options.options
 | 
			
		||||
        } else if (options.type === 'workspace') {
 | 
			
		||||
            const selection = RED.view.selection()
 | 
			
		||||
            const noSelection = !selection || Object.keys(selection).length === 0
 | 
			
		||||
            const hasSelection = (selection.nodes && selection.nodes.length > 0);
 | 
			
		||||
            const hasMultipleSelection = hasSelection && selection.nodes.length > 1;
 | 
			
		||||
            const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || [];
 | 
			
		||||
            const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || [];
 | 
			
		||||
            const hasLinks = wireLinks.length > 0;
 | 
			
		||||
            const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1
 | 
			
		||||
            const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
 | 
			
		||||
            const canDelete = hasSelection || hasLinks
 | 
			
		||||
            const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
 | 
			
		||||
            const canEdit = !RED.workspaces.isActiveLocked()
 | 
			
		||||
            const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
 | 
			
		||||
            const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
 | 
			
		||||
            const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
 | 
			
		||||
            const offset = $("#red-ui-workspace-chart").offset()
 | 
			
		||||
 | 
			
		||||
            let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
 | 
			
		||||
            let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
 | 
			
		||||
        const selection = RED.view.selection()
 | 
			
		||||
        const noSelection = !selection || Object.keys(selection).length === 0
 | 
			
		||||
        const hasSelection = (selection.nodes && selection.nodes.length > 0);
 | 
			
		||||
        const hasMultipleSelection = hasSelection && selection.nodes.length > 1;
 | 
			
		||||
        const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || [];
 | 
			
		||||
        const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || [];
 | 
			
		||||
        const hasLinks = wireLinks.length > 0;
 | 
			
		||||
        const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1
 | 
			
		||||
        const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
 | 
			
		||||
        const canDelete = hasSelection || hasLinks
 | 
			
		||||
        const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
 | 
			
		||||
 | 
			
		||||
            if (RED.view.snapGrid) {
 | 
			
		||||
                const gridSize = RED.view.gridSize()
 | 
			
		||||
                addX = gridSize * Math.floor(addX / gridSize)
 | 
			
		||||
                addY = gridSize * Math.floor(addY / gridSize)
 | 
			
		||||
            }
 | 
			
		||||
        const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
 | 
			
		||||
        const offset = $("#red-ui-workspace-chart").offset()
 | 
			
		||||
 | 
			
		||||
            menuItems.push(
 | 
			
		||||
                { onselect: 'core:show-action-list', onpostselect: function () { } }
 | 
			
		||||
            )
 | 
			
		||||
        // addX/addY must be the position in the workspace accounting for both scroll and scale
 | 
			
		||||
        // The +5 is because we display the contextMenu -5,-5 to actual click position
 | 
			
		||||
        let addX = (options.x + 5 - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / RED.view.scale()
 | 
			
		||||
        let addY = (options.y + 5 - offset.top + $("#red-ui-workspace-chart").scrollTop()) / RED.view.scale()
 | 
			
		||||
 | 
			
		||||
            const insertOptions = []
 | 
			
		||||
            menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
 | 
			
		||||
            insertOptions.push(
 | 
			
		||||
                {
 | 
			
		||||
                    label: RED._("contextMenu.node"),
 | 
			
		||||
                    onselect: function () {
 | 
			
		||||
                        RED.view.showQuickAddDialog({
 | 
			
		||||
                            position: [addX, addY],
 | 
			
		||||
                            touchTrigger: true,
 | 
			
		||||
                            splice: isSingleLink ? selection.links[0] : undefined,
 | 
			
		||||
                            // spliceMultiple: isMultipleLinks
 | 
			
		||||
                        })
 | 
			
		||||
                    },
 | 
			
		||||
                    disabled: !canEdit
 | 
			
		||||
                },
 | 
			
		||||
                (hasLinks) ? { // has least 1 wire selected
 | 
			
		||||
                    label: RED._("contextMenu.junction"),
 | 
			
		||||
                    onselect: 'core:split-wires-with-junctions',
 | 
			
		||||
                    disabled: !canEdit || !hasLinks
 | 
			
		||||
                } : {
 | 
			
		||||
                    label: RED._("contextMenu.junction"),
 | 
			
		||||
                    onselect: function () {
 | 
			
		||||
                        const nn = {
 | 
			
		||||
                            _def: { defaults: {} },
 | 
			
		||||
                            type: 'junction',
 | 
			
		||||
                            z: RED.workspaces.active(),
 | 
			
		||||
                            id: RED.nodes.id(),
 | 
			
		||||
                            x: addX,
 | 
			
		||||
                            y: addY,
 | 
			
		||||
                            w: 0, h: 0,
 | 
			
		||||
                            outputs: 1,
 | 
			
		||||
                            inputs: 1,
 | 
			
		||||
                            dirty: true
 | 
			
		||||
        const menuItems = [
 | 
			
		||||
            { onselect: 'core:show-action-list', onpostselect: function () { } },
 | 
			
		||||
            {
 | 
			
		||||
                label: RED._("contextMenu.insert"),
 | 
			
		||||
                options: [
 | 
			
		||||
                    {
 | 
			
		||||
                        label: RED._("contextMenu.node"),
 | 
			
		||||
                        onselect: function () {
 | 
			
		||||
                            RED.view.showQuickAddDialog({
 | 
			
		||||
                                position: [addX, addY],
 | 
			
		||||
                                touchTrigger: true,
 | 
			
		||||
                                splice: isSingleLink ? selection.links[0] : undefined,
 | 
			
		||||
                                // spliceMultiple: isMultipleLinks
 | 
			
		||||
                            })
 | 
			
		||||
                        },
 | 
			
		||||
                        onpostselect: function() {
 | 
			
		||||
                            // ensure quick add dialog search input has focus
 | 
			
		||||
                            $('#red-ui-type-search-input').trigger('focus')
 | 
			
		||||
                        }
 | 
			
		||||
                        const historyEvent = {
 | 
			
		||||
                            dirty: RED.nodes.dirty(),
 | 
			
		||||
                            t: 'add',
 | 
			
		||||
                            junctions: [nn]
 | 
			
		||||
                        }
 | 
			
		||||
                        RED.nodes.addJunction(nn);
 | 
			
		||||
                        RED.history.push(historyEvent);
 | 
			
		||||
                        RED.nodes.dirty(true);
 | 
			
		||||
                        RED.view.select({nodes: [nn] });
 | 
			
		||||
                        RED.view.redraw(true)
 | 
			
		||||
                    },
 | 
			
		||||
                    disabled: !canEdit
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    label: RED._("contextMenu.linkNodes"),
 | 
			
		||||
                    onselect: 'core:split-wire-with-link-nodes',
 | 
			
		||||
                    disabled: !canEdit || !hasLinks
 | 
			
		||||
                },
 | 
			
		||||
                null,
 | 
			
		||||
                { onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
 | 
			
		||||
                { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
 | 
			
		||||
            )
 | 
			
		||||
            if (hasSelection && canEdit) {
 | 
			
		||||
                const nodeOptions = []
 | 
			
		||||
                if (!hasMultipleSelection && !isGroup) {
 | 
			
		||||
                    nodeOptions.push(
 | 
			
		||||
                        { onselect: 'core:show-node-help' },
 | 
			
		||||
                        null
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                nodeOptions.push(
 | 
			
		||||
                    { onselect: 'core:enable-selected-nodes' },
 | 
			
		||||
                    { onselect: 'core:disable-selected-nodes' },
 | 
			
		||||
                    null,
 | 
			
		||||
                    { onselect: 'core:show-selected-node-labels' },
 | 
			
		||||
                    { onselect: 'core:hide-selected-node-labels' }
 | 
			
		||||
                )
 | 
			
		||||
                menuItems.push({
 | 
			
		||||
                    label: RED._('sidebar.info.node'),
 | 
			
		||||
                    options: nodeOptions
 | 
			
		||||
                })
 | 
			
		||||
                menuItems.push({
 | 
			
		||||
                    label: RED._('sidebar.info.group'),
 | 
			
		||||
                    options: [
 | 
			
		||||
                        { onselect: 'core:group-selection' },
 | 
			
		||||
                        { onselect: 'core:ungroup-selection', disabled: !hasGroup },
 | 
			
		||||
                        null,
 | 
			
		||||
                        { onselect: 'core:copy-group-style', disabled: !hasGroup },
 | 
			
		||||
                        { onselect: 'core:paste-group-style', disabled: !hasGroup}
 | 
			
		||||
                    ]
 | 
			
		||||
                })
 | 
			
		||||
                if (canRemoveFromGroup) {
 | 
			
		||||
                    menuItems[menuItems.length - 1].options.push(
 | 
			
		||||
                        null,
 | 
			
		||||
                        { onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (canEdit && hasMultipleSelection) {
 | 
			
		||||
                menuItems.push({
 | 
			
		||||
                    label: RED._('menu.label.arrange'),
 | 
			
		||||
                    options: [
 | 
			
		||||
                        { label:RED._("menu.label.alignLeft"), onselect: "core:align-selection-to-left"},
 | 
			
		||||
                        { label:RED._("menu.label.alignCenter"), onselect: "core:align-selection-to-center"},
 | 
			
		||||
                        { label:RED._("menu.label.alignRight"), onselect: "core:align-selection-to-right"},
 | 
			
		||||
                        null,
 | 
			
		||||
                        { label:RED._("menu.label.alignTop"), onselect: "core:align-selection-to-top"},
 | 
			
		||||
                        { label:RED._("menu.label.alignMiddle"), onselect: "core:align-selection-to-middle"},
 | 
			
		||||
                        { label:RED._("menu.label.alignBottom"), onselect: "core:align-selection-to-bottom"},
 | 
			
		||||
                        null,
 | 
			
		||||
                        { label:RED._("menu.label.distributeHorizontally"), onselect: "core:distribute-selection-horizontally"},
 | 
			
		||||
                        { label:RED._("menu.label.distributeVertically"), onselect: "core:distribute-selection-vertically"}
 | 
			
		||||
                    ]
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
                    (hasLinks) ? { // has least 1 wire selected
 | 
			
		||||
                        label: RED._("contextMenu.junction"),
 | 
			
		||||
                        onselect: 'core:split-wires-with-junctions',
 | 
			
		||||
                        disabled: !hasLinks
 | 
			
		||||
                    } : {
 | 
			
		||||
                        label: RED._("contextMenu.junction"),
 | 
			
		||||
                        onselect: function () {
 | 
			
		||||
                            const nn = {
 | 
			
		||||
                                _def: { defaults: {} },
 | 
			
		||||
                                type: 'junction',
 | 
			
		||||
                                z: RED.workspaces.active(),
 | 
			
		||||
                                id: RED.nodes.id(),
 | 
			
		||||
                                x: addX,
 | 
			
		||||
                                y: addY,
 | 
			
		||||
                                w: 0, h: 0,
 | 
			
		||||
                                outputs: 1,
 | 
			
		||||
                                inputs: 1,
 | 
			
		||||
                                dirty: true
 | 
			
		||||
                            }
 | 
			
		||||
                            const historyEvent = {
 | 
			
		||||
                                dirty: RED.nodes.dirty(),
 | 
			
		||||
                                t: 'add',
 | 
			
		||||
                                junctions: [nn]
 | 
			
		||||
                            }
 | 
			
		||||
                            RED.nodes.addJunction(nn);
 | 
			
		||||
                            RED.history.push(historyEvent);
 | 
			
		||||
                            RED.nodes.dirty(true);
 | 
			
		||||
                            RED.view.select({nodes: [nn] });
 | 
			
		||||
                            RED.view.redraw(true)
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        label: RED._("contextMenu.linkNodes"),
 | 
			
		||||
                        onselect: 'core:split-wire-with-link-nodes',
 | 
			
		||||
                        disabled: !hasLinks
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        menuItems.push(
 | 
			
		||||
            null,
 | 
			
		||||
            { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
 | 
			
		||||
            { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
 | 
			
		||||
            null,
 | 
			
		||||
            { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection },
 | 
			
		||||
            { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
 | 
			
		||||
            { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() },
 | 
			
		||||
            { onselect: 'core:delete-selection', disabled: !canDelete },
 | 
			
		||||
            { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
 | 
			
		||||
            { onselect: 'core:select-all-nodes' }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (hasSelection) {
 | 
			
		||||
            menuItems.push(
 | 
			
		||||
                null,
 | 
			
		||||
                { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
 | 
			
		||||
                { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
 | 
			
		||||
                null,
 | 
			
		||||
                { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
 | 
			
		||||
                { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
 | 
			
		||||
                { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
 | 
			
		||||
                { onselect: 'core:delete-selection', disabled: !canEdit || !canDelete },
 | 
			
		||||
                { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
 | 
			
		||||
                { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
 | 
			
		||||
                { onselect: 'core:select-all-nodes' },
 | 
			
		||||
                isGroup ?
 | 
			
		||||
                    { onselect: 'core:ungroup-selection', disabled: !isGroup }
 | 
			
		||||
                    : { onselect: 'core:group-selection', disabled: !hasSelection }
 | 
			
		||||
            )
 | 
			
		||||
            if (canRemoveFromGroup) {
 | 
			
		||||
                menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var direction = "right";
 | 
			
		||||
 
 | 
			
		||||
@@ -558,11 +558,6 @@ RED.deploy = (function() {
 | 
			
		||||
                RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
 | 
			
		||||
            }
 | 
			
		||||
            RED.nodes.eachNode(function (node) {
 | 
			
		||||
                const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null);
 | 
			
		||||
                const isLocked = flow ? flow.locked : false;
 | 
			
		||||
                if (flow && isLocked) {
 | 
			
		||||
                    flow.locked = false;
 | 
			
		||||
                }
 | 
			
		||||
                if (node.changed) {
 | 
			
		||||
                    node.dirty = true;
 | 
			
		||||
                    node.changed = false;
 | 
			
		||||
@@ -574,9 +569,6 @@ RED.deploy = (function() {
 | 
			
		||||
                if (node.credentials) {
 | 
			
		||||
                    delete node.credentials;
 | 
			
		||||
                }
 | 
			
		||||
                if (flow && isLocked) {
 | 
			
		||||
                    flow.locked = isLocked;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            RED.nodes.eachConfig(function (confNode) {
 | 
			
		||||
                confNode.changed = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -1853,15 +1853,11 @@ RED.editor = (function() {
 | 
			
		||||
                            workspace.disabled = disabled;
 | 
			
		||||
 | 
			
		||||
                            $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
 | 
			
		||||
                            if (workspace.id === RED.workspaces.active()) {
 | 
			
		||||
                                $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var locked = $("#node-input-locked").prop("checked");
 | 
			
		||||
                        if (workspace.locked !== locked) {
 | 
			
		||||
                            editState.changes.locked = workspace.locked;
 | 
			
		||||
                            editState.changed = true;
 | 
			
		||||
                            workspace.locked = locked;
 | 
			
		||||
                            $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!workspace.locked);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (editState.changed) {
 | 
			
		||||
                            var historyEvent = {
 | 
			
		||||
                                t: "edit",
 | 
			
		||||
@@ -1902,7 +1898,6 @@ RED.editor = (function() {
 | 
			
		||||
                var trayBody = tray.find('.red-ui-tray-body');
 | 
			
		||||
                trayBody.parent().css('overflow','hidden');
 | 
			
		||||
                var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
 | 
			
		||||
                var trayFooterRight = $('<div class="red-ui-tray-footer-right"></div>').appendTo(trayFooter)
 | 
			
		||||
 | 
			
		||||
                var nodeEditPanes = [
 | 
			
		||||
                    'editor-tab-flow-properties',
 | 
			
		||||
@@ -1917,18 +1912,6 @@ RED.editor = (function() {
 | 
			
		||||
                    disabledIcon: "fa-ban",
 | 
			
		||||
                    invertState: true
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                if (!workspace.hasOwnProperty("locked")) {
 | 
			
		||||
                    workspace.locked = false;
 | 
			
		||||
                }
 | 
			
		||||
                $('<input id="node-input-locked" type="checkbox">').prop("checked",workspace.locked).appendTo(trayFooterRight).toggleButton({
 | 
			
		||||
                    enabledLabel: 'Unlocked',
 | 
			
		||||
                    enabledIcon: "fa-unlock-alt",
 | 
			
		||||
                    disabledLabel: 'Locked',
 | 
			
		||||
                    disabledIcon: "fa-lock",
 | 
			
		||||
                    invertState: true
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                prepareEditDialog(trayBody, nodeEditPanes, workspace, {}, "node-input", defaultTab, function(_activeEditPanes) {
 | 
			
		||||
                    activeEditPanes = _activeEditPanes;
 | 
			
		||||
                    trayBody.i18n();
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,6 @@
 | 
			
		||||
            selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
 | 
			
		||||
            initialised = selectedCodeEditor.init();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $('<div id="red-ui-image-drop-target"><div data-i18n="[append]workspace.dropImageHere"><i class="fa fa-download"></i><br></div></div>').appendTo('#red-ui-editor');
 | 
			
		||||
        $("#red-ui-image-drop-target").hide();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function create(options) {
 | 
			
		||||
@@ -67,7 +64,6 @@
 | 
			
		||||
            options = {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var editor = null;
 | 
			
		||||
        if (this.editor.type === MONACO) {
 | 
			
		||||
            // compatibility (see above note)
 | 
			
		||||
            if (!options.element && !options.id) {
 | 
			
		||||
@@ -78,14 +74,10 @@
 | 
			
		||||
                console.warn("createEditor() options.element or options.id is not valid", options);
 | 
			
		||||
                $("#dialog-form").append('<div id="' + options.id + '" style="display: none;" />');
 | 
			
		||||
            }
 | 
			
		||||
            editor = this.editor.create(options);
 | 
			
		||||
            return this.editor.create(options);
 | 
			
		||||
        } else {
 | 
			
		||||
            editor = this.editor.create(options);//fallback to ACE
 | 
			
		||||
            return this.editor.create(options);//fallback to ACE
 | 
			
		||||
        }
 | 
			
		||||
        if (options.mode === "ace/mode/markdown") {
 | 
			
		||||
            RED.editor.customEditTypes['_markdown'].postInit(editor, options);
 | 
			
		||||
        }
 | 
			
		||||
        return editor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ RED.editor.envVarList = (function() {
 | 
			
		||||
 | 
			
		||||
    var currentLocale = 'en-US';
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
 | 
			
		||||
    var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred'];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create env var edit interface
 | 
			
		||||
 
 | 
			
		||||
@@ -14,61 +14,6 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 **/
 | 
			
		||||
(function() {
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts dropped image file to date URL
 | 
			
		||||
     */
 | 
			
		||||
    function file2base64Image(file, cb) {
 | 
			
		||||
        var reader = new FileReader();
 | 
			
		||||
        reader.onload = (function (fd) {
 | 
			
		||||
            return function (e) {
 | 
			
		||||
                cb(e.target.result);
 | 
			
		||||
            };
 | 
			
		||||
        })(file);
 | 
			
		||||
        reader.readAsDataURL(file);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var initialized = false;
 | 
			
		||||
    var currentEditor = null;
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize handler for image file drag events
 | 
			
		||||
     */
 | 
			
		||||
    function initImageDrag(elem, editor) {
 | 
			
		||||
        $(elem).on("dragenter", function (ev) {
 | 
			
		||||
            ev.preventDefault();
 | 
			
		||||
            $("#red-ui-image-drop-target").css({display:'table'}).focus();
 | 
			
		||||
            currentEditor = editor;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (!initialized) {
 | 
			
		||||
            initialized = true;
 | 
			
		||||
            $("#red-ui-image-drop-target").on("dragover", function (ev) {
 | 
			
		||||
                ev.preventDefault();
 | 
			
		||||
            }).on("dragleave", function (ev) {
 | 
			
		||||
                $("#red-ui-image-drop-target").hide();
 | 
			
		||||
            }).on("drop", function (ev) {
 | 
			
		||||
                ev.preventDefault();
 | 
			
		||||
                if ($.inArray("Files",ev.originalEvent.dataTransfer.types) != -1) {
 | 
			
		||||
                    var files = ev.originalEvent.dataTransfer.files;
 | 
			
		||||
                    if (files.length === 1) {
 | 
			
		||||
                        var file = files[0];
 | 
			
		||||
                        var name = file.name.toLowerCase();
 | 
			
		||||
 | 
			
		||||
                        if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
 | 
			
		||||
                            file2base64Image(file, function (image) {
 | 
			
		||||
                                var session = currentEditor.getSession();
 | 
			
		||||
                                var img = `<img src="${image}"/>\n`;
 | 
			
		||||
                                var pos = session.getCursorPosition();
 | 
			
		||||
                                session.insert(pos, img);
 | 
			
		||||
                                $("#red-ui-image-drop-target").hide();
 | 
			
		||||
                            });
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                $("#red-ui-image-drop-target").hide();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var toolbarTemplate = '<div style="margin-bottom: 5px">'+
 | 
			
		||||
        '<span class="button-group">'+
 | 
			
		||||
@@ -169,7 +114,6 @@
 | 
			
		||||
                            var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
 | 
			
		||||
                            $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
 | 
			
		||||
                            $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
 | 
			
		||||
                            mermaid.init();
 | 
			
		||||
                        },200);
 | 
			
		||||
                    })
 | 
			
		||||
                    if (options.header) {
 | 
			
		||||
@@ -178,7 +122,6 @@
 | 
			
		||||
 | 
			
		||||
                    if (value) {
 | 
			
		||||
                        $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
 | 
			
		||||
                        mermaid.init();
 | 
			
		||||
                    }
 | 
			
		||||
                    panels = RED.panels.create({
 | 
			
		||||
                        id:"red-ui-editor-type-markdown-panels",
 | 
			
		||||
@@ -205,14 +148,10 @@
 | 
			
		||||
                    });
 | 
			
		||||
                    RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview"));
 | 
			
		||||
 | 
			
		||||
                    if(!expressionEditor._initState) {
 | 
			
		||||
                        if (options.cursor) {
 | 
			
		||||
                            expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            expressionEditor.gotoLine(0, 0, false);
 | 
			
		||||
                        }
 | 
			
		||||
                    if (options.cursor && !expressionEditor._initState) {
 | 
			
		||||
                        expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    dialogForm.i18n();
 | 
			
		||||
                },
 | 
			
		||||
                close: function() {
 | 
			
		||||
@@ -276,11 +215,7 @@
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            return toolbar;
 | 
			
		||||
        },
 | 
			
		||||
        postInit: function (editor, options) {
 | 
			
		||||
            var elem = $("#"+options.id);
 | 
			
		||||
            initImageDrag(elem, editor);
 | 
			
		||||
         }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    RED.editor.registerTypeEditor("_markdown", definition);
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,8 @@
 | 
			
		||||
                    node.info = info;
 | 
			
		||||
                }
 | 
			
		||||
                $("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled);
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,175 +0,0 @@
 | 
			
		||||
RED.envVar = (function() {
 | 
			
		||||
    function saveEnvList(list) {
 | 
			
		||||
        const items = list.editableList("items")
 | 
			
		||||
        const new_env = [];
 | 
			
		||||
        items.each(function (i,el) {
 | 
			
		||||
            var data = el.data('data');
 | 
			
		||||
            var item;
 | 
			
		||||
            if (data.nameField && data.valueField) {
 | 
			
		||||
                item = {
 | 
			
		||||
                    name: data.nameField.val(),
 | 
			
		||||
                    value: data.valueField.typedInput("value"),
 | 
			
		||||
                    type: data.valueField.typedInput("type")
 | 
			
		||||
                };
 | 
			
		||||
                new_env.push(item);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return new_env;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getGlobalConf(create) {
 | 
			
		||||
        var gconf = null;
 | 
			
		||||
        RED.nodes.eachConfig(function (conf) {
 | 
			
		||||
            if (conf.type === "global-config") {
 | 
			
		||||
                gconf = conf;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if ((gconf === null) && create) {
 | 
			
		||||
            var cred = {
 | 
			
		||||
                _ : {},
 | 
			
		||||
                map: {}
 | 
			
		||||
            };
 | 
			
		||||
            gconf = {
 | 
			
		||||
                id: RED.nodes.id(),
 | 
			
		||||
                type: "global-config",
 | 
			
		||||
                env: [],
 | 
			
		||||
                name: "global-config",
 | 
			
		||||
                label: "",
 | 
			
		||||
                hasUsers: false,
 | 
			
		||||
                users: [],
 | 
			
		||||
                credentials: cred,
 | 
			
		||||
                _def: RED.nodes.getType("global-config"),
 | 
			
		||||
            };
 | 
			
		||||
            RED.nodes.add(gconf);
 | 
			
		||||
        }
 | 
			
		||||
        return gconf;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function applyChanges(list) {
 | 
			
		||||
        var gconf = getGlobalConf(false);
 | 
			
		||||
        var new_env = [];
 | 
			
		||||
        var items = list.editableList('items');
 | 
			
		||||
        var credentials = gconf ? gconf.credentials : null;
 | 
			
		||||
 | 
			
		||||
        if (!credentials) {
 | 
			
		||||
            credentials = {
 | 
			
		||||
                _ : {},
 | 
			
		||||
                map: {}
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        items.each(function (i,el) {
 | 
			
		||||
            var data = el.data('data');
 | 
			
		||||
            if (data.nameField && data.valueField) {
 | 
			
		||||
                var item = {
 | 
			
		||||
                    name: data.nameField.val(),
 | 
			
		||||
                    value: data.valueField.typedInput("value"),
 | 
			
		||||
                    type: data.valueField.typedInput("type")
 | 
			
		||||
                };
 | 
			
		||||
                if (item.name.trim() !== "") {
 | 
			
		||||
                    new_env.push(item);
 | 
			
		||||
                    if ((item.type === "cred") && (item.value !== "__PWRD__")) {
 | 
			
		||||
                        credentials.map[item.name] = item.value;
 | 
			
		||||
                        credentials.map["has_"+item.name] = (item.value !== "");
 | 
			
		||||
                        item.value = "__PWRD__";
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if (gconf === null) {
 | 
			
		||||
            gconf = getGlobalConf(true);
 | 
			
		||||
        }
 | 
			
		||||
        if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
 | 
			
		||||
            (JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
 | 
			
		||||
            gconf.env = new_env;
 | 
			
		||||
            gconf.credentials = credentials;
 | 
			
		||||
            RED.nodes.dirty(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getSettingsPane() {
 | 
			
		||||
        var gconf = getGlobalConf(false);
 | 
			
		||||
        var env = gconf ? gconf.env : [];
 | 
			
		||||
        var cred = gconf ? gconf.credentials : null;
 | 
			
		||||
        if (!cred) {
 | 
			
		||||
            cred = {
 | 
			
		||||
                _ : {},
 | 
			
		||||
                map: {}
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var pane = $("<div/>", {
 | 
			
		||||
            id: "red-ui-settings-tab-envvar",
 | 
			
		||||
            class: "form-horizontal"
 | 
			
		||||
        });
 | 
			
		||||
        var content = $("<div/>", {
 | 
			
		||||
            class: "form-row node-input-env-container-row"
 | 
			
		||||
        }).css({
 | 
			
		||||
            "margin": "10px"
 | 
			
		||||
        }).appendTo(pane);
 | 
			
		||||
 | 
			
		||||
        var label = $("<label></label>").css({
 | 
			
		||||
            width: "100%"
 | 
			
		||||
        }).appendTo(content);
 | 
			
		||||
        $("<i/>", {
 | 
			
		||||
            class: "fa fa-list"
 | 
			
		||||
        }).appendTo(label);
 | 
			
		||||
        $("<span/>").text(" "+RED._("env-var.header")).appendTo(label);
 | 
			
		||||
 | 
			
		||||
        var list = $("<ol/>", {
 | 
			
		||||
            id: "node-input-env-container"
 | 
			
		||||
        }).appendTo(content);
 | 
			
		||||
        var node = {
 | 
			
		||||
            type: "",
 | 
			
		||||
            env: env,
 | 
			
		||||
            credentials: cred.map,
 | 
			
		||||
        };
 | 
			
		||||
        RED.editor.envVarList.create(list, node);
 | 
			
		||||
 | 
			
		||||
        var buttons = $("<div/>").css({
 | 
			
		||||
            "text-align": "right",
 | 
			
		||||
        }).appendTo(content);
 | 
			
		||||
        var revertButton = $("<button/>", {
 | 
			
		||||
            class: "red-ui-button"
 | 
			
		||||
        }).css({
 | 
			
		||||
        }).text(RED._("env-var.revert")).appendTo(buttons);
 | 
			
		||||
 | 
			
		||||
        var items = saveEnvList(list);
 | 
			
		||||
        revertButton.on("click", function (ev) {
 | 
			
		||||
            list.editableList("empty");
 | 
			
		||||
            list.editableList("addItems", items);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return pane;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function init(done) {
 | 
			
		||||
        if (!RED.user.hasPermission("settings.write")) {
 | 
			
		||||
            RED.notify(RED._("user.errors.settings"),"error");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        RED.userSettings.add({
 | 
			
		||||
            id:'envvar',
 | 
			
		||||
            title: RED._("env-var.environment"),
 | 
			
		||||
            get: getSettingsPane,
 | 
			
		||||
            focus: function() {
 | 
			
		||||
                var height = $("#red-ui-settings-tab-envvar").parent().height();
 | 
			
		||||
                $("#node-input-env-container").editableList("height", (height -100));
 | 
			
		||||
            },
 | 
			
		||||
            close: function() {
 | 
			
		||||
                var list = $("#node-input-env-container");
 | 
			
		||||
                try {
 | 
			
		||||
                    applyChanges(list);
 | 
			
		||||
                }
 | 
			
		||||
                catch (e) {
 | 
			
		||||
                    console.log(e);
 | 
			
		||||
                    console.log(e.stack);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: init,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
@@ -188,8 +188,6 @@ RED.group = (function() {
 | 
			
		||||
            var activateMerge = false;
 | 
			
		||||
            var activateRemove = false;
 | 
			
		||||
            var singleGroupSelected = false;
 | 
			
		||||
            var locked = RED.workspaces.isActiveLocked()
 | 
			
		||||
 | 
			
		||||
            if (activateGroup) {
 | 
			
		||||
                singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
 | 
			
		||||
                selection.nodes.forEach(function (n) {
 | 
			
		||||
@@ -204,12 +202,12 @@ RED.group = (function() {
 | 
			
		||||
                    activateMerge = (selection.nodes.length > 1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-group", locked || !activateGroup);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-ungroup", locked || !activateUngroup);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-merge", locked || !activateMerge);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-remove", locked || !activateRemove);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-group", !activateGroup);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-merge", !activateMerge);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-group-remove", !activateRemove);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-copy-group-style", !singleGroupSelected);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-paste-group-style", locked || !activateUngroup);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-paste-group-style", !activateUngroup);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:group-selection", function() { groupSelection() })
 | 
			
		||||
@@ -266,7 +264,6 @@ RED.group = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function pasteGroupStyle() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (RED.view.state() !== RED.state.DEFAULT) { return }
 | 
			
		||||
        if (groupStyleClipboard) {
 | 
			
		||||
            var selection = RED.view.selection();
 | 
			
		||||
@@ -301,7 +298,6 @@ RED.group = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function groupSelection() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (RED.view.state() !== RED.state.DEFAULT) { return }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
@@ -320,7 +316,6 @@ RED.group = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function ungroupSelection() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (RED.view.state() !== RED.state.DEFAULT) { return }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
@@ -344,7 +339,6 @@ RED.group = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ungroup(g) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        var nodes = [];
 | 
			
		||||
        var parentGroup = RED.nodes.group(g.g);
 | 
			
		||||
        g.nodes.forEach(function(n) {
 | 
			
		||||
@@ -371,7 +365,6 @@ RED.group = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function mergeSelection() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (RED.view.state() !== RED.state.DEFAULT) { return }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
@@ -441,7 +434,6 @@ RED.group = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeSelection() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (RED.view.state() !== RED.state.DEFAULT) { return }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
@@ -469,7 +461,6 @@ RED.group = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function createGroup(nodes) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (nodes.length === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -492,7 +483,7 @@ RED.group = (function() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        group.z = nodes[0].z;
 | 
			
		||||
        group = RED.nodes.addGroup(group);
 | 
			
		||||
        RED.nodes.addGroup(group);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            addToGroup(group,nodes);
 | 
			
		||||
@@ -575,7 +566,6 @@ RED.group = (function() {
 | 
			
		||||
        markDirty(group);
 | 
			
		||||
    }
 | 
			
		||||
    function removeFromGroup(group, nodes, reparent) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (!Array.isArray(nodes)) {
 | 
			
		||||
            nodes = [nodes];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
// Mermaid diagram stub library for on-demand dynamic loading
 | 
			
		||||
// Will be overwritten after script loading by $.getScript
 | 
			
		||||
var mermaid = (function () {
 | 
			
		||||
    var enabled /* = undefined */;
 | 
			
		||||
    
 | 
			
		||||
    var initializing = false;
 | 
			
		||||
    var initCalled = false;
 | 
			
		||||
 | 
			
		||||
    function initialize(opt) {
 | 
			
		||||
        if (enabled === undefined) {
 | 
			
		||||
            if (RED.settings.markdownEditor &&
 | 
			
		||||
                RED.settings.markdownEditor.mermaid) {
 | 
			
		||||
                enabled = RED.settings.markdownEditor.mermaid.enabled;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                enabled = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (enabled) {
 | 
			
		||||
            initializing = true;
 | 
			
		||||
            $.getScript("vendor/mermaid/mermaid.min.js",
 | 
			
		||||
                        function (data, stat, jqxhr) {
 | 
			
		||||
                            $(".mermaid").show();
 | 
			
		||||
                            // invoke loaded mermaid API
 | 
			
		||||
                            initializing = false; 
 | 
			
		||||
                            mermaid.initialize(opt);
 | 
			
		||||
                            if (initCalled) {
 | 
			
		||||
                                mermaid.init();
 | 
			
		||||
                                initCalled = false;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function init() {
 | 
			
		||||
        if (initializing) {
 | 
			
		||||
            $(".mermaid").hide();
 | 
			
		||||
            initCalled = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return {
 | 
			
		||||
        initialize: initialize,
 | 
			
		||||
        init: init,
 | 
			
		||||
    };
 | 
			
		||||
})();
 | 
			
		||||
@@ -292,7 +292,6 @@ RED.palette = (function() {
 | 
			
		||||
            var hoverGroup;
 | 
			
		||||
            var paletteWidth;
 | 
			
		||||
            var paletteTop;
 | 
			
		||||
            var dropEnabled;
 | 
			
		||||
            $(d).draggable({
 | 
			
		||||
                helper: 'clone',
 | 
			
		||||
                appendTo: '#red-ui-editor',
 | 
			
		||||
@@ -300,7 +299,6 @@ RED.palette = (function() {
 | 
			
		||||
                revertDuration: 200,
 | 
			
		||||
                containment:'#red-ui-main-container',
 | 
			
		||||
                start: function() {
 | 
			
		||||
                    dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked);
 | 
			
		||||
                    paletteWidth = $("#red-ui-palette").width();
 | 
			
		||||
                    paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
 | 
			
		||||
                    hoverGroup = null;
 | 
			
		||||
@@ -311,100 +309,96 @@ RED.palette = (function() {
 | 
			
		||||
                    RED.view.focus();
 | 
			
		||||
                },
 | 
			
		||||
                stop: function() {
 | 
			
		||||
                    if (dropEnabled) {
 | 
			
		||||
                        d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
 | 
			
		||||
                        if (hoverGroup) {
 | 
			
		||||
                            document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
 | 
			
		||||
                        }
 | 
			
		||||
                        if (activeGroup) {
 | 
			
		||||
                            document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
 | 
			
		||||
                        }
 | 
			
		||||
                        if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
 | 
			
		||||
                        if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
 | 
			
		||||
                    d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
 | 
			
		||||
                    if (hoverGroup) {
 | 
			
		||||
                        document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (activeGroup) {
 | 
			
		||||
                        document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
 | 
			
		||||
                    if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
 | 
			
		||||
                },
 | 
			
		||||
                drag: function(e,ui) {
 | 
			
		||||
                    var paletteNode = getPaletteNode(nt);
 | 
			
		||||
                    ui.originalPosition.left = paletteNode.offset().left;
 | 
			
		||||
                    if (dropEnabled) {
 | 
			
		||||
                        mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
 | 
			
		||||
                        mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
 | 
			
		||||
                        if (!groupTimer) {
 | 
			
		||||
                            groupTimer = setTimeout(function() {
 | 
			
		||||
                    mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
 | 
			
		||||
                    mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
 | 
			
		||||
                    if (!groupTimer) {
 | 
			
		||||
                        groupTimer = setTimeout(function() {
 | 
			
		||||
                            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");
 | 
			
		||||
                                }
 | 
			
		||||
                                if (group) {
 | 
			
		||||
                                    document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
 | 
			
		||||
                                }
 | 
			
		||||
                                hoverGroup = group;
 | 
			
		||||
                                if (hoverGroup) {
 | 
			
		||||
                                    $(ui.helper).data('group',hoverGroup);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    $(ui.helper).removeData('group');
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            groupTimer = null;
 | 
			
		||||
 | 
			
		||||
                        },200)
 | 
			
		||||
                    }
 | 
			
		||||
                    if (def.inputs > 0 && def.outputs > 0) {
 | 
			
		||||
                        if (!spliceTimer) {
 | 
			
		||||
                            spliceTimer = setTimeout(function() {
 | 
			
		||||
                                var nodes = [];
 | 
			
		||||
                                var bestDistance = Infinity;
 | 
			
		||||
                                var bestLink = null;
 | 
			
		||||
                                if (chartSVG.getIntersectionList) {
 | 
			
		||||
                                    var svgRect = chartSVG.createSVGRect();
 | 
			
		||||
                                    svgRect.x = mouseX;
 | 
			
		||||
                                    svgRect.y = mouseY;
 | 
			
		||||
                                    svgRect.width = 1;
 | 
			
		||||
                                    svgRect.height = 1;
 | 
			
		||||
                                    nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    // Firefox doesn't do getIntersectionList and that
 | 
			
		||||
                                    // makes us sad
 | 
			
		||||
                                    nodes = RED.view.getLinksAtPoint(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");
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (group) {
 | 
			
		||||
                                        document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
 | 
			
		||||
                                    }
 | 
			
		||||
                                    hoverGroup = group;
 | 
			
		||||
                                    if (hoverGroup) {
 | 
			
		||||
                                        $(ui.helper).data('group',hoverGroup);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        $(ui.helper).removeData('group');
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                groupTimer = null;
 | 
			
		||||
 | 
			
		||||
                            },200)
 | 
			
		||||
                        }
 | 
			
		||||
                        if (def.inputs > 0 && def.outputs > 0) {
 | 
			
		||||
                            if (!spliceTimer) {
 | 
			
		||||
                                spliceTimer = setTimeout(function() {
 | 
			
		||||
                                    var nodes = [];
 | 
			
		||||
                                    var bestDistance = Infinity;
 | 
			
		||||
                                    var bestLink = null;
 | 
			
		||||
                                    if (chartSVG.getIntersectionList) {
 | 
			
		||||
                                        var svgRect = chartSVG.createSVGRect();
 | 
			
		||||
                                        svgRect.x = mouseX;
 | 
			
		||||
                                        svgRect.y = mouseY;
 | 
			
		||||
                                        svgRect.width = 1;
 | 
			
		||||
                                        svgRect.height = 1;
 | 
			
		||||
                                        nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        // Firefox doesn't do getIntersectionList and that
 | 
			
		||||
                                        // makes us sad
 | 
			
		||||
                                        nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    var mx = mouseX / RED.view.scale();
 | 
			
		||||
                                    var my = mouseY / RED.view.scale();
 | 
			
		||||
                                    for (var i=0;i<nodes.length;i++) {
 | 
			
		||||
                                        var node = d3.select(nodes[i]);
 | 
			
		||||
                                        if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) {
 | 
			
		||||
                                            var length = nodes[i].getTotalLength();
 | 
			
		||||
                                            for (var j=0;j<length;j+=10) {
 | 
			
		||||
                                                var p = nodes[i].getPointAtLength(j);
 | 
			
		||||
                                                var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my));
 | 
			
		||||
                                                if (d2 < 200 && d2 < bestDistance) {
 | 
			
		||||
                                                    bestDistance = d2;
 | 
			
		||||
                                                    bestLink = nodes[i];
 | 
			
		||||
                                                }
 | 
			
		||||
                                for (var i=0;i<nodes.length;i++) {
 | 
			
		||||
                                    var node = d3.select(nodes[i]);
 | 
			
		||||
                                    if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) {
 | 
			
		||||
                                        var length = nodes[i].getTotalLength();
 | 
			
		||||
                                        for (var j=0;j<length;j+=10) {
 | 
			
		||||
                                            var p = nodes[i].getPointAtLength(j);
 | 
			
		||||
                                            var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my));
 | 
			
		||||
                                            if (d2 < 200 && d2 < bestDistance) {
 | 
			
		||||
                                                bestDistance = d2;
 | 
			
		||||
                                                bestLink = nodes[i];
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (activeSpliceLink && activeSpliceLink !== bestLink) {
 | 
			
		||||
                                        d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                if (activeSpliceLink && activeSpliceLink !== bestLink) {
 | 
			
		||||
                                    d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
 | 
			
		||||
                                }
 | 
			
		||||
                                if (bestLink) {
 | 
			
		||||
                                    d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
 | 
			
		||||
                                }
 | 
			
		||||
                                if (activeSpliceLink !== bestLink) {
 | 
			
		||||
                                    if (bestLink) {
 | 
			
		||||
                                        d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
 | 
			
		||||
                                        $(ui.helper).data('splice',d3.select(bestLink).data()[0]);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
 | 
			
		||||
                                        $(ui.helper).removeData('splice');
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (activeSpliceLink !== bestLink) {
 | 
			
		||||
                                        if (bestLink) {
 | 
			
		||||
                                            $(ui.helper).data('splice',d3.select(bestLink).data()[0]);
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            $(ui.helper).removeData('splice');
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    activeSpliceLink = bestLink;
 | 
			
		||||
                                    spliceTimer = null;
 | 
			
		||||
                                },200);
 | 
			
		||||
                            }
 | 
			
		||||
                                }
 | 
			
		||||
                                activeSpliceLink = bestLink;
 | 
			
		||||
                                spliceTimer = null;
 | 
			
		||||
                            },200);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -273,11 +273,6 @@ RED.subflow = (function() {
 | 
			
		||||
        var subflowInstances = [];
 | 
			
		||||
        if (activeSubflow) {
 | 
			
		||||
            RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
 | 
			
		||||
                const parentFlow = RED.nodes.workspace(n.z)
 | 
			
		||||
                const wasLocked = parentFlow && parentFlow.locked
 | 
			
		||||
                if (wasLocked) {
 | 
			
		||||
                    parentFlow.locked = false
 | 
			
		||||
                }
 | 
			
		||||
                subflowInstances.push({
 | 
			
		||||
                    id: n.id,
 | 
			
		||||
                    changed: n.changed
 | 
			
		||||
@@ -290,9 +285,6 @@ RED.subflow = (function() {
 | 
			
		||||
                n.resize = true;
 | 
			
		||||
                n.dirty = true;
 | 
			
		||||
                RED.editor.updateNodeProperties(n);
 | 
			
		||||
                if (wasLocked) {
 | 
			
		||||
                    parentFlow.locked = true
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            RED.editor.validateNode(activeSubflow);
 | 
			
		||||
            return {
 | 
			
		||||
@@ -439,7 +431,44 @@ RED.subflow = (function() {
 | 
			
		||||
 | 
			
		||||
        $("#red-ui-subflow-delete").on("click", function(event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            RED.subflow.delete(RED.workspaces.active())
 | 
			
		||||
            var subflow = RED.nodes.subflow(RED.workspaces.active());
 | 
			
		||||
            if (subflow.instances.length > 0) {
 | 
			
		||||
                var msg = $('<div>')
 | 
			
		||||
                $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
 | 
			
		||||
                $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
 | 
			
		||||
                var confirmDeleteNotification = RED.notify(msg, {
 | 
			
		||||
                    modal: true,
 | 
			
		||||
                    fixed: true,
 | 
			
		||||
                    buttons: [
 | 
			
		||||
                        {
 | 
			
		||||
                            text: RED._('common.label.cancel'),
 | 
			
		||||
                            click: function() {
 | 
			
		||||
                                confirmDeleteNotification.close();
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            text: RED._('workspace.confirmDelete'),
 | 
			
		||||
                            class: "primary",
 | 
			
		||||
                            click: function() {
 | 
			
		||||
                                confirmDeleteNotification.close();
 | 
			
		||||
                                completeDelete();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                completeDelete();
 | 
			
		||||
            }
 | 
			
		||||
            function completeDelete() {
 | 
			
		||||
                var startDirty = RED.nodes.dirty();
 | 
			
		||||
                var historyEvent = removeSubflow(RED.workspaces.active());
 | 
			
		||||
                historyEvent.t = 'delete';
 | 
			
		||||
                historyEvent.dirty = startDirty;
 | 
			
		||||
                RED.history.push(historyEvent);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        refreshToolbar(activeSubflow);
 | 
			
		||||
@@ -452,51 +481,7 @@ RED.subflow = (function() {
 | 
			
		||||
        $("#red-ui-workspace-toolbar").hide().empty();
 | 
			
		||||
        $("#red-ui-workspace-chart").css({"margin-top": "0"});
 | 
			
		||||
    }
 | 
			
		||||
    function deleteSubflow(id) {
 | 
			
		||||
        const subflow = RED.nodes.subflow(id || RED.workspaces.active());
 | 
			
		||||
        if (!subflow) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        if (subflow.instances.length > 0) {
 | 
			
		||||
            if (subflow.instances.some(sf => { const ws = RED.nodes.workspace(sf.z); return ws?ws.locked:false })) {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            const msg = $('<div>')
 | 
			
		||||
            $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
 | 
			
		||||
            $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
 | 
			
		||||
            const confirmDeleteNotification = RED.notify(msg, {
 | 
			
		||||
                modal: true,
 | 
			
		||||
                fixed: true,
 | 
			
		||||
                buttons: [
 | 
			
		||||
                    {
 | 
			
		||||
                        text: RED._('common.label.cancel'),
 | 
			
		||||
                        click: function() {
 | 
			
		||||
                            confirmDeleteNotification.close();
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        text: RED._('workspace.confirmDelete'),
 | 
			
		||||
                        class: "primary",
 | 
			
		||||
                        click: function() {
 | 
			
		||||
                            confirmDeleteNotification.close();
 | 
			
		||||
                            completeDelete();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        } else {
 | 
			
		||||
            completeDelete();
 | 
			
		||||
        }
 | 
			
		||||
        function completeDelete() {
 | 
			
		||||
            const startDirty = RED.nodes.dirty();
 | 
			
		||||
            const historyEvent = removeSubflow(subflow.id);
 | 
			
		||||
            historyEvent.t = 'delete';
 | 
			
		||||
            historyEvent.dirty = startDirty;
 | 
			
		||||
            RED.history.push(historyEvent);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function removeSubflow(id, keepInstanceNodes) {
 | 
			
		||||
        // TODO:  A lot of this logic is common with RED.nodes.removeWorkspace
 | 
			
		||||
        var removedNodes = [];
 | 
			
		||||
@@ -573,7 +558,7 @@ RED.subflow = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        RED.events.on("view:selection-changed",function(selection) {
 | 
			
		||||
            if (!selection.nodes || RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            if (!selection.nodes) {
 | 
			
		||||
                RED.menu.setDisabled("menu-item-subflow-convert",true);
 | 
			
		||||
            } else {
 | 
			
		||||
                RED.menu.setDisabled("menu-item-subflow-convert",false);
 | 
			
		||||
@@ -636,9 +621,6 @@ RED.subflow = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function convertToSubflow() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (!selection.nodes) {
 | 
			
		||||
            RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
 | 
			
		||||
@@ -794,7 +776,7 @@ RED.subflow = (function() {
 | 
			
		||||
        }
 | 
			
		||||
        subflowInstance._def = RED.nodes.getType(subflowInstance.type);
 | 
			
		||||
        RED.editor.validateNode(subflowInstance);
 | 
			
		||||
        subflowInstance = RED.nodes.add(subflowInstance);
 | 
			
		||||
        RED.nodes.add(subflowInstance);
 | 
			
		||||
 | 
			
		||||
        if (containingGroup) {
 | 
			
		||||
            RED.group.addToGroup(containingGroup, subflowInstance);
 | 
			
		||||
@@ -1348,10 +1330,7 @@ RED.subflow = (function() {
 | 
			
		||||
        init: init,
 | 
			
		||||
        createSubflow: createSubflow,
 | 
			
		||||
        convertToSubflow: convertToSubflow,
 | 
			
		||||
        // removeSubflow: Internal function to remove subflow
 | 
			
		||||
        removeSubflow: removeSubflow,
 | 
			
		||||
        // delete: Prompt user for confirmation
 | 
			
		||||
        delete: deleteSubflow,
 | 
			
		||||
        refresh: refresh,
 | 
			
		||||
        removeInput: removeSubflowInput,
 | 
			
		||||
        removeOutput: removeSubflowOutput,
 | 
			
		||||
 
 | 
			
		||||
@@ -43,15 +43,12 @@ RED.sidebar.config = (function() {
 | 
			
		||||
 | 
			
		||||
    var categories = {};
 | 
			
		||||
 | 
			
		||||
    function getOrCreateCategory(name,parent,label,isLocked) {
 | 
			
		||||
    function getOrCreateCategory(name,parent,label) {
 | 
			
		||||
        name = name.replace(/\./i,"-");
 | 
			
		||||
        if (!categories[name]) {
 | 
			
		||||
            var container = $('<div class="red-ui-palette-category red-ui-sidebar-config-category" id="red-ui-sidebar-config-category-'+name+'"></div>').appendTo(parent);
 | 
			
		||||
            var header = $('<div class="red-ui-sidebar-config-tray-header red-ui-palette-header"><i class="fa fa-angle-down expanded"></i></div>').appendTo(container);
 | 
			
		||||
            let lockIcon
 | 
			
		||||
            if (label) {
 | 
			
		||||
                lockIcon = $('<span style="margin-right: 5px"><i class="fa fa-lock"/></span>').appendTo(header)
 | 
			
		||||
                lockIcon.toggle(!!isLocked)
 | 
			
		||||
                $('<span class="red-ui-palette-node-config-label"/>').text(label).appendTo(header);
 | 
			
		||||
            } else {
 | 
			
		||||
                $('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
 | 
			
		||||
@@ -65,7 +62,6 @@ RED.sidebar.config = (function() {
 | 
			
		||||
            var icon = header.find("i");
 | 
			
		||||
            var result = {
 | 
			
		||||
                label: label,
 | 
			
		||||
                lockIcon,
 | 
			
		||||
                list: category,
 | 
			
		||||
                size: function() {
 | 
			
		||||
                    return result.list.find("li:not(.red-ui-palette-node-config-none)").length
 | 
			
		||||
@@ -104,9 +100,6 @@ RED.sidebar.config = (function() {
 | 
			
		||||
            });
 | 
			
		||||
            categories[name] = result;
 | 
			
		||||
        } else {
 | 
			
		||||
            if (isLocked !== undefined && categories[name].lockIcon) {
 | 
			
		||||
                categories[name].lockIcon.toggle(!!isLocked)
 | 
			
		||||
            }
 | 
			
		||||
            if (categories[name].label !== label) {
 | 
			
		||||
                categories[name].list.parent().find('.red-ui-palette-node-config-label').text(label);
 | 
			
		||||
                categories[name].label = label;
 | 
			
		||||
@@ -223,7 +216,7 @@ RED.sidebar.config = (function() {
 | 
			
		||||
 | 
			
		||||
        RED.nodes.eachWorkspace(function(ws) {
 | 
			
		||||
            validList[ws.id.replace(/\./g,"-")] = true;
 | 
			
		||||
            getOrCreateCategory(ws.id,flowCategories,ws.label, ws.locked);
 | 
			
		||||
            getOrCreateCategory(ws.id,flowCategories,ws.label);
 | 
			
		||||
        })
 | 
			
		||||
        RED.nodes.eachSubflow(function(sf) {
 | 
			
		||||
            validList[sf.id.replace(/\./g,"-")] = true;
 | 
			
		||||
@@ -281,15 +274,6 @@ RED.sidebar.config = (function() {
 | 
			
		||||
                    changes: {},
 | 
			
		||||
                    dirty: RED.nodes.dirty()
 | 
			
		||||
                }
 | 
			
		||||
                for (let i = 0; i < selectedNodes.length; i++) {
 | 
			
		||||
                    let node = RED.nodes.node(selectedNodes[i])
 | 
			
		||||
                    if (node.z) {
 | 
			
		||||
                        let ws = RED.nodes.workspace(node.z)
 | 
			
		||||
                        if (ws && ws.locked) {
 | 
			
		||||
                            return
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                selectedNodes.forEach(function(id) {
 | 
			
		||||
                    var node = RED.nodes.node(id);
 | 
			
		||||
                    try {
 | 
			
		||||
 
 | 
			
		||||
@@ -141,8 +141,7 @@ RED.sidebar.help = (function() {
 | 
			
		||||
        RED.events.on('registry:node-type-removed', queueRefresh);
 | 
			
		||||
        RED.events.on('subflows:change', refreshSubflow);
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:show-help-tab", show);
 | 
			
		||||
        RED.actions.add("core:show-node-help", showNodeHelp)
 | 
			
		||||
        RED.actions.add("core:show-help-tab",show);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -339,19 +338,6 @@ RED.sidebar.help = (function() {
 | 
			
		||||
        resizeStack();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showNodeHelp(node) {
 | 
			
		||||
        if (!node) {
 | 
			
		||||
            const selection = RED.view.selection()
 | 
			
		||||
            if (selection.nodes && selection.nodes.length > 0) {
 | 
			
		||||
                node = selection.nodes.find(n => n.type !== 'group' && n.type !== 'junction')
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (node) {
 | 
			
		||||
            show(node.type, true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // TODO: DRY - projects.js
 | 
			
		||||
    function addTargetToExternalLinks(el) {
 | 
			
		||||
        $(el).find("a").each(function(el) {
 | 
			
		||||
 
 | 
			
		||||
@@ -135,6 +135,10 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
                    RED.workspaces.show(n.id, null, true);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            RED.popover.tooltip(toggleVisibleButton, function () {
 | 
			
		||||
                var isHidden = !div.hasClass("red-ui-info-outline-item-hidden");
 | 
			
		||||
                return RED._("sidebar.info." + (isHidden ? "hideFlow" : "showFlow"));
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (n.type !== 'subflow') {
 | 
			
		||||
            var toggleButton = $('<button type="button" class="red-ui-info-outline-item-control-disable red-ui-button red-ui-button-small"><i class="fa fa-circle-thin"></i><i class="fa fa-ban"></i></button>').appendTo(controls).on("click",function(evt) {
 | 
			
		||||
@@ -221,22 +225,6 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        } else {
 | 
			
		||||
            $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls)
 | 
			
		||||
        }
 | 
			
		||||
        if (n.type === 'tab') {
 | 
			
		||||
            var lockToggleButton = $('<button type="button" class="red-ui-info-outline-item-control-lock red-ui-button red-ui-button-small"><i class="fa fa-unlock-alt"></i><i class="fa fa-lock"></i></button>').appendTo(controls).on("click",function(evt) {
 | 
			
		||||
                evt.preventDefault();
 | 
			
		||||
                evt.stopPropagation();
 | 
			
		||||
                if (n.locked) {
 | 
			
		||||
                    RED.workspaces.unlock(n.id)
 | 
			
		||||
                } else {
 | 
			
		||||
                    RED.workspaces.lock(n.id)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            RED.popover.tooltip(lockToggleButton,function() {
 | 
			
		||||
                return RED._("common.label."+(n.locked?"unlock":"lock"));
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls)
 | 
			
		||||
        }
 | 
			
		||||
        controls.find("button").on("dblclick", function(evt) {
 | 
			
		||||
            evt.preventDefault();
 | 
			
		||||
            evt.stopPropagation();
 | 
			
		||||
@@ -380,8 +368,6 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        flowList.treeList.addChild(objects[ws.id])
 | 
			
		||||
        objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
 | 
			
		||||
        objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
 | 
			
		||||
        objects[ws.id].element.toggleClass("red-ui-info-outline-item-locked", !!ws.locked)
 | 
			
		||||
        objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-locked", !!ws.locked)
 | 
			
		||||
        updateSearch();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
@@ -396,8 +382,6 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
        existingObject.element.find(".red-ui-info-outline-item-label").text(label);
 | 
			
		||||
        existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
 | 
			
		||||
        existingObject.treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
 | 
			
		||||
        existingObject.element.toggleClass("red-ui-info-outline-item-locked", !!n.locked)
 | 
			
		||||
        existingObject.treeList.container.toggleClass("red-ui-info-outline-item-locked", !!n.locked)
 | 
			
		||||
        updateSearch();
 | 
			
		||||
    }
 | 
			
		||||
    function onFlowsReorder(order) {
 | 
			
		||||
@@ -633,6 +617,9 @@ RED.sidebar.info.outliner = (function() {
 | 
			
		||||
                objects[n.id].children = missingParents[n.id];
 | 
			
		||||
                delete missingParents[n.id]
 | 
			
		||||
            }
 | 
			
		||||
            if (objects[n.id].children.length === 0) {
 | 
			
		||||
                objects[n.id].children.push(getEmptyItem(n.id));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        var parent = n.g||n.z||"__global__";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ RED.sidebar.info = (function() {
 | 
			
		||||
    var propertiesPanelHeaderLabel;
 | 
			
		||||
    var propertiesPanelHeaderReveal;
 | 
			
		||||
    var propertiesPanelHeaderHelp;
 | 
			
		||||
    var propertiesPanelHeaderCopyLink;
 | 
			
		||||
 | 
			
		||||
    var selectedObject;
 | 
			
		||||
 | 
			
		||||
@@ -68,20 +67,10 @@ RED.sidebar.info = (function() {
 | 
			
		||||
 | 
			
		||||
        propertiesPanelHeaderIcon = $("<span>").appendTo(propertiesPanelHeader);
 | 
			
		||||
        propertiesPanelHeaderLabel = $("<span>").appendTo(propertiesPanelHeader);
 | 
			
		||||
 | 
			
		||||
        propertiesPanelHeaderCopyLink = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-link"></button>').css({
 | 
			
		||||
        propertiesPanelHeaderHelp = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-book"></button>').css({
 | 
			
		||||
            position: 'absolute',
 | 
			
		||||
            top: '12px',
 | 
			
		||||
            right: '32px'
 | 
			
		||||
        }).on("click", function(evt) {
 | 
			
		||||
            RED.actions.invoke('core:copy-item-url',selectedObject)
 | 
			
		||||
        }).appendTo(propertiesPanelHeader);
 | 
			
		||||
        RED.popover.tooltip(propertiesPanelHeaderCopyLink,RED._("sidebar.info.copyItemUrl"));
 | 
			
		||||
 | 
			
		||||
        propertiesPanelHeaderHelp = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-book"></button>').css({
 | 
			
		||||
            position: 'absolute',
 | 
			
		||||
            top: '12px',
 | 
			
		||||
            right: '56px'
 | 
			
		||||
        }).on("click", function(evt) {
 | 
			
		||||
            evt.preventDefault();
 | 
			
		||||
            evt.stopPropagation();
 | 
			
		||||
@@ -91,7 +80,8 @@ RED.sidebar.info = (function() {
 | 
			
		||||
        }).appendTo(propertiesPanelHeader);
 | 
			
		||||
        RED.popover.tooltip(propertiesPanelHeaderHelp,RED._("sidebar.help.showHelp"));
 | 
			
		||||
 | 
			
		||||
        propertiesPanelHeaderReveal = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-search"></button>').css({
 | 
			
		||||
 | 
			
		||||
        propertiesPanelHeaderReveal = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-search"></button>').css({
 | 
			
		||||
            position: 'absolute',
 | 
			
		||||
            top: '12px',
 | 
			
		||||
            right: '8px'
 | 
			
		||||
@@ -195,7 +185,6 @@ RED.sidebar.info = (function() {
 | 
			
		||||
            propertiesPanelHeaderLabel.text("");
 | 
			
		||||
            propertiesPanelHeaderReveal.hide();
 | 
			
		||||
            propertiesPanelHeaderHelp.hide();
 | 
			
		||||
            propertiesPanelHeaderCopyLink.hide();
 | 
			
		||||
            return;
 | 
			
		||||
        } else if (Array.isArray(node)) {
 | 
			
		||||
            // Multiple things selected
 | 
			
		||||
@@ -207,7 +196,6 @@ RED.sidebar.info = (function() {
 | 
			
		||||
            propertiesPanelHeaderLabel.text("Selection");
 | 
			
		||||
            propertiesPanelHeaderReveal.hide();
 | 
			
		||||
            propertiesPanelHeaderHelp.hide();
 | 
			
		||||
            propertiesPanelHeaderCopyLink.hide();
 | 
			
		||||
            selectedObject = null;
 | 
			
		||||
 | 
			
		||||
            var types = {
 | 
			
		||||
@@ -289,11 +277,9 @@ RED.sidebar.info = (function() {
 | 
			
		||||
            if (node.type === "tab" || node.type === "subflow") {
 | 
			
		||||
                // If nothing is selected, but we're on a flow or subflow tab.
 | 
			
		||||
                propertiesPanelHeaderHelp.hide();
 | 
			
		||||
                propertiesPanelHeaderCopyLink.show();
 | 
			
		||||
 | 
			
		||||
            } else if (node.type === "group") {
 | 
			
		||||
                propertiesPanelHeaderHelp.hide();
 | 
			
		||||
                propertiesPanelHeaderCopyLink.show();
 | 
			
		||||
 | 
			
		||||
                propRow = $('<tr class="red-ui-help-info-row"><td> </td><td></td></tr>').appendTo(tableBody);
 | 
			
		||||
 | 
			
		||||
@@ -318,10 +304,8 @@ RED.sidebar.info = (function() {
 | 
			
		||||
                }
 | 
			
		||||
            } else if (node.type === 'junction') {
 | 
			
		||||
                propertiesPanelHeaderHelp.hide();
 | 
			
		||||
                propertiesPanelHeaderCopyLink.hide();
 | 
			
		||||
            } else {
 | 
			
		||||
                propertiesPanelHeaderHelp.show();
 | 
			
		||||
                propertiesPanelHeaderCopyLink.show();
 | 
			
		||||
 | 
			
		||||
                if (!subflowRegex) {
 | 
			
		||||
                    propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.type")+'</td><td></td></tr>').appendTo(tableBody);
 | 
			
		||||
@@ -463,8 +447,7 @@ RED.sidebar.info = (function() {
 | 
			
		||||
                    el = el.next();
 | 
			
		||||
                }
 | 
			
		||||
                $(this).toggleClass('expanded',!isExpanded);
 | 
			
		||||
            });
 | 
			
		||||
        mermaid.init();
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var tips = (function() {
 | 
			
		||||
 
 | 
			
		||||
@@ -435,15 +435,10 @@ RED.tourGuide = (function() {
 | 
			
		||||
 | 
			
		||||
    function listTour() {
 | 
			
		||||
        return [
 | 
			
		||||
            {
 | 
			
		||||
                id: "3_1",
 | 
			
		||||
                label: "3.1",
 | 
			
		||||
                path: "./tours/welcome.js"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: "3_0",
 | 
			
		||||
                label: "3.0",
 | 
			
		||||
                path: "./tours/3.0/welcome.js"
 | 
			
		||||
                path: "./tours/welcome.js"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: "2_2",
 | 
			
		||||
 
 | 
			
		||||
@@ -96,37 +96,6 @@ RED.utils = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var mermaidIsInitialized = false;
 | 
			
		||||
    var mermaidIsEnabled /* = undefined */;
 | 
			
		||||
 | 
			
		||||
    renderer.code = function (code, lang) {
 | 
			
		||||
        if(lang === "mermaid") {
 | 
			
		||||
            // mermaid diagram rendering 
 | 
			
		||||
            if (mermaidIsEnabled === undefined) {
 | 
			
		||||
                if (RED.settings.markdownEditor &&
 | 
			
		||||
                    RED.settings.markdownEditor.mermaid) {
 | 
			
		||||
                    mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled;
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    mermaidIsEnabled = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (mermaidIsEnabled) {
 | 
			
		||||
                if (!mermaidIsInitialized) {
 | 
			
		||||
                    mermaidIsInitialized = true;
 | 
			
		||||
                    mermaid.initialize({startOnLoad:false});
 | 
			
		||||
                }
 | 
			
		||||
                return `<pre class='mermaid'>${code}</pre>`;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            return "<pre><code>" +code +"</code></pre>";
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    window._marked.setOptions({
 | 
			
		||||
        renderer: renderer,
 | 
			
		||||
        gfm: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 **/
 | 
			
		||||
 | 
			
		||||
RED.view.tools = (function() {
 | 
			
		||||
    'use strict';
 | 
			
		||||
 | 
			
		||||
    function selectConnected(type) {
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        var visited = new Set();
 | 
			
		||||
@@ -39,9 +39,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function alignToGrid() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
            var changedNodes = [];
 | 
			
		||||
@@ -90,9 +87,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function moveSelection(dx,dy) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        if (moving_set === null) {
 | 
			
		||||
            moving_set = [];
 | 
			
		||||
            var selection = RED.view.selection();
 | 
			
		||||
@@ -159,9 +153,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setSelectedNodeLabelState(labelShown) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        var historyEvents = [];
 | 
			
		||||
        var nodes = [];
 | 
			
		||||
@@ -448,9 +439,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function alignSelectionToEdge(direction) {
 | 
			
		||||
        // if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
        //     return
 | 
			
		||||
        // }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
 | 
			
		||||
        if (selection.nodes && selection.nodes.length > 1) {
 | 
			
		||||
@@ -551,10 +539,8 @@ RED.view.tools = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function distributeSelection(direction) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
 | 
			
		||||
        if (selection.nodes && selection.nodes.length > 2) {
 | 
			
		||||
@@ -713,9 +699,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function reorderSelection(dir) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
            var nodesToMove = [];
 | 
			
		||||
@@ -751,10 +734,8 @@ RED.view.tools = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function wireSeriesOfNodes() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
            if (selection.nodes.length > 1) {
 | 
			
		||||
@@ -795,9 +776,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function wireNodeToMultiple() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
            if (selection.nodes.length > 1) {
 | 
			
		||||
@@ -845,9 +823,6 @@ RED.view.tools = (function() {
 | 
			
		||||
     * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
 | 
			
		||||
     */
 | 
			
		||||
    function splitWiresWithLinkNodes(wires) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
 | 
			
		||||
        if (!wiresToSplit) {
 | 
			
		||||
            return
 | 
			
		||||
@@ -902,6 +877,7 @@ RED.view.tools = (function() {
 | 
			
		||||
            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) {
 | 
			
		||||
 | 
			
		||||
@@ -916,8 +892,7 @@ RED.view.tools = (function() {
 | 
			
		||||
                    updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
 | 
			
		||||
                }
 | 
			
		||||
                //add created node
 | 
			
		||||
                nnLinkOut = RED.nodes.add(nnLinkOut);
 | 
			
		||||
                nodeSrcMap[linkOutMapId] = nnLinkOut;
 | 
			
		||||
                RED.nodes.add(nnLinkOut);
 | 
			
		||||
                RED.editor.validateNode(nnLinkOut);
 | 
			
		||||
                history.events.push(nLinkOut.historyEvent);
 | 
			
		||||
                //connect node to link node
 | 
			
		||||
@@ -938,10 +913,10 @@ RED.view.tools = (function() {
 | 
			
		||||
            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
 | 
			
		||||
                nnLinkIn = RED.nodes.add(nnLinkIn);
 | 
			
		||||
                nodeTrgMap[nTrg.id] = nnLinkIn;
 | 
			
		||||
                RED.nodes.add(nnLinkIn);
 | 
			
		||||
                RED.editor.validateNode(nnLinkIn);
 | 
			
		||||
                history.events.push(nLinkIn.historyEvent);
 | 
			
		||||
                //connect node to link node
 | 
			
		||||
@@ -1016,9 +991,6 @@ RED.view.tools = (function() {
 | 
			
		||||
     * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
 | 
			
		||||
     */
 | 
			
		||||
    function generateNodeNames(node, options) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        options = Object.assign({
 | 
			
		||||
            renameBlank: true,
 | 
			
		||||
            renameClash: true,
 | 
			
		||||
@@ -1089,9 +1061,6 @@ RED.view.tools = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function addJunctionsToWires(wires) {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
 | 
			
		||||
        if (!wiresToSplit) {
 | 
			
		||||
            return
 | 
			
		||||
@@ -1162,7 +1131,7 @@ RED.view.tools = (function() {
 | 
			
		||||
 | 
			
		||||
            var nodeGroups = new Set()
 | 
			
		||||
 | 
			
		||||
            junction = RED.nodes.addJunction(junction)
 | 
			
		||||
            RED.nodes.addJunction(junction)
 | 
			
		||||
            addedJunctions.push(junction)
 | 
			
		||||
            let newLink
 | 
			
		||||
            if (gid === links[0].source.id+":"+links[0].sourcePort) {
 | 
			
		||||
@@ -1223,30 +1192,6 @@ RED.view.tools = (function() {
 | 
			
		||||
        RED.view.redraw(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function copyItemUrl(node, isEdit) {
 | 
			
		||||
        if (!node) {
 | 
			
		||||
            const selection = RED.view.selection();
 | 
			
		||||
            if (selection.nodes && selection.nodes.length > 0) {
 | 
			
		||||
                node = selection.nodes[0]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (node) {
 | 
			
		||||
            let thingType = 'node'
 | 
			
		||||
            if (node.type === 'group') {
 | 
			
		||||
                thingType = 'group'
 | 
			
		||||
            } else if (node.type === 'tab' || node.type === 'subflow') {
 | 
			
		||||
                thingType = 'flow'
 | 
			
		||||
            }
 | 
			
		||||
            let url = `${window.location.origin}${window.location.pathname}#${thingType}/${node.id}`
 | 
			
		||||
            if (isEdit) {
 | 
			
		||||
                url += '/edit'
 | 
			
		||||
            }
 | 
			
		||||
            if (RED.clipboard.copyText(url)) {
 | 
			
		||||
                RED.notify(RED._("sidebar.info.copyURL2Clipboard"), { timeout: 2000 })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        init: function() {
 | 
			
		||||
            RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
 | 
			
		||||
@@ -1313,9 +1258,6 @@ RED.view.tools = (function() {
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:generate-node-names", generateNodeNames )
 | 
			
		||||
 | 
			
		||||
            RED.actions.add("core:copy-item-url", function (node) { copyItemUrl(node) })
 | 
			
		||||
            RED.actions.add("core:copy-item-edit-url", function (node) { copyItemUrl(node, true) })
 | 
			
		||||
 | 
			
		||||
            // RED.actions.add("core:add-node", function() { addNode() })
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,6 @@ RED.view = (function() {
 | 
			
		||||
    var spliceTimer;
 | 
			
		||||
    var groupHoverTimer;
 | 
			
		||||
 | 
			
		||||
    var activeFlowLocked = false;
 | 
			
		||||
    var activeSubflow = null;
 | 
			
		||||
    var activeNodes = [];
 | 
			
		||||
    var activeLinks = [];
 | 
			
		||||
@@ -212,7 +211,6 @@ RED.view = (function() {
 | 
			
		||||
            evt.preventDefault()
 | 
			
		||||
            evt.stopPropagation()
 | 
			
		||||
            RED.contextMenu.show({
 | 
			
		||||
                type: 'workspace',
 | 
			
		||||
                x:evt.clientX-5,
 | 
			
		||||
                y:evt.clientY-5
 | 
			
		||||
            })
 | 
			
		||||
@@ -412,19 +410,8 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
            activeSubflow = RED.nodes.subflow(event.workspace);
 | 
			
		||||
 | 
			
		||||
            if (activeSubflow) {
 | 
			
		||||
                activeFlowLocked = activeSubflow.locked
 | 
			
		||||
            } else {
 | 
			
		||||
                var activeWorkspace = RED.nodes.workspace(event.workspace)
 | 
			
		||||
                if (activeWorkspace) {
 | 
			
		||||
                    activeFlowLocked = activeWorkspace.locked
 | 
			
		||||
                } else {
 | 
			
		||||
                    activeFlowLocked = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-edit", activeFlowLocked || activeSubflow || event.workspace === 0);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-delete",activeFlowLocked || event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow);
 | 
			
		||||
 | 
			
		||||
            if (workspaceScrollPositions[event.workspace]) {
 | 
			
		||||
                chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
 | 
			
		||||
@@ -451,15 +438,6 @@ RED.view = (function() {
 | 
			
		||||
            redraw();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.events.on("flows:change", function(workspace) {
 | 
			
		||||
            if (workspace.id === RED.workspaces.active()) {
 | 
			
		||||
                activeFlowLocked = !!workspace.locked
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-locked",!!workspace.locked);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.statusBar.add({
 | 
			
		||||
            id: "view-zoom-controls",
 | 
			
		||||
            align: "right",
 | 
			
		||||
@@ -517,9 +495,6 @@ RED.view = (function() {
 | 
			
		||||
        chart.droppable({
 | 
			
		||||
            accept:".red-ui-palette-node",
 | 
			
		||||
            drop: function( event, ui ) {
 | 
			
		||||
                if (activeFlowLocked) {
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                d3.event = event;
 | 
			
		||||
                var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
 | 
			
		||||
                var result = createNode(selected_tool);
 | 
			
		||||
@@ -527,7 +502,9 @@ RED.view = (function() {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var historyEvent = result.historyEvent;
 | 
			
		||||
                var nn = RED.nodes.add(result.node);
 | 
			
		||||
                var nn = result.node;
 | 
			
		||||
 | 
			
		||||
                RED.nodes.add(nn);
 | 
			
		||||
 | 
			
		||||
                var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
 | 
			
		||||
                if (showLabel !== undefined &&  (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
 | 
			
		||||
@@ -609,11 +586,28 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
                var group = $(ui.helper).data("group");
 | 
			
		||||
                if (group) {
 | 
			
		||||
                    var oldX = group.x; 
 | 
			
		||||
                    var oldY = group.y; 
 | 
			
		||||
                    RED.group.addToGroup(group, nn);
 | 
			
		||||
                    var moveEvent = null;
 | 
			
		||||
                    if ((group.x !== oldX) ||
 | 
			
		||||
                        (group.y !== oldY)) {
 | 
			
		||||
                        moveEvent = {
 | 
			
		||||
                            t: "move",
 | 
			
		||||
                            nodes: [{n: group,
 | 
			
		||||
                                     ox: oldX, oy: oldY,
 | 
			
		||||
                                     dx: group.x -oldX,
 | 
			
		||||
                                     dy: group.y -oldY}],
 | 
			
		||||
                            dirty: true
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    historyEvent = {
 | 
			
		||||
                        t: 'multi',
 | 
			
		||||
                        events: [historyEvent],
 | 
			
		||||
 | 
			
		||||
                    };
 | 
			
		||||
                    if (moveEvent) {
 | 
			
		||||
                        historyEvent.events.push(moveEvent)
 | 
			
		||||
                    }
 | 
			
		||||
                    historyEvent.events.push({
 | 
			
		||||
                        t: "addToGroup",
 | 
			
		||||
@@ -654,9 +648,6 @@ RED.view = (function() {
 | 
			
		||||
        RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
 | 
			
		||||
        RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
 | 
			
		||||
        RED.actions.add("core:paste-from-internal-clipboard",function(){
 | 
			
		||||
            if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -665,27 +656,22 @@ RED.view = (function() {
 | 
			
		||||
        RED.events.on("view:selection-changed", function(selection) {
 | 
			
		||||
            var hasSelection = (selection.nodes && selection.nodes.length > 0);
 | 
			
		||||
            var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
 | 
			
		||||
            var hasLinkSelected = selection.links && selection.links.length > 0;
 | 
			
		||||
            var canEdit = !activeFlowLocked && hasSelection
 | 
			
		||||
            var canEditMultiple = !activeFlowLocked && hasMultipleSelection
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-cut", !canEdit);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-copy", !hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-select-connected", !hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-to-back", !canEdit);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-to-front", !canEdit);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-backwards", !canEdit);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-forwards", !canEdit);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-cut",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-copy",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection);
 | 
			
		||||
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-left", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-center", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-right", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-top", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-middle", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-bottom", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally", !canEditMultiple);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally", !canEditMultiple);
 | 
			
		||||
 | 
			
		||||
            RED.menu.setDisabled("menu-item-edit-split-wire-with-links", activeFlowLocked || !hasLinkSelected);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection);
 | 
			
		||||
            RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection);
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:delete-selection",deleteSelection);
 | 
			
		||||
@@ -1075,7 +1061,7 @@ RED.view = (function() {
 | 
			
		||||
                            .attr("class", "nr-ui-view-lasso");
 | 
			
		||||
                        d3.event.preventDefault();
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (d3.event.altKey && !activeFlowLocked) {
 | 
			
		||||
                } else if (d3.event.altKey) {
 | 
			
		||||
                    //Alt [+shift] held - Begin slicing
 | 
			
		||||
                    clearSelection();
 | 
			
		||||
                    mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING;
 | 
			
		||||
@@ -1089,9 +1075,6 @@ RED.view = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showQuickAddDialog(options) {
 | 
			
		||||
        if (activeFlowLocked) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        options = options || {};
 | 
			
		||||
        var point = options.position || lastClickPosition;
 | 
			
		||||
        var spliceLink = options.splice;
 | 
			
		||||
@@ -1274,11 +1257,6 @@ RED.view = (function() {
 | 
			
		||||
                if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
 | 
			
		||||
                    nn.l = showLabel;
 | 
			
		||||
                }
 | 
			
		||||
                if (nn.type === 'junction') {
 | 
			
		||||
                    nn = RED.nodes.addJunction(nn);
 | 
			
		||||
                } else {
 | 
			
		||||
                    nn = RED.nodes.add(nn);
 | 
			
		||||
                }
 | 
			
		||||
                if (quickAddLink) {
 | 
			
		||||
                    var drag_line = quickAddLink;
 | 
			
		||||
                    var src = null,dst,src_port;
 | 
			
		||||
@@ -1381,23 +1359,43 @@ RED.view = (function() {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (nn.type === 'junction') {
 | 
			
		||||
                    RED.nodes.addJunction(nn);
 | 
			
		||||
                } else {
 | 
			
		||||
                    RED.nodes.add(nn);
 | 
			
		||||
                }
 | 
			
		||||
                RED.editor.validateNode(nn);
 | 
			
		||||
 | 
			
		||||
                if (targetGroup) {
 | 
			
		||||
                    var oldX = targetGroup.x; 
 | 
			
		||||
                    var oldY = targetGroup.y; 
 | 
			
		||||
                    RED.group.addToGroup(targetGroup, nn);
 | 
			
		||||
                    var moveEvent = null;
 | 
			
		||||
                    if ((targetGroup.x !== oldX) ||
 | 
			
		||||
                        (targetGroup.y !== oldY)) {
 | 
			
		||||
                        moveEvent = {
 | 
			
		||||
                            t: "move",
 | 
			
		||||
                            nodes: [{n: targetGroup,
 | 
			
		||||
                                     ox: oldX, oy: oldY,
 | 
			
		||||
                                     dx: targetGroup.x -oldX,
 | 
			
		||||
                                     dy: targetGroup.y -oldY}],
 | 
			
		||||
                            dirty: true
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    if (historyEvent.t !== "multi") {
 | 
			
		||||
                        historyEvent = {
 | 
			
		||||
                            t:'multi',
 | 
			
		||||
                            events: [historyEvent]
 | 
			
		||||
                        }
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    historyEvent.events.push({
 | 
			
		||||
                        t: "addToGroup",
 | 
			
		||||
                        group: targetGroup,
 | 
			
		||||
                        nodes: nn
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
                    });
 | 
			
		||||
                    if (moveEvent) {
 | 
			
		||||
                        historyEvent.events.push(moveEvent);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (spliceLink) {
 | 
			
		||||
@@ -1639,18 +1637,16 @@ RED.view = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]);
 | 
			
		||||
            if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) {
 | 
			
		||||
                mouse_mode = RED.state.MOVING_ACTIVE;
 | 
			
		||||
                clickElapsed = 0;
 | 
			
		||||
                if (!activeFlowLocked) {
 | 
			
		||||
                    mouse_mode = RED.state.MOVING_ACTIVE;
 | 
			
		||||
                    spliceActive = false;
 | 
			
		||||
                    if (movingSet.length() === 1) {
 | 
			
		||||
                        node = movingSet.get(0);
 | 
			
		||||
                        spliceActive = node.n.hasOwnProperty("_def") &&
 | 
			
		||||
                                       ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
 | 
			
		||||
                                       ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
 | 
			
		||||
                                       RED.nodes.filterLinks({ source: node.n }).length === 0 &&
 | 
			
		||||
                                       RED.nodes.filterLinks({ target: node.n }).length === 0;
 | 
			
		||||
                    }
 | 
			
		||||
                spliceActive = false;
 | 
			
		||||
                if (movingSet.length() === 1) {
 | 
			
		||||
                    node = movingSet.get(0);
 | 
			
		||||
                    spliceActive = node.n.hasOwnProperty("_def") &&
 | 
			
		||||
                                   ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
 | 
			
		||||
                                   ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
 | 
			
		||||
                                   RED.nodes.filterLinks({ source: node.n }).length === 0 &&
 | 
			
		||||
                                   RED.nodes.filterLinks({ target: node.n }).length === 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
 | 
			
		||||
@@ -1735,6 +1731,7 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
            // Check link splice or group-add
 | 
			
		||||
            if (movingSet.length() === 1 && movingSet.get(0).n.type !== "group") {
 | 
			
		||||
            //}{//NIS
 | 
			
		||||
                node = movingSet.get(0);
 | 
			
		||||
                if (spliceActive) {
 | 
			
		||||
                    if (!spliceTimer) {
 | 
			
		||||
@@ -2094,11 +2091,25 @@ RED.view = (function() {
 | 
			
		||||
        if (mouse_mode == RED.state.MOVING_ACTIVE) {
 | 
			
		||||
            if (movingSet.length() > 0) {
 | 
			
		||||
                var addedToGroup = null;
 | 
			
		||||
                var moveEvent = null;
 | 
			
		||||
                if (activeHoverGroup) {
 | 
			
		||||
                    var oldX = activeHoverGroup.x; 
 | 
			
		||||
                    var oldY = activeHoverGroup.y; 
 | 
			
		||||
                    for (var j=0;j<movingSet.length();j++) {
 | 
			
		||||
                        var n = movingSet.get(j);
 | 
			
		||||
                        RED.group.addToGroup(activeHoverGroup,n.n);
 | 
			
		||||
                    }
 | 
			
		||||
                    if ((activeHoverGroup.x !== oldX) ||
 | 
			
		||||
                        (activeHoverGroup.y !== oldY)) {
 | 
			
		||||
                        moveEvent = {
 | 
			
		||||
                            t: "move",
 | 
			
		||||
                            nodes: [{n: activeHoverGroup,
 | 
			
		||||
                                     ox: oldX, oy: oldY,
 | 
			
		||||
                                     dx: activeHoverGroup.x -oldX,
 | 
			
		||||
                                     dy: activeHoverGroup.y -oldY}],
 | 
			
		||||
                            dirty: true
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    addedToGroup = activeHoverGroup;
 | 
			
		||||
 | 
			
		||||
                    activeHoverGroup.hovered = false;
 | 
			
		||||
@@ -2144,6 +2155,12 @@ RED.view = (function() {
 | 
			
		||||
                        historyEvent.addToGroup = addedToGroup;
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.nodes.dirty(true);
 | 
			
		||||
                    if (moveEvent) {
 | 
			
		||||
                        historyEvent = {
 | 
			
		||||
                            t: "multi",
 | 
			
		||||
                            events: [moveEvent, historyEvent]
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    RED.history.push(historyEvent);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -2495,7 +2512,6 @@ RED.view = (function() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function editSelection() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (movingSet.length() > 0) {
 | 
			
		||||
            var node = movingSet.get(0).n;
 | 
			
		||||
            if (node.type === "subflow") {
 | 
			
		||||
@@ -2511,9 +2527,6 @@ RED.view = (function() {
 | 
			
		||||
        if (mouse_mode === RED.state.SELECTING_NODE) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (activeFlowLocked) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        if (portLabelHover) {
 | 
			
		||||
            portLabelHover.remove();
 | 
			
		||||
            portLabelHover = null;
 | 
			
		||||
@@ -2829,7 +2842,6 @@ RED.view = (function() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function detachSelectedNodes() {
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        var selection = RED.view.selection();
 | 
			
		||||
        if (selection.nodes) {
 | 
			
		||||
            const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
 | 
			
		||||
@@ -2971,7 +2983,7 @@ RED.view = (function() {
 | 
			
		||||
        mousedown_node = d;
 | 
			
		||||
        mousedown_port_type = portType;
 | 
			
		||||
        mousedown_port_index = portIndex || 0;
 | 
			
		||||
        if (mouse_mode !== RED.state.QUICK_JOINING && !activeFlowLocked) {
 | 
			
		||||
        if (mouse_mode !== RED.state.QUICK_JOINING) {
 | 
			
		||||
            mouse_mode = RED.state.JOINING;
 | 
			
		||||
            document.body.style.cursor = "crosshair";
 | 
			
		||||
            if (evt.ctrlKey || evt.metaKey) {
 | 
			
		||||
@@ -3411,11 +3423,6 @@ RED.view = (function() {
 | 
			
		||||
        }
 | 
			
		||||
        if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
 | 
			
		||||
            mouse_mode = RED.state.DEFAULT;
 | 
			
		||||
            if (RED.workspaces.isActiveLocked()) {
 | 
			
		||||
                clickElapsed = 0;
 | 
			
		||||
                d3.event.stopPropagation();
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            // Avoid dbl click causing text selection.
 | 
			
		||||
            d3.event.preventDefault()
 | 
			
		||||
            document.getSelection().removeAllRanges()
 | 
			
		||||
@@ -3513,11 +3520,25 @@ RED.view = (function() {
 | 
			
		||||
                updateActiveNodes();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var moveEvent = null;
 | 
			
		||||
            if (activeHoverGroup) {
 | 
			
		||||
                var oldX = activeHoverGroup.x; 
 | 
			
		||||
                var oldY = activeHoverGroup.y; 
 | 
			
		||||
                for (var j=0;j<movingSet.length();j++) {
 | 
			
		||||
                    var n = movingSet.get(j);
 | 
			
		||||
                    RED.group.addToGroup(activeHoverGroup,n.n);
 | 
			
		||||
                }
 | 
			
		||||
                if ((activeHoverGroup.x !== oldX) ||
 | 
			
		||||
                    (activeHoverGroup.y !== oldY)) {
 | 
			
		||||
                    moveEvent = {
 | 
			
		||||
                        t: "move",
 | 
			
		||||
                        nodes: [{n: activeHoverGroup,
 | 
			
		||||
                                 ox: oldX, oy: oldY,
 | 
			
		||||
                                 dx: activeHoverGroup.x -oldX,
 | 
			
		||||
                                 dy: activeHoverGroup.y -oldY}],
 | 
			
		||||
                        dirty: true
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                historyEvent.addedToGroup = activeHoverGroup;
 | 
			
		||||
 | 
			
		||||
                activeHoverGroup.hovered = false;
 | 
			
		||||
@@ -3526,7 +3547,6 @@ RED.view = (function() {
 | 
			
		||||
                activeGroup.selected = true;
 | 
			
		||||
                activeHoverGroup = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (mouse_mode == RED.state.DETACHED_DRAGGING) {
 | 
			
		||||
                var ns = [];
 | 
			
		||||
                for (var j=0;j<movingSet.length();j++) {
 | 
			
		||||
@@ -3537,7 +3557,15 @@ RED.view = (function() {
 | 
			
		||||
                        n.n.moved = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                RED.history.replace({t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty})
 | 
			
		||||
                var event = {t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty};
 | 
			
		||||
                if (moveEvent) {
 | 
			
		||||
                    event.events.push(moveEvent);
 | 
			
		||||
                }
 | 
			
		||||
                RED.history.replace(event)
 | 
			
		||||
            }
 | 
			
		||||
            else if(moveEvent) {
 | 
			
		||||
                var event = {t:"multi", events:[historyEvent, moveEvent], dirty: true};
 | 
			
		||||
                RED.history.replace(event);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            updateSelection();
 | 
			
		||||
@@ -3742,6 +3770,7 @@ RED.view = (function() {
 | 
			
		||||
            }
 | 
			
		||||
            // selectedLinks.clear();
 | 
			
		||||
            if (d3.event.button != 2) {
 | 
			
		||||
                mouse_mode = RED.state.MOVING;
 | 
			
		||||
                var mouse = d3.touches(this)[0]||d3.mouse(this);
 | 
			
		||||
                mouse[0] += d.x-d.w/2;
 | 
			
		||||
                mouse[1] += d.y-d.h/2;
 | 
			
		||||
@@ -3934,7 +3963,6 @@ RED.view = (function() {
 | 
			
		||||
        if (RED.view.DEBUG) {
 | 
			
		||||
            console.warn("groupMouseUp", { mouse_mode, event: d3.event });
 | 
			
		||||
        }
 | 
			
		||||
        if (RED.workspaces.isActiveLocked()) { return }
 | 
			
		||||
        if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
 | 
			
		||||
            mouse_mode = RED.state.DEFAULT;
 | 
			
		||||
            RED.editor.editGroup(g);
 | 
			
		||||
@@ -4105,7 +4133,7 @@ RED.view = (function() {
 | 
			
		||||
    function isButtonEnabled(d) {
 | 
			
		||||
        var buttonEnabled = true;
 | 
			
		||||
        var ws = RED.nodes.workspace(RED.workspaces.active());
 | 
			
		||||
        if (ws && !ws.disabled && !d.d && !ws.locked) {
 | 
			
		||||
        if (ws && !ws.disabled && !d.d) {
 | 
			
		||||
            if (d._def.button.hasOwnProperty('enabled')) {
 | 
			
		||||
                if (typeof d._def.button.enabled === "function") {
 | 
			
		||||
                    buttonEnabled = d._def.button.enabled.call(d);
 | 
			
		||||
@@ -4128,7 +4156,7 @@ RED.view = (function() {
 | 
			
		||||
        }
 | 
			
		||||
        var activeWorkspace = RED.workspaces.active();
 | 
			
		||||
        var ws = RED.nodes.workspace(activeWorkspace);
 | 
			
		||||
        if (ws && !ws.disabled && !d.d && !ws.locked) {
 | 
			
		||||
        if (ws && !ws.disabled && !d.d) {
 | 
			
		||||
            if (d._def.button.toggle) {
 | 
			
		||||
                d[d._def.button.toggle] = !d[d._def.button.toggle];
 | 
			
		||||
                d.dirty = true;
 | 
			
		||||
@@ -4143,7 +4171,7 @@ RED.view = (function() {
 | 
			
		||||
            if (d.dirty) {
 | 
			
		||||
                redraw();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (!ws || !ws.locked){
 | 
			
		||||
        } else {
 | 
			
		||||
            if (activeSubflow) {
 | 
			
		||||
                RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning");
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -4158,15 +4186,14 @@ RED.view = (function() {
 | 
			
		||||
    function showTouchMenu(obj,pos) {
 | 
			
		||||
        var mdn = mousedown_node;
 | 
			
		||||
        var options = [];
 | 
			
		||||
        const isActiveLocked = RED.workspaces.isActiveLocked()
 | 
			
		||||
        options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
 | 
			
		||||
        options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
 | 
			
		||||
        options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}});
 | 
			
		||||
        options.push({name:"paste",disabled:(isActiveLocked || clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
 | 
			
		||||
        options.push({name:"edit",disabled:(isActiveLocked || movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
 | 
			
		||||
        options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
 | 
			
		||||
        options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
 | 
			
		||||
        options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
 | 
			
		||||
        options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
 | 
			
		||||
        options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
 | 
			
		||||
        options.push({name:"select",onselect:function() {selectAll();}});
 | 
			
		||||
        options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
 | 
			
		||||
        options.push({name:"add",disabled:isActiveLocked, onselect:function() {
 | 
			
		||||
        options.push({name:"add",onselect:function() {
 | 
			
		||||
            chartPos = chart.offset();
 | 
			
		||||
            showQuickAddDialog({
 | 
			
		||||
                position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()],
 | 
			
		||||
@@ -5657,24 +5684,7 @@ RED.view = (function() {
 | 
			
		||||
            if (activeSubflow) {
 | 
			
		||||
                activeSubflowChanged = activeSubflow.changed;
 | 
			
		||||
            }
 | 
			
		||||
            var filteredNodesToImport = nodesToImport;
 | 
			
		||||
            var globalConfig = null;
 | 
			
		||||
            var gconf = null;
 | 
			
		||||
 | 
			
		||||
            RED.nodes.eachConfig(function (conf) {
 | 
			
		||||
                if (conf.type === "global-config") {
 | 
			
		||||
                    gconf = conf;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (gconf) {
 | 
			
		||||
                filteredNodesToImport = nodesToImport.filter(function (n) {
 | 
			
		||||
                    return (n.type !== "global-config");
 | 
			
		||||
                });
 | 
			
		||||
                globalConfig = nodesToImport.find(function (n) {
 | 
			
		||||
                    return (n.type === "global-config");
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            var result = RED.nodes.import(filteredNodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
 | 
			
		||||
            var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
 | 
			
		||||
            if (result) {
 | 
			
		||||
                var new_nodes = result.nodes;
 | 
			
		||||
                var new_links = result.links;
 | 
			
		||||
@@ -5806,50 +5816,6 @@ RED.view = (function() {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (globalConfig) {
 | 
			
		||||
                    // merge global env to existing global-config
 | 
			
		||||
                    var env0 = gconf.env;
 | 
			
		||||
                    var env1 = globalConfig.env;
 | 
			
		||||
                    var newEnv = Array.from(env0);
 | 
			
		||||
                    var changed = false;
 | 
			
		||||
 | 
			
		||||
                    env1.forEach(function (item1) {
 | 
			
		||||
                        var index = newEnv.findIndex(function (item0) {
 | 
			
		||||
                            return (item0.name === item1.name);
 | 
			
		||||
                        });
 | 
			
		||||
                        if (index >= 0) {
 | 
			
		||||
                            var item0 = newEnv[index];
 | 
			
		||||
                            if ((item0.type !== item1.type) ||
 | 
			
		||||
                                (item0.value !== item1.value)) {
 | 
			
		||||
                                newEnv[index] = item1;
 | 
			
		||||
                                changed = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            newEnv.push(item1);
 | 
			
		||||
                            changed = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    if(changed) {
 | 
			
		||||
                        gconf.env = newEnv;
 | 
			
		||||
                        var replaceEvent = {
 | 
			
		||||
                            t: "edit",
 | 
			
		||||
                            node: gconf,
 | 
			
		||||
                            changed: true,
 | 
			
		||||
                            changes: {
 | 
			
		||||
                                env: env0
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                        historyEvent = {
 | 
			
		||||
                            t:"multi",
 | 
			
		||||
                            events: [
 | 
			
		||||
                                replaceEvent,
 | 
			
		||||
                                historyEvent,
 | 
			
		||||
                            ]
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                RED.history.push(historyEvent);
 | 
			
		||||
 | 
			
		||||
                updateActiveNodes();
 | 
			
		||||
@@ -5925,9 +5891,6 @@ RED.view = (function() {
 | 
			
		||||
        if (mouse_mode === RED.state.SELECTING_NODE) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (activeFlowLocked) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var workspaceSelection = RED.workspaces.selection();
 | 
			
		||||
        var changed = false;
 | 
			
		||||
        if (workspaceSelection.length > 0) {
 | 
			
		||||
@@ -6255,9 +6218,7 @@ RED.view = (function() {
 | 
			
		||||
                    if (node.z && (node.type === "group" || node._def.category !== 'config')) {
 | 
			
		||||
                        node.dirty = true;
 | 
			
		||||
                        RED.workspaces.show(node.z);
 | 
			
		||||
                        if (node.type === "group" && !node.w && !node.h) {
 | 
			
		||||
                            _redraw();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor];
 | 
			
		||||
                        var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
 | 
			
		||||
                        var cx = node.x;
 | 
			
		||||
 
 | 
			
		||||
@@ -58,9 +58,6 @@ RED.workspaces = (function() {
 | 
			
		||||
            if (!ws.closeable) {
 | 
			
		||||
                ws.hideable = true;
 | 
			
		||||
            }
 | 
			
		||||
            if (!ws.hasOwnProperty('locked')) {
 | 
			
		||||
                ws.locked = false
 | 
			
		||||
            }
 | 
			
		||||
            workspace_tabs.addTab(ws,targetIndex);
 | 
			
		||||
 | 
			
		||||
            var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
@@ -78,7 +75,6 @@ RED.workspaces = (function() {
 | 
			
		||||
                type: "tab",
 | 
			
		||||
                id: tabId,
 | 
			
		||||
                disabled: false,
 | 
			
		||||
                locked: false,
 | 
			
		||||
                info: "",
 | 
			
		||||
                label: RED._('workspace.defaultName',{number:workspaceIndex}),
 | 
			
		||||
                env: [],
 | 
			
		||||
@@ -103,9 +99,6 @@ RED.workspaces = (function() {
 | 
			
		||||
        if (workspaceTabCount === 1) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (ws.locked) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        var workspaceOrder = RED.nodes.getWorkspaceOrder();
 | 
			
		||||
        ws._index = workspaceOrder.indexOf(ws.id);
 | 
			
		||||
        removeWorkspace(ws);
 | 
			
		||||
@@ -126,206 +119,13 @@ RED.workspaces = (function() {
 | 
			
		||||
                RED.editor.editSubflow(subflow);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (!workspace.locked) {
 | 
			
		||||
                RED.editor.editFlow(workspace);
 | 
			
		||||
            }
 | 
			
		||||
            RED.editor.editFlow(workspace);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    var workspace_tabs;
 | 
			
		||||
    var workspaceTabCount = 0;
 | 
			
		||||
 | 
			
		||||
    function getMenuItems(isMenuButton, tab) {
 | 
			
		||||
        let hiddenFlows = new Set()
 | 
			
		||||
        for (let i = 0; i < hideStack.length; i++) {
 | 
			
		||||
            let ids = hideStack[i]
 | 
			
		||||
            if (!Array.isArray(ids)) {
 | 
			
		||||
                ids = [ids]
 | 
			
		||||
            }
 | 
			
		||||
            ids.forEach(id => {
 | 
			
		||||
                if (RED.nodes.workspace(id)) {
 | 
			
		||||
                    hiddenFlows.add(id)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        const hiddenflowCount = hiddenFlows.size;
 | 
			
		||||
        let activeWorkspace = tab || RED.nodes.workspace(RED.workspaces.active()) || RED.nodes.subflow(RED.workspaces.active())
 | 
			
		||||
        let isFlowDisabled = activeWorkspace ? activeWorkspace.disabled : false
 | 
			
		||||
        const currentTabs = workspace_tabs.listTabs();
 | 
			
		||||
        let flowCount = 0;
 | 
			
		||||
        currentTabs.forEach(tab => {
 | 
			
		||||
            if (RED.nodes.workspace(tab)) {
 | 
			
		||||
                flowCount++;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let isCurrentLocked = RED.workspaces.isActiveLocked()
 | 
			
		||||
        if (tab) {
 | 
			
		||||
            isCurrentLocked = tab.locked
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var menuItems = []
 | 
			
		||||
        if (isMenuButton) {
 | 
			
		||||
            menuItems.push({
 | 
			
		||||
                id:"red-ui-tabs-menu-option-search-flows",
 | 
			
		||||
                label: RED._("workspace.listFlows"),
 | 
			
		||||
                onselect: "core:list-flows"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id:"red-ui-tabs-menu-option-search-subflows",
 | 
			
		||||
                label: RED._("workspace.listSubflows"),
 | 
			
		||||
                onselect: "core:list-subflows"
 | 
			
		||||
            },
 | 
			
		||||
            null)
 | 
			
		||||
        }
 | 
			
		||||
        menuItems.push(
 | 
			
		||||
            {
 | 
			
		||||
                id:"red-ui-tabs-menu-option-add-flow",
 | 
			
		||||
                label: RED._("workspace.addFlow"),
 | 
			
		||||
                onselect: "core:add-flow"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        if (isMenuButton || !!tab) {
 | 
			
		||||
            menuItems.push(
 | 
			
		||||
                {
 | 
			
		||||
                    id:"red-ui-tabs-menu-option-add-flow-right",
 | 
			
		||||
                    label: RED._("workspace.addFlowToRight"),
 | 
			
		||||
                    shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        RED.actions.invoke("core:add-flow-to-right", tab)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                null
 | 
			
		||||
            )
 | 
			
		||||
            if (activeWorkspace && activeWorkspace.type === 'tab') {
 | 
			
		||||
                menuItems.push(
 | 
			
		||||
                    isFlowDisabled ? {
 | 
			
		||||
                        label: RED._("workspace.enableFlow"),
 | 
			
		||||
                        shortcut: RED.keyboard.getShortcut("core:enable-flow"),
 | 
			
		||||
                        onselect: function() {
 | 
			
		||||
                            RED.actions.invoke("core:enable-flow", tab?tab.id:undefined)
 | 
			
		||||
                        },
 | 
			
		||||
                        disabled: isCurrentLocked
 | 
			
		||||
                    } : {
 | 
			
		||||
                        label: RED._("workspace.disableFlow"),
 | 
			
		||||
                        shortcut: RED.keyboard.getShortcut("core:disable-flow"),
 | 
			
		||||
                        onselect: function() {
 | 
			
		||||
                            RED.actions.invoke("core:disable-flow", tab?tab.id:undefined)
 | 
			
		||||
                        },
 | 
			
		||||
                        disabled: isCurrentLocked
 | 
			
		||||
                    },
 | 
			
		||||
                    isCurrentLocked? {
 | 
			
		||||
                        label: RED._("workspace.unlockFlow"),
 | 
			
		||||
                        shortcut: RED.keyboard.getShortcut("core:unlock-flow"),
 | 
			
		||||
                        onselect: function() {
 | 
			
		||||
                            RED.actions.invoke('core:unlock-flow', tab?tab.id:undefined)
 | 
			
		||||
                        }
 | 
			
		||||
                    } : {
 | 
			
		||||
                        label: RED._("workspace.lockFlow"),
 | 
			
		||||
                        shortcut: RED.keyboard.getShortcut("core:lock-flow"),
 | 
			
		||||
                        onselect: function() {
 | 
			
		||||
                            RED.actions.invoke('core:lock-flow', tab?tab.id:undefined)
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    null
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            const activeIndex = currentTabs.findIndex(id => (activeWorkspace && (id === activeWorkspace.id)));
 | 
			
		||||
            menuItems.push(
 | 
			
		||||
                {
 | 
			
		||||
                    label: RED._("workspace.moveToStart"),
 | 
			
		||||
                    shortcut: RED.keyboard.getShortcut("core:move-flow-to-start"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        RED.actions.invoke("core:move-flow-to-start", tab?tab.id:undefined)
 | 
			
		||||
                    },
 | 
			
		||||
                    disabled: activeIndex === 0
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    label: RED._("workspace.moveToEnd"),
 | 
			
		||||
                    shortcut: RED.keyboard.getShortcut("core:move-flow-to-end"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        RED.actions.invoke("core:move-flow-to-end", tab?tab.id:undefined)
 | 
			
		||||
                    },
 | 
			
		||||
                    disabled: activeIndex === currentTabs.length - 1
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        menuItems.push(null)
 | 
			
		||||
        if (isMenuButton || !!tab) {
 | 
			
		||||
            menuItems.push(
 | 
			
		||||
                {
 | 
			
		||||
                    id:"red-ui-tabs-menu-option-add-hide-flows",
 | 
			
		||||
                    label: RED._("workspace.hideFlow"),
 | 
			
		||||
                    shortcut: RED.keyboard.getShortcut("core:hide-flow"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        RED.actions.invoke("core:hide-flow", tab)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    id:"red-ui-tabs-menu-option-add-hide-other-flows",
 | 
			
		||||
                    label: RED._("workspace.hideOtherFlows"),
 | 
			
		||||
                    shortcut: RED.keyboard.getShortcut("core:hide-other-flows"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        RED.actions.invoke("core:hide-other-flows", tab)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        menuItems.push(
 | 
			
		||||
            {
 | 
			
		||||
                id:"red-ui-tabs-menu-option-add-hide-all-flows",
 | 
			
		||||
                label: RED._("workspace.hideAllFlows"),
 | 
			
		||||
                onselect: "core:hide-all-flows",
 | 
			
		||||
                disabled: (hiddenflowCount === flowCount)
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id:"red-ui-tabs-menu-option-add-show-all-flows",
 | 
			
		||||
                disabled: hiddenflowCount === 0,
 | 
			
		||||
                label: RED._("workspace.showAllFlows", { count: hiddenflowCount }),
 | 
			
		||||
                onselect: "core:show-all-flows"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id:"red-ui-tabs-menu-option-add-show-last-flow",
 | 
			
		||||
                disabled: hideStack.length === 0,
 | 
			
		||||
                label: RED._("workspace.showLastHiddenFlow"),
 | 
			
		||||
                onselect: "core:show-last-hidden-flow"
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        if (tab) {
 | 
			
		||||
            menuItems.push(
 | 
			
		||||
                null,
 | 
			
		||||
                {
 | 
			
		||||
                    label: RED._("common.label.delete"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        if (tab.type === 'tab') {
 | 
			
		||||
                            RED.workspaces.delete(tab)
 | 
			
		||||
                        } else if (tab.type === 'subflow') {
 | 
			
		||||
                            RED.subflow.delete(tab.id)
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    disabled: isCurrentLocked || (workspaceTabCount === 1)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    label: RED._("menu.label.export"),
 | 
			
		||||
                    shortcut: RED.keyboard.getShortcut("core:show-export-dialog"),
 | 
			
		||||
                    onselect: function() {
 | 
			
		||||
                        RED.workspaces.show(tab.id)
 | 
			
		||||
                        RED.actions.invoke('core:show-export-dialog', null, 'flow')
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        // if (isMenuButton && hiddenflowCount > 0) {
 | 
			
		||||
        //     menuItems.unshift({
 | 
			
		||||
        //         label: RED._("workspace.hiddenFlows",{count: hiddenflowCount}),
 | 
			
		||||
        //         onselect: "core:list-hidden-flows"
 | 
			
		||||
        //     })
 | 
			
		||||
        // }
 | 
			
		||||
        return menuItems;
 | 
			
		||||
    }
 | 
			
		||||
    function createWorkspaceTabs() {
 | 
			
		||||
        workspace_tabs = RED.tabs.create({
 | 
			
		||||
            id: "red-ui-workspace-tabs",
 | 
			
		||||
@@ -337,9 +137,8 @@ RED.workspaces = (function() {
 | 
			
		||||
                    $("#red-ui-workspace-chart").show();
 | 
			
		||||
                    activeWorkspace = tab.id;
 | 
			
		||||
                    window.location.hash = 'flow/'+tab.id;
 | 
			
		||||
                    $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled);
 | 
			
		||||
                    $("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!tab.disabled);
 | 
			
		||||
                    } else {
 | 
			
		||||
                    $("#red-ui-workspace-chart").hide();
 | 
			
		||||
                    activeWorkspace = 0;
 | 
			
		||||
                    window.location.hash = '';
 | 
			
		||||
@@ -370,12 +169,6 @@ RED.workspaces = (function() {
 | 
			
		||||
                if (tab.disabled) {
 | 
			
		||||
                    $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-disabled');
 | 
			
		||||
                }
 | 
			
		||||
                $('<span class="red-ui-workspace-locked-icon"><i class="fa fa-lock"></i> </span>').prependTo("#red-ui-tab-"+(tab.id.replace(".","-"))+" .red-ui-tab-label");
 | 
			
		||||
                if (tab.locked) {
 | 
			
		||||
                    $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
 | 
			
		||||
                if (workspaceTabCount === 1) {
 | 
			
		||||
                    showWorkspace();
 | 
			
		||||
@@ -396,19 +189,13 @@ RED.workspaces = (function() {
 | 
			
		||||
                RED.history.push({
 | 
			
		||||
                    t:'reorder',
 | 
			
		||||
                    workspaces: {
 | 
			
		||||
                        from: oldOrder,
 | 
			
		||||
                        to: newOrder
 | 
			
		||||
                        from:oldOrder,
 | 
			
		||||
                        to:newOrder
 | 
			
		||||
                    },
 | 
			
		||||
                    dirty:RED.nodes.dirty()
 | 
			
		||||
                });
 | 
			
		||||
                // Only mark flows dirty if flow-order has changed (excluding subflows)
 | 
			
		||||
                const filteredOldOrder = oldOrder.filter(id => !!RED.nodes.workspace(id))
 | 
			
		||||
                const filteredNewOrder = newOrder.filter(id => !!RED.nodes.workspace(id))
 | 
			
		||||
 | 
			
		||||
                if (JSON.stringify(filteredOldOrder) !== JSON.stringify(filteredNewOrder)) {
 | 
			
		||||
                    RED.nodes.dirty(true);
 | 
			
		||||
                    setWorkspaceOrder(newOrder);
 | 
			
		||||
                }
 | 
			
		||||
                RED.nodes.dirty(true);
 | 
			
		||||
                setWorkspaceOrder(newOrder);
 | 
			
		||||
            },
 | 
			
		||||
            onselect: function(selectedTabs) {
 | 
			
		||||
                RED.view.select(false)
 | 
			
		||||
@@ -427,12 +214,12 @@ RED.workspaces = (function() {
 | 
			
		||||
            },
 | 
			
		||||
            onhide: function(tab) {
 | 
			
		||||
                hideStack.push(tab.id);
 | 
			
		||||
                if (tab.type === "tab") {
 | 
			
		||||
                    var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
                    hiddenTabs[tab.id] = true;
 | 
			
		||||
                    RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
 | 
			
		||||
                    RED.events.emit("workspace:hide",{workspace: tab.id})
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
 | 
			
		||||
                hiddenTabs[tab.id] = true;
 | 
			
		||||
                RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
 | 
			
		||||
 | 
			
		||||
                RED.events.emit("workspace:hide",{workspace: tab.id})
 | 
			
		||||
            },
 | 
			
		||||
            onshow: function(tab) {
 | 
			
		||||
                removeFromHideStack(tab.id);
 | 
			
		||||
@@ -447,8 +234,77 @@ RED.workspaces = (function() {
 | 
			
		||||
            scrollable: true,
 | 
			
		||||
            addButton: "core:add-flow",
 | 
			
		||||
            addButtonCaption: RED._("workspace.addFlow"),
 | 
			
		||||
            menu: function() { return getMenuItems(true) },
 | 
			
		||||
            contextmenu: function(tab) { return getMenuItems(false, tab) }
 | 
			
		||||
            menu: function() {
 | 
			
		||||
                var menuItems = [
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-search-flows",
 | 
			
		||||
                        label: RED._("workspace.listFlows"),
 | 
			
		||||
                        onselect: "core:list-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-search-subflows",
 | 
			
		||||
                        label: RED._("workspace.listSubflows"),
 | 
			
		||||
                        onselect: "core:list-subflows"
 | 
			
		||||
                    },
 | 
			
		||||
                    null,
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-flow",
 | 
			
		||||
                        label: RED._("workspace.addFlow"),
 | 
			
		||||
                        onselect: "core:add-flow"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-flow-right",
 | 
			
		||||
                        label: RED._("workspace.addFlowToRight"),
 | 
			
		||||
                        onselect: "core:add-flow-to-right"
 | 
			
		||||
                    },
 | 
			
		||||
                    null,
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-hide-flows",
 | 
			
		||||
                        label: RED._("workspace.hideFlow"),
 | 
			
		||||
                        onselect: "core:hide-flow"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-hide-other-flows",
 | 
			
		||||
                        label: RED._("workspace.hideOtherFlows"),
 | 
			
		||||
                        onselect: "core:hide-other-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-show-all-flows",
 | 
			
		||||
                        label: RED._("workspace.showAllFlows"),
 | 
			
		||||
                        onselect: "core:show-all-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-hide-all-flows",
 | 
			
		||||
                        label: RED._("workspace.hideAllFlows"),
 | 
			
		||||
                        onselect: "core:hide-all-flows"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        id:"red-ui-tabs-menu-option-add-show-last-flow",
 | 
			
		||||
                        label: RED._("workspace.showLastHiddenFlow"),
 | 
			
		||||
                        onselect: "core:show-last-hidden-flow"
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
                let hiddenFlows = new Set()
 | 
			
		||||
                for (let i = 0; i < hideStack.length; i++) {
 | 
			
		||||
                    let ids = hideStack[i]
 | 
			
		||||
                    if (!Array.isArray(ids)) {
 | 
			
		||||
                        ids = [ids]
 | 
			
		||||
                    }
 | 
			
		||||
                    ids.forEach(id => {
 | 
			
		||||
                        if (RED.nodes.workspace(id)) {
 | 
			
		||||
                            hiddenFlows.add(id)
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                const flowCount = hiddenFlows.size;
 | 
			
		||||
                if (flowCount > 0) {
 | 
			
		||||
                    menuItems.unshift({
 | 
			
		||||
                        label: RED._("workspace.hiddenFlows",{count: flowCount}),
 | 
			
		||||
                        onselect: "core:list-hidden-flows"
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
                return menuItems;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        workspaceTabCount = 0;
 | 
			
		||||
    }
 | 
			
		||||
@@ -499,33 +355,16 @@ RED.workspaces = (function() {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
 | 
			
		||||
        RED.actions.add("core:add-flow-to-right",function(workspace) {
 | 
			
		||||
            let index
 | 
			
		||||
            if (workspace) {
 | 
			
		||||
                index = workspace_tabs.getTabIndex(workspace.id)+1
 | 
			
		||||
            } else {
 | 
			
		||||
                index = workspace_tabs.activeIndex()+1
 | 
			
		||||
            }
 | 
			
		||||
            addWorkspace(undefined,undefined,index)
 | 
			
		||||
        });
 | 
			
		||||
        RED.actions.add("core:add-flow-to-right",function(opts) { addWorkspace(undefined,undefined,workspace_tabs.activeIndex()+1)});
 | 
			
		||||
        RED.actions.add("core:edit-flow",editWorkspace);
 | 
			
		||||
        RED.actions.add("core:remove-flow",removeWorkspace);
 | 
			
		||||
        RED.actions.add("core:enable-flow",enableWorkspace);
 | 
			
		||||
        RED.actions.add("core:disable-flow",disableWorkspace);
 | 
			
		||||
        RED.actions.add("core:lock-flow",lockWorkspace);
 | 
			
		||||
        RED.actions.add("core:unlock-flow",unlockWorkspace);
 | 
			
		||||
        RED.actions.add("core:move-flow-to-start", function(id) { moveWorkspace(id, 'start') });
 | 
			
		||||
        RED.actions.add("core:move-flow-to-end", function(id) { moveWorkspace(id, 'end') });
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:hide-flow", function(workspace) {
 | 
			
		||||
            let selection
 | 
			
		||||
            if (workspace) {
 | 
			
		||||
                selection = [workspace]
 | 
			
		||||
            } else {
 | 
			
		||||
                selection = workspace_tabs.selection();
 | 
			
		||||
                if (selection.length === 0) {
 | 
			
		||||
                    selection = [{id:activeWorkspace}]
 | 
			
		||||
                }
 | 
			
		||||
        RED.actions.add("core:hide-flow", function() {
 | 
			
		||||
            var selection = workspace_tabs.selection();
 | 
			
		||||
            if (selection.length === 0) {
 | 
			
		||||
                selection = [{id:activeWorkspace}]
 | 
			
		||||
            }
 | 
			
		||||
            var hiddenTabs = [];
 | 
			
		||||
            selection.forEach(function(ws) {
 | 
			
		||||
@@ -539,15 +378,10 @@ RED.workspaces = (function() {
 | 
			
		||||
            workspace_tabs.clearSelection();
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        RED.actions.add("core:hide-other-flows", function(workspace) {
 | 
			
		||||
            let selection
 | 
			
		||||
            if (workspace) {
 | 
			
		||||
                selection = [workspace]
 | 
			
		||||
            } else {
 | 
			
		||||
                selection = workspace_tabs.selection();
 | 
			
		||||
                if (selection.length === 0) {
 | 
			
		||||
                    selection = [{id:activeWorkspace}]
 | 
			
		||||
                }
 | 
			
		||||
        RED.actions.add("core:hide-other-flows", function() {
 | 
			
		||||
            var selection = workspace_tabs.selection();
 | 
			
		||||
            if (selection.length === 0) {
 | 
			
		||||
                selection = [{id:activeWorkspace}]
 | 
			
		||||
            }
 | 
			
		||||
            var selected = new Set(selection.map(function(ws) { return ws.id }))
 | 
			
		||||
 | 
			
		||||
@@ -652,7 +486,7 @@ RED.workspaces = (function() {
 | 
			
		||||
    }
 | 
			
		||||
    function setWorkspaceState(id,disabled) {
 | 
			
		||||
        var workspace = RED.nodes.workspace(id||activeWorkspace);
 | 
			
		||||
        if (!workspace || workspace.locked) {
 | 
			
		||||
        if (!workspace) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (workspace.disabled !== disabled) {
 | 
			
		||||
@@ -687,58 +521,11 @@ RED.workspaces = (function() {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function lockWorkspace(id) {
 | 
			
		||||
        setWorkspaceLockState(id,true);
 | 
			
		||||
    }
 | 
			
		||||
    function unlockWorkspace(id) {
 | 
			
		||||
        setWorkspaceLockState(id,false);
 | 
			
		||||
    }
 | 
			
		||||
    function setWorkspaceLockState(id,locked) {
 | 
			
		||||
        var workspace = RED.nodes.workspace(id||activeWorkspace);
 | 
			
		||||
        if (!workspace) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (workspace.locked !== locked) {
 | 
			
		||||
            var changes = { locked: workspace.locked };
 | 
			
		||||
            workspace.locked = locked;
 | 
			
		||||
            $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!workspace.locked);
 | 
			
		||||
            if (!id || (id === activeWorkspace)) {
 | 
			
		||||
                $("#red-ui-workspace").toggleClass("red-ui-workspace-locked",!!workspace.locked);
 | 
			
		||||
            }
 | 
			
		||||
            var historyEvent = {
 | 
			
		||||
                t: "edit",
 | 
			
		||||
                changes:changes,
 | 
			
		||||
                node: workspace,
 | 
			
		||||
                dirty: RED.nodes.dirty()
 | 
			
		||||
            }
 | 
			
		||||
            workspace.changed = true;
 | 
			
		||||
            RED.history.push(historyEvent);
 | 
			
		||||
            RED.events.emit("flows:change",workspace);
 | 
			
		||||
            RED.nodes.dirty(true);
 | 
			
		||||
            // RED.sidebar.config.refresh();
 | 
			
		||||
            // var selection = RED.view.selection();
 | 
			
		||||
            // if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) {
 | 
			
		||||
            //     RED.sidebar.info.refresh(workspace);
 | 
			
		||||
            // }
 | 
			
		||||
            // if (changes.hasOwnProperty('disabled')) {
 | 
			
		||||
            //     RED.nodes.eachNode(function(n) {
 | 
			
		||||
            //         if (n.z === workspace.id) {
 | 
			
		||||
            //             n.dirty = true;
 | 
			
		||||
            //         }
 | 
			
		||||
            //     });
 | 
			
		||||
            //     RED.view.redraw();
 | 
			
		||||
            // }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removeWorkspace(ws) {
 | 
			
		||||
        if (!ws) {
 | 
			
		||||
            ws = RED.nodes.workspace(activeWorkspace)
 | 
			
		||||
            if (ws && !ws.locked) {
 | 
			
		||||
                deleteWorkspace(RED.nodes.workspace(activeWorkspace));
 | 
			
		||||
            }
 | 
			
		||||
            deleteWorkspace(RED.nodes.workspace(activeWorkspace));
 | 
			
		||||
        } else {
 | 
			
		||||
            if (ws.locked) { return }
 | 
			
		||||
            if (workspace_tabs.contains(ws.id)) {
 | 
			
		||||
                workspace_tabs.removeTab(ws.id);
 | 
			
		||||
            }
 | 
			
		||||
@@ -748,46 +535,16 @@ RED.workspaces = (function() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function moveWorkspace(id, direction) {
 | 
			
		||||
        const workspace = RED.nodes.workspace(id||activeWorkspace) || RED.nodes.subflow(id||activeWorkspace);
 | 
			
		||||
        if (!workspace) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const currentOrder = workspace_tabs.listTabs()
 | 
			
		||||
        const oldOrder = [...currentOrder]
 | 
			
		||||
        const currentIndex = currentOrder.findIndex(id => id === workspace.id)
 | 
			
		||||
        currentOrder.splice(currentIndex, 1)
 | 
			
		||||
        if (direction === 'start') {
 | 
			
		||||
            currentOrder.unshift(workspace.id)
 | 
			
		||||
        } else if (direction === 'end') {
 | 
			
		||||
            currentOrder.push(workspace.id)
 | 
			
		||||
        }
 | 
			
		||||
        const newOrder = setWorkspaceOrder(currentOrder)
 | 
			
		||||
        if (JSON.stringify(newOrder) !== JSON.stringify(oldOrder)) {
 | 
			
		||||
            RED.history.push({
 | 
			
		||||
                t:'reorder',
 | 
			
		||||
                workspaces: {
 | 
			
		||||
                    from:oldOrder,
 | 
			
		||||
                    to:newOrder
 | 
			
		||||
                },
 | 
			
		||||
                dirty:RED.nodes.dirty()
 | 
			
		||||
            });
 | 
			
		||||
            const filteredOldOrder = oldOrder.filter(id => !!RED.nodes.workspace(id))
 | 
			
		||||
            const filteredNewOrder = newOrder.filter(id => !!RED.nodes.workspace(id))
 | 
			
		||||
            if (JSON.stringify(filteredOldOrder) !== JSON.stringify(filteredNewOrder)) {
 | 
			
		||||
                RED.nodes.dirty(true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    function setWorkspaceOrder(order) {
 | 
			
		||||
        var newOrder = order.filter(id => !!RED.nodes.workspace(id))
 | 
			
		||||
        var newOrder = order.filter(function(id) {
 | 
			
		||||
            return RED.nodes.workspace(id) !== undefined;
 | 
			
		||||
        })
 | 
			
		||||
        var currentOrder = RED.nodes.getWorkspaceOrder();
 | 
			
		||||
        if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) {
 | 
			
		||||
            RED.nodes.setWorkspaceOrder(newOrder);
 | 
			
		||||
            RED.events.emit("flows:reorder",newOrder);
 | 
			
		||||
        }
 | 
			
		||||
        workspace_tabs.order(order);
 | 
			
		||||
        return newOrder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function flashTab(tabId) {
 | 
			
		||||
@@ -833,10 +590,6 @@ RED.workspaces = (function() {
 | 
			
		||||
        active: function() {
 | 
			
		||||
            return activeWorkspace
 | 
			
		||||
        },
 | 
			
		||||
        isActiveLocked: function() {
 | 
			
		||||
            var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace)
 | 
			
		||||
            return ws && ws.locked
 | 
			
		||||
        },
 | 
			
		||||
        selection: function() {
 | 
			
		||||
            return workspace_tabs.selection();
 | 
			
		||||
        },
 | 
			
		||||
@@ -893,8 +646,6 @@ RED.workspaces = (function() {
 | 
			
		||||
            workspace_tabs.resize();
 | 
			
		||||
        },
 | 
			
		||||
        enable: enableWorkspace,
 | 
			
		||||
        disable: disableWorkspace,
 | 
			
		||||
        lock: lockWorkspace,
 | 
			
		||||
        unlock: unlockWorkspace
 | 
			
		||||
        disable: disableWorkspace
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 
 | 
			
		||||
@@ -37,27 +37,3 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#red-ui-image-drop-target {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0; bottom: 0;
 | 
			
		||||
    left: 0; right: 0;
 | 
			
		||||
    background: var(--red-ui-dnd-background);
 | 
			
		||||
    display:table;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: none;
 | 
			
		||||
    z-index:100;
 | 
			
		||||
    div {
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
        display: table-cell;
 | 
			
		||||
        vertical-align: middle;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        font-size: 40px;
 | 
			
		||||
        color: var(--red-ui-dnd-color);
 | 
			
		||||
        i {
 | 
			
		||||
            pointer-events: none;
 | 
			
		||||
            font-size: 80px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -68,9 +68,6 @@
 | 
			
		||||
    stroke: var(--red-ui-node-border);
 | 
			
		||||
    cursor: move;
 | 
			
		||||
    stroke-width: 1;
 | 
			
		||||
    .red-ui-workspace-locked & {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
.red-ui-workspace-select-mode {
 | 
			
		||||
    g.red-ui-flow-node.red-ui-flow-node-hovered * {
 | 
			
		||||
@@ -290,11 +287,9 @@ g.red-ui-flow-node-selected {
 | 
			
		||||
    text-anchor:start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#red-ui-workspace:not(.red-ui-workspace-locked) {
 | 
			
		||||
    .red-ui-flow-port-hovered {
 | 
			
		||||
        stroke: var(--red-ui-port-selected-color);
 | 
			
		||||
        fill:  var(--red-ui-port-selected-color);
 | 
			
		||||
    }
 | 
			
		||||
.red-ui-flow-port-hovered {
 | 
			
		||||
    stroke: var(--red-ui-port-selected-color);
 | 
			
		||||
    fill:  var(--red-ui-port-selected-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.red-ui-flow-subflow-port {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@
 | 
			
		||||
    color: var(--red-ui-primary-text-color);
 | 
			
		||||
    border: 1px solid var(--red-ui-notification-border-default);
 | 
			
		||||
    border-left-width: 16px;
 | 
			
		||||
    overflow: scroll;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    max-height: 80vh;
 | 
			
		||||
    .ui-dialog-buttonset {
 | 
			
		||||
        margin-top: 20px;
 | 
			
		||||
 
 | 
			
		||||
@@ -467,9 +467,6 @@ div.red-ui-info-table {
 | 
			
		||||
        .fa-eye {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
        .fa-unlock-alt {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .red-ui-info-outline-item-control-reveal,
 | 
			
		||||
    .red-ui-info-outline-item-control-action {
 | 
			
		||||
@@ -503,25 +500,6 @@ div.red-ui-info-table {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .fa-lock {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    .red-ui-info-outline-item.red-ui-info-outline-item-locked & {
 | 
			
		||||
        .fa-lock {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
        }
 | 
			
		||||
        .fa-unlock-alt {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // If the parent is locked, do not show the display/action buttons when
 | 
			
		||||
    // hovering in the outline
 | 
			
		||||
    .red-ui-info-outline-item-locked .red-ui-info-outline-item & {
 | 
			
		||||
        .red-ui-info-outline-item-control-disable,
 | 
			
		||||
        .red-ui-info-outline-item-control-action {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    button {
 | 
			
		||||
        margin-right: 3px
 | 
			
		||||
    }
 | 
			
		||||
@@ -539,6 +517,8 @@ div.red-ui-info-table {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.red-ui-icons {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: 18px;
 | 
			
		||||
 
 | 
			
		||||
@@ -106,28 +106,6 @@
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.red-ui-workspace-locked-icon {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
.red-ui-workspace-locked {
 | 
			
		||||
    &.red-ui-tab {
 | 
			
		||||
        // border-top-style: dashed;
 | 
			
		||||
        // border-left-style: dashed;
 | 
			
		||||
        // border-right-style: dashed;
 | 
			
		||||
 | 
			
		||||
        // a {
 | 
			
		||||
        //     font-style: italic;
 | 
			
		||||
        //     color: var(--red-ui-tab-text-color-disabled-inactive) !important;
 | 
			
		||||
        // }
 | 
			
		||||
        // &.active a {
 | 
			
		||||
        //     font-weight: normal;
 | 
			
		||||
        //     color: var(--red-ui-tab-text-color-disabled-active) !important;
 | 
			
		||||
        // }
 | 
			
		||||
        .red-ui-workspace-locked-icon {
 | 
			
		||||
            display: inline;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#red-ui-navigator-canvas {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 66 KiB  | 
@@ -1,155 +0,0 @@
 | 
			
		||||
export default {
 | 
			
		||||
    version: "3.0.0",
 | 
			
		||||
    steps: [
 | 
			
		||||
        {
 | 
			
		||||
            titleIcon: "fa fa-map-o",
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Welcome to Node-RED 3.0!",
 | 
			
		||||
                "ja": "Node-RED 3.0へようこそ!"
 | 
			
		||||
            },
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
 | 
			
		||||
                "ja": "<p>本リリースの新機能を見つけてみましょう。</p>"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Context Menu",
 | 
			
		||||
                "ja": "コンテキストメニュー"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/context-menu.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>The editor now has its own context menu when you
 | 
			
		||||
                          right-click in the workspace.</p>
 | 
			
		||||
                          <p>This makes many of the built-in actions much easier
 | 
			
		||||
                          to access.</p>`,
 | 
			
		||||
                "ja": `<p>ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。</p>
 | 
			
		||||
                       <p>これによって多くの組み込み動作を、より簡単に利用できます。</p>`
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Wire Junctions",
 | 
			
		||||
                "ja": "分岐点をワイヤーに追加"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/junction-slice.gif',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>To make it easier to route wires around your flows,
 | 
			
		||||
                             it is now possible to add junction nodes that give
 | 
			
		||||
                             you more control.</p>
 | 
			
		||||
                          <p>Junctions can be added to wires by holding both the Alt key and the Shift key
 | 
			
		||||
                          then click and drag the mouse across the wires.</p>`,
 | 
			
		||||
                "ja": `<p>フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。</p>
 | 
			
		||||
                       <p>Altキーとシフトキーを押しながらマウスをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Wire Junctions",
 | 
			
		||||
                "ja": "分岐点をワイヤーに追加"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/junction-quick-add.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>Junctions can also be added using the quick-add dialog.</p>
 | 
			
		||||
                          <p>The dialog is opened by holding the Ctrl (or Cmd) key when
 | 
			
		||||
                             clicking in the workspace.</p>`,
 | 
			
		||||
                "ja": `<p>クイック追加ダイアログを用いて、分岐点を追加することもできます。</p>
 | 
			
		||||
                       <p>本ダイアログを開くには、Ctrl(またはCmd)キーを押しながら、ワークスペース上でクリックします。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Debug Path Tooltip",
 | 
			
		||||
                "ja": "デバッグパスのツールチップ"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/debug-path-tooltip.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>When hovering over a node name in the Debug sidebar, a
 | 
			
		||||
                             new tooltip shows the full location of the node.</p>
 | 
			
		||||
                          <p>This is useful when working with subflows, making it
 | 
			
		||||
                             much easier to identify exactly which node generated
 | 
			
		||||
                             the message.</p>
 | 
			
		||||
                          <p>Clicking on any item in the list will reveal it in
 | 
			
		||||
                             the workspace.</p>`,
 | 
			
		||||
                "ja": `<p>デバックサイドバー内のノード名の上にマウスカーソルを乗せると、新たにツールチップが表示され、ノードの場所が分かるようになっています。</p>
 | 
			
		||||
                       <p>これは、サブフローを用いる時に役立つ機能であり、メッセージがどのノードから出力されたかを正確に特定することが遥かに簡単になります。</p>
 | 
			
		||||
                       <p>本リスト内の要素をクリックすると、ワークスペース内にその要素が表示されます。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Continuous Search",
 | 
			
		||||
                "ja": "連続した検索"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/continuous-search.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>When searching for things in the editor, a new toolbar in
 | 
			
		||||
                             the workspace provides options to quickly jump between
 | 
			
		||||
                             the search results.</p>`,
 | 
			
		||||
                "ja": `<p>ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "New wiring actions",
 | 
			
		||||
                "ja": "新しいワイヤー操作"
 | 
			
		||||
            },
 | 
			
		||||
            image: "images/split-wire-with-links.gif",
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>A new action has been added that will replace a wire with a pair of connected Link nodes:</p>
 | 
			
		||||
                          <ul>
 | 
			
		||||
                          <li><b><code>Split Wire With Link Nodes</code></b></li>
 | 
			
		||||
                          </ul>
 | 
			
		||||
                          <p>Actions can be accessed from the Action List in the main menu.</p>`,
 | 
			
		||||
                "ja": `<p>ワイヤーを、接続されたLinkノードのペアに置き換える動作が新たに追加されました:</p>
 | 
			
		||||
                       <ul>
 | 
			
		||||
                       <li><b><code>ワイヤーをlinkノードで分割</code></b></li>
 | 
			
		||||
                       </ul>
 | 
			
		||||
                       <p>本アクションは、メインメニュー内の動作一覧から呼び出せます。</p>`,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Default node names",
 | 
			
		||||
                "ja": "標準ノードの名前"
 | 
			
		||||
            },
 | 
			
		||||
            // image: "images/",
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>Some nodes have been updated to generate a unique name when
 | 
			
		||||
                             new instances are added to the workspace. This applies to
 | 
			
		||||
                             <code>Debug</code>, <code>Function</code> and <code>Link</code> nodes.</p>
 | 
			
		||||
                          <p>A new action has also been added to generate default names for the selected
 | 
			
		||||
                             nodes:</p>
 | 
			
		||||
                             <ul>
 | 
			
		||||
                             <li><b><code>Generate Node Names</code></b></li>
 | 
			
		||||
                             </ul><p>Actions can be accessed from the Action List in the main menu.</p>
 | 
			
		||||
                            `,
 | 
			
		||||
                "ja": `<p>一部のノードは、ワークスペース上に新インスタンスとして追加した際に、一意の名前を付けるよう変更されました。この変更は、<code>Debug</code>、<code>Function</code>、<code>Link</code>ノードに適用されています。</p>
 | 
			
		||||
                       <p>選択したノードに対して、標準の名前を生成する動作も新たに追加されました:</p>
 | 
			
		||||
                          <ul>
 | 
			
		||||
                          <li><b><code>ノード名を生成</code></b></li>
 | 
			
		||||
                          </ul><p>本アクションは、メインメニュー内の動作一覧から呼び出せます。</p>
 | 
			
		||||
                         `
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Node Updates",
 | 
			
		||||
                "ja": "ノードの更新"
 | 
			
		||||
            },
 | 
			
		||||
            // image: "images/",
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<ul>
 | 
			
		||||
                            <li>The Debug node can be configured to count messages it receives</li>
 | 
			
		||||
                            <li>The Link Call node can use a message property to dynamically target the link it should call</li>
 | 
			
		||||
                            <li>The HTTP Request node can be preconfigured with HTTP headers</li>
 | 
			
		||||
                          </ul>`,
 | 
			
		||||
                "ja": `<ul>
 | 
			
		||||
                         <li>Debugノードは、受信したメッセージの数をカウントするよう設定できるようになりました。</li>
 | 
			
		||||
                         <li>Link Callノードは、メッセージのプロパティによって、呼び出し対象のlinkを動的に指定できるようになりました。</li>
 | 
			
		||||
                         <li>HTTP Requestノードは、HTTPヘッダを事前設定できるようになりました。</li>
 | 
			
		||||
                       </ul>`
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 66 KiB  | 
| 
		 Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB  | 
| 
		 Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB  | 
| 
		 Before Width: | Height: | Size: 68 KiB  | 
| 
		 Before Width: | Height: | Size: 25 KiB  | 
| 
		 Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB  | 
| 
		 Before Width: | Height: | Size: 5.4 KiB  | 
| 
		 Before Width: | Height: | Size: 189 KiB  | 
| 
		 Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB  | 
@@ -1,97 +1,137 @@
 | 
			
		||||
export default {
 | 
			
		||||
    version: "3.1.0-beta.1",
 | 
			
		||||
    version: "3.0.0",
 | 
			
		||||
    steps: [
 | 
			
		||||
        {
 | 
			
		||||
            titleIcon: "fa fa-map-o",
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Welcome to Node-RED 3.1 Beta 1!",
 | 
			
		||||
                // "ja": "Node-RED 3.1へようこそ!"
 | 
			
		||||
                "en-US": "Welcome to Node-RED 3.0!",
 | 
			
		||||
                "ja": "Node-RED 3.0へようこそ!"
 | 
			
		||||
            },
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": "<p>This is the first beta release for 3.1.0 and we have a few new features to tell you about.</p>",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
 | 
			
		||||
                "ja": "<p>本リリースの新機能を見つけてみましょう。</p>"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Improved Context Menu",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "Context Menu",
 | 
			
		||||
                "ja": "コンテキストメニュー"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/context-menu.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>The editor's context menu has been expanded to make lots more of
 | 
			
		||||
                        the built-in actions available.</p>
 | 
			
		||||
                        <p>Adding nodes, working with groups and plenty
 | 
			
		||||
                        of other useful tools are now just a click away.</p>
 | 
			
		||||
                        <p>The flow tab bar also has its own context menu to make working
 | 
			
		||||
                        with your flows much easier.</p>`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<p>The editor now has its own context menu when you
 | 
			
		||||
                          right-click in the workspace.</p>
 | 
			
		||||
                          <p>This makes many of the built-in actions much easier
 | 
			
		||||
                          to access.</p>`,
 | 
			
		||||
                "ja": `<p>ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。</p>
 | 
			
		||||
                       <p>これによって多くの組み込み動作を、より簡単に利用できます。</p>`
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Hiding Flows",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "Wire Junctions",
 | 
			
		||||
                "ja": "分岐点をワイヤーに追加"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/hiding-flows.png',
 | 
			
		||||
            image: 'images/junction-slice.gif',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>Hiding flows is now done through the flow context menu.</p>
 | 
			
		||||
                          <p>The 'hide' button in previous releases has been removed from the tabs
 | 
			
		||||
                             as they were being clicked accidentally too often.</p>`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<p>To make it easier to route wires around your flows,
 | 
			
		||||
                             it is now possible to add junction nodes that give
 | 
			
		||||
                             you more control.</p>
 | 
			
		||||
                          <p>Junctions can be added to wires by holding both the Alt key and the Shift key
 | 
			
		||||
                          then click and drag the mouse across the wires.</p>`,
 | 
			
		||||
                "ja": `<p>フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。</p>
 | 
			
		||||
                       <p>Altキーとシフトキーを押しながらマウスをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Locking Flows",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "Wire Junctions",
 | 
			
		||||
                "ja": "分岐点をワイヤーに追加"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/locking-flows.png',
 | 
			
		||||
            image: 'images/junction-quick-add.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>Flows can now be locked to prevent accidental changes being made.</p>
 | 
			
		||||
                          <p>When locked you cannot modify the nodes in any way.</p>
 | 
			
		||||
                          <p>The flow context menu provides the options to lock and unlock flows,
 | 
			
		||||
                             as well as in the Info sidebar explorer.</p>`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<p>Junctions can also be added using the quick-add dialog.</p>
 | 
			
		||||
                          <p>The dialog is opened by holding the Ctrl (or Cmd) key when
 | 
			
		||||
                             clicking in the workspace.</p>`,
 | 
			
		||||
                "ja": `<p>クイック追加ダイアログを用いて、分岐点を追加することもできます。</p>
 | 
			
		||||
                       <p>本ダイアログを開くには、Ctrl(またはCmd)キーを押しながら、ワークスペース上でクリックします。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Adding Images to node/flow descriptions",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "Debug Path Tooltip",
 | 
			
		||||
                "ja": "デバッグパスのツールチップ"
 | 
			
		||||
            },
 | 
			
		||||
            // image: 'images/debug-path-tooltip.png',
 | 
			
		||||
            image: 'images/debug-path-tooltip.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>You can now add images to a node's or flows's description.</p>
 | 
			
		||||
                          <p>Simply drag the image into the text editor and it will get added inline.</p>
 | 
			
		||||
                          <p>When the description is shown in the Info sidebar, the image will be displayed.`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<p>When hovering over a node name in the Debug sidebar, a
 | 
			
		||||
                             new tooltip shows the full location of the node.</p>
 | 
			
		||||
                          <p>This is useful when working with subflows, making it
 | 
			
		||||
                             much easier to identify exactly which node generated
 | 
			
		||||
                             the message.</p>
 | 
			
		||||
                          <p>Clicking on any item in the list will reveal it in
 | 
			
		||||
                             the workspace.</p>`,
 | 
			
		||||
                "ja": `<p>デバックサイドバー内のノード名の上にマウスカーソルを乗せると、新たにツールチップが表示され、ノードの場所が分かるようになっています。</p>
 | 
			
		||||
                       <p>これは、サブフローを用いる時に役立つ機能であり、メッセージがどのノードから出力されたかを正確に特定することが遥かに簡単になります。</p>
 | 
			
		||||
                       <p>本リスト内の要素をクリックすると、ワークスペース内にその要素が表示されます。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Adding Mermaid Diagrams",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "Continuous Search",
 | 
			
		||||
                "ja": "連続した検索"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/mermaid.png',
 | 
			
		||||
            image: 'images/continuous-search.png',
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
 | 
			
		||||
                          <p>This gives you much richer options for documenting your flows.</p>`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<p>When searching for things in the editor, a new toolbar in
 | 
			
		||||
                             the workspace provides options to quickly jump between
 | 
			
		||||
                             the search results.</p>`,
 | 
			
		||||
                "ja": `<p>ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。</p>`
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Managing Global Environment Variables",
 | 
			
		||||
                // "ja": ""
 | 
			
		||||
                "en-US": "New wiring actions",
 | 
			
		||||
                "ja": "新しいワイヤー操作"
 | 
			
		||||
            },
 | 
			
		||||
            image: 'images/global-env-vars.png',
 | 
			
		||||
            image: "images/split-wire-with-links.gif",
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
 | 
			
		||||
                          'Global Environment Variables' section of User Settings.</p>`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<p>A new action has been added that will replace a wire with a pair of connected Link nodes:</p>
 | 
			
		||||
                          <ul>
 | 
			
		||||
                          <li><b><code>Split Wire With Link Nodes</code></b></li>
 | 
			
		||||
                          </ul>
 | 
			
		||||
                          <p>Actions can be accessed from the Action List in the main menu.</p>`,
 | 
			
		||||
                "ja": `<p>ワイヤーを、接続されたLinkノードのペアに置き換える動作が新たに追加されました:</p>
 | 
			
		||||
                       <ul>
 | 
			
		||||
                       <li><b><code>ワイヤーをlinkノードで分割</code></b></li>
 | 
			
		||||
                       </ul>
 | 
			
		||||
                       <p>本アクションは、メインメニュー内の動作一覧から呼び出せます。</p>`,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Default node names",
 | 
			
		||||
                "ja": "標準ノードの名前"
 | 
			
		||||
            },
 | 
			
		||||
            // image: "images/",
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>Some nodes have been updated to generate a unique name when
 | 
			
		||||
                             new instances are added to the workspace. This applies to
 | 
			
		||||
                             <code>Debug</code>, <code>Function</code> and <code>Link</code> nodes.</p>
 | 
			
		||||
                          <p>A new action has also been added to generate default names for the selected
 | 
			
		||||
                             nodes:</p>
 | 
			
		||||
                             <ul>
 | 
			
		||||
                             <li><b><code>Generate Node Names</code></b></li>
 | 
			
		||||
                             </ul><p>Actions can be accessed from the Action List in the main menu.</p>
 | 
			
		||||
                            `,
 | 
			
		||||
                "ja": `<p>一部のノードは、ワークスペース上に新インスタンスとして追加した際に、一意の名前を付けるよう変更されました。この変更は、<code>Debug</code>、<code>Function</code>、<code>Link</code>ノードに適用されています。</p>
 | 
			
		||||
                       <p>選択したノードに対して、標準の名前を生成する動作も新たに追加されました:</p>
 | 
			
		||||
                          <ul>
 | 
			
		||||
                          <li><b><code>ノード名を生成</code></b></li>
 | 
			
		||||
                          </ul><p>本アクションは、メインメニュー内の動作一覧から呼び出せます。</p>
 | 
			
		||||
                         `
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            title: {
 | 
			
		||||
                "en-US": "Node Updates",
 | 
			
		||||
@@ -99,9 +139,16 @@ export default {
 | 
			
		||||
            },
 | 
			
		||||
            // image: "images/",
 | 
			
		||||
            description: {
 | 
			
		||||
                "en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
 | 
			
		||||
                          small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
 | 
			
		||||
                // "ja": ``
 | 
			
		||||
                "en-US": `<ul>
 | 
			
		||||
                            <li>The Debug node can be configured to count messages it receives</li>
 | 
			
		||||
                            <li>The Link Call node can use a message property to dynamically target the link it should call</li>
 | 
			
		||||
                            <li>The HTTP Request node can be preconfigured with HTTP headers</li>
 | 
			
		||||
                          </ul>`,
 | 
			
		||||
                "ja": `<ul>
 | 
			
		||||
                         <li>Debugノードは、受信したメッセージの数をカウントするよう設定できるようになりました。</li>
 | 
			
		||||
                         <li>Link Callノードは、メッセージのプロパティによって、呼び出し対象のlinkを動的に指定できるようになりました。</li>
 | 
			
		||||
                         <li>HTTP Requestノードは、HTTPヘッダを事前設定できるようになりました。</li>
 | 
			
		||||
                       </ul>`
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,16 @@
 | 
			
		||||
        color:"#c0edc0",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: {value:""},
 | 
			
		||||
            scope: {value:[], type:"*[]"},
 | 
			
		||||
            scope: {
 | 
			
		||||
                value: [],
 | 
			
		||||
                type: "*[]",
 | 
			
		||||
                validate: function (v, opt) {
 | 
			
		||||
                    if (v.length > 0) {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    return RED._("node-red:complete.errors.scopeUndefined");
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            uncaught: {value:false}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:0,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
 | 
			
		||||
<script type="text/html" data-template-name="link in">
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
 | 
			
		||||
@@ -272,7 +271,17 @@
 | 
			
		||||
        color:"#ddd",//"#87D8CF",
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: { value: "" },
 | 
			
		||||
            links: { value: [], type:"link in[]" },
 | 
			
		||||
            links: {
 | 
			
		||||
                value: [],
 | 
			
		||||
                type: "link in[]",
 | 
			
		||||
                validate: function (v, opt) {
 | 
			
		||||
                    if ((this.linkType === "static" && v.length > 0)
 | 
			
		||||
                      || this.linkType === "dynamic") {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    return RED._("node-red:link.errors.linkUndefined");
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            linkType: { value:"static" },
 | 
			
		||||
            timeout: {
 | 
			
		||||
                value: "30",
 | 
			
		||||
 
 | 
			
		||||
@@ -164,10 +164,10 @@ module.exports = function(RED) {
 | 
			
		||||
                    if (returnNode && returnNode.returnLinkMessage) {
 | 
			
		||||
                        returnNode.returnLinkMessage(messageEvent.id, msg);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.warn(RED._("link.error.missingReturn"))
 | 
			
		||||
                        node.warn(RED._("link.errors.missingReturn"));
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    node.warn(RED._("link.error.missingReturn"))
 | 
			
		||||
                    node.warn(RED._("link.errors.missingReturn"));
 | 
			
		||||
                }
 | 
			
		||||
                done();
 | 
			
		||||
            } else if (mode === "link") {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
<script type="text/html" data-template-name="global-config">
 | 
			
		||||
  <div class="form-row">
 | 
			
		||||
        <label style="width: 100%"><span data-i18n="global-config.label.open-conf"></span>:</label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <button class="red-ui-button" type="button" id="node-input-edit-env-var" data-i18n="editor:env-var.header" style="margin-left: 20px"></button>
 | 
			
		||||
    </div>
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    RED.nodes.registerType('global-config',{
 | 
			
		||||
        category: 'config',
 | 
			
		||||
        defaults: {
 | 
			
		||||
            name: { value: "" },
 | 
			
		||||
            env: { value: [] },
 | 
			
		||||
        },
 | 
			
		||||
        credentials: {
 | 
			
		||||
            map: { type: "map" }
 | 
			
		||||
        },
 | 
			
		||||
        oneditprepare: function() {
 | 
			
		||||
            $('#node-input-edit-env-var').on('click', function(evt) {
 | 
			
		||||
                RED.actions.invoke('core:show-user-settings', 'envvar')
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        hasUsers: false
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
module.exports = function(RED) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
    function GlobalConfigNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
    }
 | 
			
		||||
    RED.nodes.registerType("global-config", GlobalConfigNode);
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,6 @@
 | 
			
		||||
            <option value="scale" data-i18n="range.scale.payload"></option>
 | 
			
		||||
            <option value="clamp" data-i18n="range.scale.limit"></option>
 | 
			
		||||
            <option value="roll" data-i18n="range.scale.wrap"></option>
 | 
			
		||||
            <option value="drop" data-i18n="range.scale.drop"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br/>
 | 
			
		||||
 
 | 
			
		||||
@@ -32,15 +32,11 @@ module.exports = function(RED) {
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                var n = Number(value);
 | 
			
		||||
                if (!isNaN(n)) {
 | 
			
		||||
                    if (node.action === "drop") {
 | 
			
		||||
                        if (n < node.minin) { done(); return; }
 | 
			
		||||
                        if (n > node.maxin) { done(); return; }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.action === "clamp") {
 | 
			
		||||
                    if (node.action == "clamp") {
 | 
			
		||||
                        if (n < node.minin) { n = node.minin; }
 | 
			
		||||
                        if (n > node.maxin) { n = node.maxin; }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (node.action === "roll") {
 | 
			
		||||
                    if (node.action == "roll") {
 | 
			
		||||
                        var divisor = node.maxin - node.minin;
 | 
			
		||||
                        n = ((n - node.minin) % divisor + divisor) % divisor + node.minin;
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -201,7 +201,6 @@ module.exports = function(RED) {
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() { clearDelayList(); });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        else if (node.pauseType === "random") {
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                var wait = node.randomFirst + (node.diff * Math.random());
 | 
			
		||||
@@ -227,19 +226,34 @@ module.exports = function(RED) {
 | 
			
		||||
        // The rate limit/queue type modes
 | 
			
		||||
        else if (node.pauseType === "rate") {
 | 
			
		||||
            node.on("input", function(msg, send, done) {
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    if (node.intervalID !== -1 ) {
 | 
			
		||||
                        clearInterval(node.intervalID);
 | 
			
		||||
                        node.intervalID = -1;
 | 
			
		||||
                    }
 | 
			
		||||
                    delete node.lastSent;
 | 
			
		||||
                    node.buffer = [];
 | 
			
		||||
                    node.rate = node.fixedrate;
 | 
			
		||||
                    node.status({fill:"blue",shape:"ring",text:0});
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!node.drop) {
 | 
			
		||||
                    var m = RED.util.cloneMessage(msg);
 | 
			
		||||
                    delete m.flush;
 | 
			
		||||
                    delete m.lifo;
 | 
			
		||||
                    if (Object.keys(m).length > 1) {
 | 
			
		||||
                        if (node.intervalID !== -1) {
 | 
			
		||||
                            if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) {
 | 
			
		||||
                                node.rate = m.rate;
 | 
			
		||||
                            if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate)) && node.rate !== msg.rate) {
 | 
			
		||||
                                node.rate = msg.rate;
 | 
			
		||||
                                clearInterval(node.intervalID);
 | 
			
		||||
                                node.intervalID = setInterval(sendMsgFromBuffer, node.rate);
 | 
			
		||||
                            }
 | 
			
		||||
                            var max_msgs = maxKeptMsgsCount(node);
 | 
			
		||||
                            if ((max_msgs > 0) && (node.buffer.length >= max_msgs)) {
 | 
			
		||||
                                node.buffer = [];
 | 
			
		||||
                                node.error(RED._("delay.errors.too-many"), m);
 | 
			
		||||
                                node.error(RED._("delay.errors.too-many"), msg);
 | 
			
		||||
                            } else if (msg.toFront === true) {
 | 
			
		||||
                                node.buffer.unshift({msg: m, send: send, done: done});
 | 
			
		||||
                                node.reportDepth();
 | 
			
		||||
@@ -249,8 +263,8 @@ module.exports = function(RED) {
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) {
 | 
			
		||||
                                node.rate = m.rate;
 | 
			
		||||
                            if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) {
 | 
			
		||||
                                node.rate = msg.rate;
 | 
			
		||||
                            }
 | 
			
		||||
                            send(m);
 | 
			
		||||
                            node.reportDepth();
 | 
			
		||||
@@ -268,8 +282,6 @@ module.exports = function(RED) {
 | 
			
		||||
                        else {
 | 
			
		||||
                            while (len > 0) {
 | 
			
		||||
                                const msgInfo = node.buffer.shift();
 | 
			
		||||
                                delete msgInfo.msg.flush;
 | 
			
		||||
                                delete msgInfo.msg.reset;
 | 
			
		||||
                                if (Object.keys(msgInfo.msg).length > 1) {
 | 
			
		||||
                                    node.send(msgInfo.msg);
 | 
			
		||||
                                    msgInfo.done();
 | 
			
		||||
@@ -323,21 +335,6 @@ module.exports = function(RED) {
 | 
			
		||||
                    }
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    if (msg.flush === undefined) {
 | 
			
		||||
                        if (node.intervalID !== -1 ) {
 | 
			
		||||
                            clearInterval(node.intervalID);
 | 
			
		||||
                            node.intervalID = -1;
 | 
			
		||||
                        }
 | 
			
		||||
                        delete node.lastSent;
 | 
			
		||||
                    }
 | 
			
		||||
                    node.buffer = [];
 | 
			
		||||
                    node.rate = node.fixedrate;
 | 
			
		||||
                    node.status({fill:"blue",shape:"ring",text:0});
 | 
			
		||||
                    done();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() {
 | 
			
		||||
                clearInterval(node.intervalID);
 | 
			
		||||
@@ -390,22 +387,6 @@ module.exports = function(RED) {
 | 
			
		||||
                    node.buffer.push({msg, send, done}); // if not add to end of queue
 | 
			
		||||
                    node.reportDepth();
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.hasOwnProperty("flush")) {
 | 
			
		||||
                    var len = node.buffer.length;
 | 
			
		||||
                    if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush,len)); }
 | 
			
		||||
                    while (len > 0) {
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
                        delete msgInfo.msg.flush;
 | 
			
		||||
                        delete msgInfo.msg.reset;
 | 
			
		||||
                        if (Object.keys(msgInfo.msg).length > 2) {
 | 
			
		||||
                            node.send(msgInfo.msg);
 | 
			
		||||
                            msgInfo.done();
 | 
			
		||||
                        }
 | 
			
		||||
                        len = len - 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    node.status({});
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.hasOwnProperty("reset")) {
 | 
			
		||||
                    while (node.buffer.length > 0) {
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
@@ -416,6 +397,21 @@ module.exports = function(RED) {
 | 
			
		||||
                    node.status({text:"reset"});
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.hasOwnProperty("flush")) {
 | 
			
		||||
                    var len = node.buffer.length;
 | 
			
		||||
                    if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush,len)); }
 | 
			
		||||
                    while (len > 0) {
 | 
			
		||||
                        const msgInfo = node.buffer.shift();
 | 
			
		||||
                        delete msgInfo.msg.flush;
 | 
			
		||||
                        if (Object.keys(msgInfo.msg).length > 2) {
 | 
			
		||||
                            node.send(msgInfo.msg);
 | 
			
		||||
                            msgInfo.done();
 | 
			
		||||
                        }
 | 
			
		||||
                        len = len - 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    node.status({});
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            node.on("close", function() {
 | 
			
		||||
                clearInterval(node.intervalID);
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
        <select id="node-then-type" style="width:70%;">
 | 
			
		||||
            <option value="block" data-i18n="trigger.wait-reset"></option>
 | 
			
		||||
            <option value="wait" data-i18n="trigger.wait-for"></option>
 | 
			
		||||
            <option id="node-trigger-wait-loop" value="loop" data-i18n="trigger.wait-loop"></option>
 | 
			
		||||
            <option value="loop" data-i18n="trigger.wait-loop"></option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row node-type-duration">
 | 
			
		||||
@@ -181,13 +181,6 @@
 | 
			
		||||
                $("#node-input-op2type").val('str');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $("#node-input-op1").on("change", function() {
 | 
			
		||||
                if ($("#node-input-op1type").val() === "nul") {
 | 
			
		||||
                    $("#node-trigger-wait-loop").hide();
 | 
			
		||||
                }
 | 
			
		||||
                else { $("#node-trigger-wait-loop").show(); }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var optionNothing = {value:"nul",label:this._("trigger.output.nothing"),hasValue:false};
 | 
			
		||||
            var optionPayload = {value:"pay",label:this._("trigger.output.existing"),hasValue:false};
 | 
			
		||||
            var optionOriginalPayload = {value:"pay",label:this._("trigger.output.original"),hasValue:false};
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@ module.exports = function(RED) {
 | 
			
		||||
        this.topic = n.topic;
 | 
			
		||||
        this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
 | 
			
		||||
        this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
 | 
			
		||||
        this.newline = (n.newline||"").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t");
 | 
			
		||||
        this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
 | 
			
		||||
        this.base64 = n.base64;
 | 
			
		||||
        this.trim = n.trim || false;
 | 
			
		||||
        this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,9 @@ module.exports = function(RED) {
 | 
			
		||||
    function CSVNode(n) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        this.template = (n.temp || "");
 | 
			
		||||
        this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
 | 
			
		||||
        this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
 | 
			
		||||
        this.quo = '"';
 | 
			
		||||
        this.ret = (n.ret || "\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
 | 
			
		||||
        this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
 | 
			
		||||
        this.winflag = (this.ret === "\r\n");
 | 
			
		||||
        this.lineend = "\n";
 | 
			
		||||
        this.multi = n.multi || "one";
 | 
			
		||||
 
 | 
			
		||||
@@ -224,12 +224,7 @@
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "join.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            var nam = this.name||this._("join.join");
 | 
			
		||||
            if (this.mode === "custom" && !isNaN(Number(this.count))) {
 | 
			
		||||
                nam += " "+this.count;
 | 
			
		||||
                if (this.accumulate === true) { nam+= "+"; }
 | 
			
		||||
            }
 | 
			
		||||
            return nam;
 | 
			
		||||
            return this.name||this._("join.join");
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name?"node_label_italic":"";
 | 
			
		||||
 
 | 
			
		||||
@@ -107,14 +107,7 @@
 | 
			
		||||
        outputs:1,
 | 
			
		||||
        icon: "batch.svg",
 | 
			
		||||
        label: function() {
 | 
			
		||||
            var nam = this.name||this._("batch.batch");
 | 
			
		||||
            if (this.mode === "count" && !isNaN(Number(this.count))) {
 | 
			
		||||
                nam += " "+this.count;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.mode === "interval" && !isNaN(Number(this.interval))) {
 | 
			
		||||
                nam += " "+this.interval+"s";
 | 
			
		||||
            }
 | 
			
		||||
            return nam;
 | 
			
		||||
            return this.name||this._("batch.batch");;
 | 
			
		||||
        },
 | 
			
		||||
        labelStyle: function() {
 | 
			
		||||
            return this.name ? "node_label_italic" : "";
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,9 @@ module.exports = function(RED) {
 | 
			
		||||
                }
 | 
			
		||||
                if (typeof data === "boolean") { data = data.toString(); }
 | 
			
		||||
                if (typeof data === "number") { data = data.toString(); }
 | 
			
		||||
                if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
 | 
			
		||||
                var aflg = true;
 | 
			
		||||
                if (msg.hasOwnProperty("parts") && msg.parts.type === "string" && (msg.parts.count === msg.parts.index + 1)) { aflg = false; }
 | 
			
		||||
                if ((node.appendNewline) && (!Buffer.isBuffer(data)) && aflg) { data += os.EOL; }
 | 
			
		||||
                var buf;
 | 
			
		||||
                if (node.encoding === "setbymsg") {
 | 
			
		||||
                    buf = encode(data, msg.encoding || "none");
 | 
			
		||||
@@ -314,7 +316,6 @@ module.exports = function(RED) {
 | 
			
		||||
            });
 | 
			
		||||
            filename = filename || "";
 | 
			
		||||
            var fullFilename = filename;
 | 
			
		||||
            var filePath = "";
 | 
			
		||||
            if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
 | 
			
		||||
                fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
<script type="text/html" data-help-name="global-config">
 | 
			
		||||
    <p>A node for holding global configuration of flows.</p>
 | 
			
		||||
</script>
 | 
			
		||||
@@ -34,14 +34,11 @@
 | 
			
		||||
    the range specified within the target range.</p>
 | 
			
		||||
    <p><i>Scale and wrap within the target range</i> means that the result will
 | 
			
		||||
    be wrapped within the target range.</p>
 | 
			
		||||
    <p><i>Scale, but drop if outside input range</i> means that the result will
 | 
			
		||||
    be scaled, but any inputs outside of the inout range will be dropped.</p>
 | 
			
		||||
    <p>For example an input 0 - 10 mapped to 0 - 100.</p>
 | 
			
		||||
    <table style="outline-width:#888 solid thin">
 | 
			
		||||
        <tr><th width="80px">mode</th><th width="80px">input</th><th width="80px">output</th></tr>
 | 
			
		||||
        <tr><td><center>scale</center></td><td><center>12</center></td><td><center>120</center></td></tr>
 | 
			
		||||
        <tr><td><center>limit</center></td><td><center>12</center></td><td><center>100</center></td></tr>
 | 
			
		||||
        <tr><td><center>wrap</center></td><td><center>12</center></td><td><center>20</center></td></tr>
 | 
			
		||||
        <tr><td><center>drop</center></td><td><center>12</center></td><td><center><i>(no output)</i></center></td></tr>
 | 
			
		||||
    </table>
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -119,7 +119,10 @@
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "complete": {
 | 
			
		||||
        "completeNodes": "complete: __number__"
 | 
			
		||||
        "completeNodes": "complete: __number__",
 | 
			
		||||
        "errors": {
 | 
			
		||||
            "scopeUndefined": "scope undefined"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "debug": {
 | 
			
		||||
        "output": "Output",
 | 
			
		||||
@@ -181,8 +184,9 @@
 | 
			
		||||
        "staticLinkCall": "Fixed target",
 | 
			
		||||
        "dynamicLinkCall": "Dynamic target (msg.target)",
 | 
			
		||||
        "dynamicLinkLabel": "Dynamic",
 | 
			
		||||
        "error": {
 | 
			
		||||
            "missingReturn": "Missing return node information"
 | 
			
		||||
        "errors": {
 | 
			
		||||
            "missingReturn": "Missing return node information",
 | 
			
		||||
            "linkUndefined": "link undefined"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "tls": {
 | 
			
		||||
@@ -816,8 +820,7 @@
 | 
			
		||||
        "scale": {
 | 
			
		||||
            "payload": "Scale the message property",
 | 
			
		||||
            "limit": "Scale and limit to the target range",
 | 
			
		||||
            "wrap": "Scale and wrap within the target range",
 | 
			
		||||
            "drop": "Scale, but drop msg if outside input range"
 | 
			
		||||
            "wrap": "Scale and wrap within the target range"
 | 
			
		||||
        },
 | 
			
		||||
        "tip": "Tip: This node ONLY works with numbers.",
 | 
			
		||||
        "errors": {
 | 
			
		||||
@@ -1127,10 +1130,5 @@
 | 
			
		||||
        "warn": {
 | 
			
		||||
            "nonumber": "no number found in payload"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "global-config": {
 | 
			
		||||
        "label": {
 | 
			
		||||
            "open-conf": "Open Configuration"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,5 @@
 | 
			
		||||
    <p>If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.</p>
 | 
			
		||||
    <p>The node can accept a multi-part input as long as the <code>parts</code> property is set correctly, for example from a file-in node or split node.</p>
 | 
			
		||||
    <p>If outputting multiple messages they will have their <code>parts</code> property set and form a complete message sequence.</p>
 | 
			
		||||
    <p>If the node is set to only send column headers once, then setting <code>msg.reset</code> to any value will cause the node to resend the headers.</p>
 | 
			
		||||
    <p><b>Note:</b> the column template must be comma separated - even if a different separator is chosen for the data.</p>
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
<script type="text/html" data-help-name="global-config">
 | 
			
		||||
    <p>大域的なフローの設定を保持するノード。大域的な環境変数の定義を含みます。</p>
 | 
			
		||||
</script>p
 | 
			
		||||
@@ -119,7 +119,10 @@
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "complete": {
 | 
			
		||||
        "completeNodes": "complete: __number__"
 | 
			
		||||
        "completeNodes": "complete: __number__",
 | 
			
		||||
        "errors": {
 | 
			
		||||
            "scopeUndefined": "スコープが未定義"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "debug": {
 | 
			
		||||
        "output": "対象",
 | 
			
		||||
@@ -181,8 +184,9 @@
 | 
			
		||||
        "staticLinkCall": "対象を固定で指定",
 | 
			
		||||
        "dynamicLinkCall": "対象を動的に指定 (msg.target)",
 | 
			
		||||
        "dynamicLinkLabel": "動的",
 | 
			
		||||
        "error": {
 | 
			
		||||
            "missingReturn": "返却するノードの情報が存在しません"
 | 
			
		||||
        "errors": {
 | 
			
		||||
            "missingReturn": "返却するノードの情報が存在しません",
 | 
			
		||||
            "linkUndefined": "リンクが未定義"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "tls": {
 | 
			
		||||
@@ -780,8 +784,8 @@
 | 
			
		||||
            "change": "値の置換",
 | 
			
		||||
            "delete": "値の削除",
 | 
			
		||||
            "move": "値の移動",
 | 
			
		||||
            "toValue": "代入する値",
 | 
			
		||||
            "to": "移動先",
 | 
			
		||||
            "toValue": "対象の値",
 | 
			
		||||
            "to": "対象の値",
 | 
			
		||||
            "search": "検索する文字列",
 | 
			
		||||
            "replace": "置換後の文字列"
 | 
			
		||||
        },
 | 
			
		||||
@@ -816,8 +820,7 @@
 | 
			
		||||
        "scale": {
 | 
			
		||||
            "payload": "msg.payloadの値を拡大/縮小",
 | 
			
		||||
            "limit": "入力値の範囲外の値を最小値/最大値とし拡大/縮小",
 | 
			
		||||
            "wrap": "入力値の範囲外の値を範囲幅で割った余りとし拡大/縮小",
 | 
			
		||||
            "drop": "値を拡大/縮小(入力範囲外の時はメッセージを削除)"
 | 
			
		||||
            "wrap": "入力値の範囲外の値を範囲幅で割った余りとし拡大/縮小"
 | 
			
		||||
        },
 | 
			
		||||
        "tip": "注釈: 本ノードは、数値のみ扱うことができます。",
 | 
			
		||||
        "errors": {
 | 
			
		||||
@@ -1127,10 +1130,5 @@
 | 
			
		||||
        "warn": {
 | 
			
		||||
            "nonumber": "ペイロードに数値が含まれていません"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "global-config": {
 | 
			
		||||
        "label": {
 | 
			
		||||
            "open-conf": "設定を開く"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,5 @@
 | 
			
		||||
    <p>「null値を含む」オプションがチェックされている場合、null値が結果に返されます。つまり「"1",,3」の真ん中の値がnullになります。</p>
 | 
			
		||||
    <p>file-inノードやsplitノードが出力するメッセージの様に、<code>parts</code>プロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。</p>
 | 
			
		||||
    <p>CSVを複数のメッセージに変換して出力する場合、出力がメッセージ列となるよう<code>parts</code>プロパティを設定します。</p>
 | 
			
		||||
    <p>ヘッダを一度だけ送信するよう設定している場合、任意の値を持つ<code>msg.reset</code>を渡すと、再度ヘッダを含めて送信できるようになります。</p>
 | 
			
		||||
    <p><b>注:</b> カンマ以外の区切り文字を設定した場合であっても、「列名」はカンマ区切りとしてください。</p>
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/nodes",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
@@ -15,10 +15,10 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "acorn": "8.8.1",
 | 
			
		||||
        "acorn": "8.7.1",
 | 
			
		||||
        "acorn-walk": "8.2.0",
 | 
			
		||||
        "ajv": "8.11.2",
 | 
			
		||||
        "body-parser": "1.20.1",
 | 
			
		||||
        "ajv": "8.11.0",
 | 
			
		||||
        "body-parser": "1.20.0",
 | 
			
		||||
        "cheerio": "1.0.0-rc.10",
 | 
			
		||||
        "content-type": "1.0.4",
 | 
			
		||||
        "cookie-parser": "1.4.6",
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
        "fs-extra": "10.1.0",
 | 
			
		||||
        "got": "11.8.5",
 | 
			
		||||
        "hash-sum": "2.0.0",
 | 
			
		||||
        "hpagent": "1.2.0",
 | 
			
		||||
        "hpagent": "1.0.0",
 | 
			
		||||
        "https-proxy-agent": "5.0.1",
 | 
			
		||||
        "is-utf8": "0.2.1",
 | 
			
		||||
        "js-yaml": "4.1.0",
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
        "node-watch": "0.7.3",
 | 
			
		||||
        "on-headers": "1.0.2",
 | 
			
		||||
        "raw-body": "2.5.1",
 | 
			
		||||
        "tough-cookie": "4.1.2",
 | 
			
		||||
        "tough-cookie": "4.0.0",
 | 
			
		||||
        "uuid": "8.3.2",
 | 
			
		||||
        "ws": "7.5.6",
 | 
			
		||||
        "xml2js": "0.4.23",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/registry",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "main": "./lib/index.js",
 | 
			
		||||
    "repository": {
 | 
			
		||||
@@ -16,11 +16,11 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@node-red/util": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/util": "3.0.2",
 | 
			
		||||
        "clone": "2.1.2",
 | 
			
		||||
        "fs-extra": "10.1.0",
 | 
			
		||||
        "semver": "7.3.8",
 | 
			
		||||
        "tar": "6.1.12",
 | 
			
		||||
        "uglify-js": "3.17.4"
 | 
			
		||||
        "semver": "7.3.7",
 | 
			
		||||
        "tar": "6.1.11",
 | 
			
		||||
        "uglify-js": "3.16.3"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -89,16 +89,10 @@ var api = module.exports = {
 | 
			
		||||
 | 
			
		||||
        if (!runtime.settings.disableEditor) {
 | 
			
		||||
            safeSettings.context = runtime.nodes.listContextStores();
 | 
			
		||||
            if (runtime.settings.editorTheme) {
 | 
			
		||||
                if (runtime.settings.editorTheme.codeEditor) {
 | 
			
		||||
                    safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {};
 | 
			
		||||
                    safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco";
 | 
			
		||||
                    safeSettings.codeEditor.options = safeSettings.codeEditor.options || {};
 | 
			
		||||
                }
 | 
			
		||||
                if (runtime.settings.editorTheme.markdownEditor) {
 | 
			
		||||
                    safeSettings.markdownEditor = runtime.settings.editorTheme.markdownEditor || {};
 | 
			
		||||
                    safeSettings.markdownEditor.mermaid = safeSettings.markdownEditor.mermaid || { enabled: true };
 | 
			
		||||
                }
 | 
			
		||||
            if (runtime.settings.editorTheme && runtime.settings.editorTheme.codeEditor) {
 | 
			
		||||
                safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {};
 | 
			
		||||
                safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco";
 | 
			
		||||
                safeSettings.codeEditor.options = safeSettings.codeEditor.options || {};
 | 
			
		||||
            }
 | 
			
		||||
            safeSettings.libraries = runtime.library.getLibraries();
 | 
			
		||||
            if (util.isArray(runtime.settings.paletteCategories)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -818,6 +818,16 @@ function handlePreRoute(flow, sendEvent, reportError) {
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function deliverMessageToDestination(sendEvent) {
 | 
			
		||||
    if (sendEvent?.destination?.node) {
 | 
			
		||||
        try {
 | 
			
		||||
            sendEvent.destination.node.receive(sendEvent.msg);
 | 
			
		||||
        } catch(err) {
 | 
			
		||||
            Log.error(`Error delivering message to node:${sendEvent.destination.node._path} [${sendEvent.destination.node.type}]`)
 | 
			
		||||
            Log.error(err.stack)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
function handlePreDeliver(flow,sendEvent, reportError) {
 | 
			
		||||
    // preDeliver - the local router has identified the node it is going to send to. At this point, the message has been cloned if needed.
 | 
			
		||||
    hooks.trigger("preDeliver",sendEvent,(err) => {
 | 
			
		||||
@@ -827,15 +837,10 @@ function handlePreDeliver(flow,sendEvent, reportError) {
 | 
			
		||||
        } else if (err !== false) {
 | 
			
		||||
            if (asyncMessageDelivery) {
 | 
			
		||||
                setImmediate(function() {
 | 
			
		||||
                    if (sendEvent.destination.node) {
 | 
			
		||||
                        sendEvent.destination.node.receive(sendEvent.msg);
 | 
			
		||||
                    }
 | 
			
		||||
                    deliverMessageToDestination(sendEvent)
 | 
			
		||||
                })
 | 
			
		||||
            } else {
 | 
			
		||||
                if (sendEvent.destination.node) {
 | 
			
		||||
                    sendEvent.destination.node.receive(sendEvent.msg);
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                deliverMessageToDestination(sendEvent)
 | 
			
		||||
            }
 | 
			
		||||
            // postDeliver - the message has been dispatched to be delivered asynchronously (unless the sync delivery flag is set, in which case it would be continue as synchronous delivery)
 | 
			
		||||
            hooks.trigger("postDeliver", sendEvent, function(err) {
 | 
			
		||||
 
 | 
			
		||||
@@ -785,16 +785,6 @@ const flowAPI = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getGlobalConfig() {
 | 
			
		||||
    let gconf = null;
 | 
			
		||||
    eachNode((n) => {
 | 
			
		||||
        if (n.type === "global-config") {
 | 
			
		||||
            gconf = n;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return gconf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    init: init,
 | 
			
		||||
 | 
			
		||||
@@ -808,9 +798,6 @@ module.exports = {
 | 
			
		||||
    get:getNode,
 | 
			
		||||
    eachNode: eachNode,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    getGlobalConfig: getGlobalConfig,
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the current flow configuration
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,7 @@ var redUtil = require("@node-red/util").util;
 | 
			
		||||
var Log = require("@node-red/util").log;
 | 
			
		||||
var subflowInstanceRE = /^subflow:(.+)$/;
 | 
			
		||||
var typeRegistry = require("@node-red/registry");
 | 
			
		||||
const credentials = require("../nodes/credentials");
 | 
			
		||||
 | 
			
		||||
let _runtime = null;
 | 
			
		||||
 | 
			
		||||
var envVarExcludes = {};
 | 
			
		||||
 | 
			
		||||
@@ -267,55 +265,15 @@ function parseConfig(config) {
 | 
			
		||||
   return flow;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getGlobalEnv(name) {
 | 
			
		||||
    const nodes = _runtime.nodes;
 | 
			
		||||
    if (!nodes) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    const gconf = nodes.getGlobalConfig();
 | 
			
		||||
    const env = gconf ? gconf.env : null;
 | 
			
		||||
 | 
			
		||||
    if (env) {
 | 
			
		||||
        const cred = (gconf ? credentials.get(gconf.id) : null) || {
 | 
			
		||||
            map: {}
 | 
			
		||||
        };
 | 
			
		||||
        const map = cred.map;
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < env.length; i++) {
 | 
			
		||||
            const item = env[i];
 | 
			
		||||
            if (item.name === name) {
 | 
			
		||||
                if (item.type === "cred") {
 | 
			
		||||
                    return {
 | 
			
		||||
                        name: name,
 | 
			
		||||
                        value: map[name],
 | 
			
		||||
                        type: "cred"
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                return item;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    init: function(runtime) {
 | 
			
		||||
        _runtime = runtime;
 | 
			
		||||
        envVarExcludes = {};
 | 
			
		||||
        if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
 | 
			
		||||
            runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    getEnvVar: function(k) {
 | 
			
		||||
        if (!envVarExcludes[k]) {
 | 
			
		||||
            const item = getGlobalEnv(k);
 | 
			
		||||
            if (item) {
 | 
			
		||||
                const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, null);
 | 
			
		||||
                return val;
 | 
			
		||||
            }
 | 
			
		||||
            return process.env[k];
 | 
			
		||||
        }
 | 
			
		||||
        return undefined;
 | 
			
		||||
        return !envVarExcludes[k]?process.env[k]:undefined
 | 
			
		||||
    },
 | 
			
		||||
    diffNodes: diffNodes,
 | 
			
		||||
    mapEnvVarProperties: mapEnvVarProperties,
 | 
			
		||||
 
 | 
			
		||||
@@ -383,11 +383,6 @@ var api = module.exports = {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else if (nodeType === "global-config") {
 | 
			
		||||
                if (JSON.stringify(savedCredentials.map) !== JSON.stringify(newCreds.map)) {
 | 
			
		||||
                    savedCredentials.map = newCreds.map;
 | 
			
		||||
                    dirty = true;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                var dashedType = nodeType.replace(/\s+/g, '-');
 | 
			
		||||
                var definition = credentialsDef[dashedType];
 | 
			
		||||
 
 | 
			
		||||
@@ -205,7 +205,6 @@ module.exports = {
 | 
			
		||||
    getNode: flows.get,
 | 
			
		||||
    eachNode: flows.eachNode,
 | 
			
		||||
    getContext: context.get,
 | 
			
		||||
    getGlobalConfig: flows.getGlobalConfig,
 | 
			
		||||
 | 
			
		||||
    clearContext: context.clear,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/runtime",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "main": "./lib/index.js",
 | 
			
		||||
    "repository": {
 | 
			
		||||
@@ -16,11 +16,11 @@
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@node-red/registry": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/util": "3.1.0-beta.1",
 | 
			
		||||
        "async-mutex": "0.4.0",
 | 
			
		||||
        "@node-red/registry": "3.0.2",
 | 
			
		||||
        "@node-red/util": "3.0.2",
 | 
			
		||||
        "async-mutex": "0.3.2",
 | 
			
		||||
        "clone": "2.1.2",
 | 
			
		||||
        "express": "4.18.2",
 | 
			
		||||
        "express": "4.18.1",
 | 
			
		||||
        "fs-extra": "10.1.0",
 | 
			
		||||
        "json-stringify-safe": "5.0.1"
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/node_modules/@node-red/util/lib/util.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -696,19 +696,13 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
 | 
			
		||||
function prepareJSONataExpression(value,node) {
 | 
			
		||||
    var expr = jsonata(value);
 | 
			
		||||
    expr.assign('flowContext', function(val, store) {
 | 
			
		||||
        if (node) {
 | 
			
		||||
            return node.context().flow.get(val, store);
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
        return node.context().flow.get(val, store);
 | 
			
		||||
    });
 | 
			
		||||
    expr.assign('globalContext', function(val, store) {
 | 
			
		||||
        if (node) {
 | 
			
		||||
            return node.context().global.get(val, store);
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
        return node.context().global.get(val, store);
 | 
			
		||||
    });
 | 
			
		||||
    expr.assign('env', function(name) {
 | 
			
		||||
        var val = getSetting(node, name, node ? node._flow : null);
 | 
			
		||||
        var val = getSetting(node, name, node._flow);
 | 
			
		||||
        if (typeof val !== 'undefined') {
 | 
			
		||||
            return val;
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "@node-red/util",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
@@ -16,11 +16,11 @@
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "fs-extra": "10.1.0",
 | 
			
		||||
        "i18next": "21.10.0",
 | 
			
		||||
        "i18next": "21.8.16",
 | 
			
		||||
        "json-stringify-safe": "5.0.1",
 | 
			
		||||
        "jsonata": "1.8.6",
 | 
			
		||||
        "lodash.clonedeep": "^4.5.0",
 | 
			
		||||
        "moment": "2.29.4",
 | 
			
		||||
        "moment-timezone": "0.5.39"
 | 
			
		||||
        "moment-timezone": "0.5.34"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/node_modules/node-red/lib/red.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -25,15 +25,9 @@ var api = require("@node-red/editor-api");
 | 
			
		||||
var server = null;
 | 
			
		||||
var apiEnabled = false;
 | 
			
		||||
 | 
			
		||||
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
 | 
			
		||||
if (NODE_MAJOR_VERSION > 14) {
 | 
			
		||||
    const dns = require('node:dns');
 | 
			
		||||
    dns.setDefaultResultOrder('ipv4first');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkVersion(userSettings) {
 | 
			
		||||
    var semver = require('semver');
 | 
			
		||||
    if (!semver.satisfies(process.version,">=12.0.0")) {
 | 
			
		||||
    if (!semver.satisfies(process.version,">=8.9.0")) {
 | 
			
		||||
        // TODO: in the future, make this a hard error.
 | 
			
		||||
        // var e = new Error("Unsupported version of Node.js");
 | 
			
		||||
        // e.code = "unsupported_version";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "node-red",
 | 
			
		||||
    "version": "3.1.0-beta.1",
 | 
			
		||||
    "version": "3.0.2",
 | 
			
		||||
    "description": "Low-code programming for event-driven applications",
 | 
			
		||||
    "homepage": "http://nodered.org",
 | 
			
		||||
    "license": "Apache-2.0",
 | 
			
		||||
@@ -31,20 +31,20 @@
 | 
			
		||||
        "flow"
 | 
			
		||||
    ],
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@node-red/editor-api": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/runtime": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/util": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/nodes": "3.1.0-beta.1",
 | 
			
		||||
        "@node-red/editor-api": "3.0.2",
 | 
			
		||||
        "@node-red/runtime": "3.0.2",
 | 
			
		||||
        "@node-red/util": "3.0.2",
 | 
			
		||||
        "@node-red/nodes": "3.0.2",
 | 
			
		||||
        "basic-auth": "2.0.1",
 | 
			
		||||
        "bcryptjs": "2.4.3",
 | 
			
		||||
        "express": "4.18.2",
 | 
			
		||||
        "express": "4.18.1",
 | 
			
		||||
        "fs-extra": "10.1.0",
 | 
			
		||||
        "node-red-admin": "^3.0.0",
 | 
			
		||||
        "nopt": "5.0.0",
 | 
			
		||||
        "semver": "7.3.8"
 | 
			
		||||
        "semver": "7.3.7"
 | 
			
		||||
    },
 | 
			
		||||
    "optionalDependencies": {
 | 
			
		||||
        "bcrypt": "5.1.0"
 | 
			
		||||
        "bcrypt": "5.0.1"
 | 
			
		||||
    },
 | 
			
		||||
    "engines": {
 | 
			
		||||
        "node": ">=14"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								packages/node_modules/node-red/red.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -458,7 +458,7 @@ httpsPromise.then(function(startupHttps) {
 | 
			
		||||
    RED.start().then(function() {
 | 
			
		||||
        if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
 | 
			
		||||
            server.on('error', function(err) {
 | 
			
		||||
                if (err.errno === "EADDRINUSE") {
 | 
			
		||||
                if (err.code === "EADDRINUSE") {
 | 
			
		||||
                    RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
 | 
			
		||||
                    RED.log.error(RED.log._("server.port-in-use"));
 | 
			
		||||
                } else {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								packages/node_modules/node-red/settings.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -416,22 +416,13 @@ module.exports = {
 | 
			
		||||
                 */
 | 
			
		||||
                // theme: "vs",
 | 
			
		||||
                /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc.
 | 
			
		||||
                 * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html
 | 
			
		||||
                 * for the full list, see https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneEditorConstructionOptions.html
 | 
			
		||||
                 */
 | 
			
		||||
                //fontSize: 14,
 | 
			
		||||
                //fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace",
 | 
			
		||||
                //fontLigatures: true,
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        markdownEditor: {
 | 
			
		||||
            mermaid: {
 | 
			
		||||
                /** enable or disable mermaid diagram in markdown document
 | 
			
		||||
                 */
 | 
			
		||||
                enabled: true
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
/*******************************************************************************
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +0,0 @@
 | 
			
		||||
var should = require("should");
 | 
			
		||||
var config = require("nr-test-utils").require("@node-red/nodes/core/common/91-global-config.js");
 | 
			
		||||
var inject = require("nr-test-utils").require("@node-red/nodes/core/common/20-inject.js");
 | 
			
		||||
var helper = require("node-red-node-test-helper");
 | 
			
		||||
 | 
			
		||||
describe('unknown Node', function() {
 | 
			
		||||
 | 
			
		||||
    afterEach(function() {
 | 
			
		||||
        helper.unload();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should be loaded', function(done) {
 | 
			
		||||
        var flow = [{id:"n1", type:"global-config", name: "XYZ" }];
 | 
			
		||||
        helper.load(config, flow, function() {
 | 
			
		||||
            var n1 = helper.getNode("n1");
 | 
			
		||||
            n1.should.have.property("name", "XYZ");
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should access global environment variable', function(done) {
 | 
			
		||||
        var flow = [{id:"n1", type:"global-config", name: "XYZ",
 | 
			
		||||
                     env: [ {
 | 
			
		||||
                         name: "X",
 | 
			
		||||
                         type: "string",
 | 
			
		||||
                         value: "foo"
 | 
			
		||||
                     }]
 | 
			
		||||
                    },
 | 
			
		||||
                    {id: "n2", type: "inject", topic: "t1", payload: "X", payloadType: "env", wires: [["n3"]], z: "flow"},
 | 
			
		||||
                    {id: "n3", type: "helper"}
 | 
			
		||||
                   ];
 | 
			
		||||
        helper.load([config, inject], flow, function() {
 | 
			
		||||
            var n2 = helper.getNode("n2");
 | 
			
		||||
            var n3 = helper.getNode("n3");
 | 
			
		||||
            n3.on("input", (msg) => {
 | 
			
		||||
                try {
 | 
			
		||||
                    msg.should.have.property("payload", "foo");
 | 
			
		||||
                    done();
 | 
			
		||||
                } catch (err) {
 | 
			
		||||
                    done(err);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            n2.receive({});
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@@ -106,27 +106,6 @@ describe('range Node', function() {
 | 
			
		||||
        genericRangeTest("clamp", 0, 10, 0, 1000, false, -1, 0, done);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('drops msg if in drop mode and input outside range', function(done) {
 | 
			
		||||
        var flow = [{"id":"rangeNode1","type":"range","minin":2,"maxin":8,"minout":20,"maxout":80,"action":"drop","round":true,"name":"rangeNode","wires":[["helperNode1"]]},
 | 
			
		||||
                    {id:"helperNode1", type:"helper", wires:[]}];
 | 
			
		||||
        helper.load(rangeNode, flow, function() {
 | 
			
		||||
            var rangeNode1 = helper.getNode("rangeNode1");
 | 
			
		||||
            var helperNode1 = helper.getNode("helperNode1");
 | 
			
		||||
            helperNode1.on("input", function(msg) {
 | 
			
		||||
                try {
 | 
			
		||||
                    msg.should.have.property('payload');
 | 
			
		||||
                    msg.payload.should.equal(50);
 | 
			
		||||
                    done();
 | 
			
		||||
                } catch(err) {
 | 
			
		||||
                    done(err);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            rangeNode1.receive({payload:1});
 | 
			
		||||
            rangeNode1.receive({payload:9});
 | 
			
		||||
            rangeNode1.receive({payload:5});
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('just passes on msg if payload not present', function(done) {
 | 
			
		||||
        var flow = [{"id":"rangeNode1","type":"range","minin":0,"maxin":100,"minout":0,"maxout":100,"action":"scale","round":true,"name":"rangeNode","wires":[["helperNode1"]]},
 | 
			
		||||
                    {id:"helperNode1", type:"helper", wires:[]}];
 | 
			
		||||
 
 | 
			
		||||
@@ -817,105 +817,6 @@ describe('delay Node', function() {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('can part flush and reset rate limit queue', function(done) {
 | 
			
		||||
        this.timeout(2000);
 | 
			
		||||
        var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":1,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"wires":[["helperNode1"]]},
 | 
			
		||||
                    {id:"helperNode1", type:"helper", wires:[]}];
 | 
			
		||||
        helper.load(delayNode, flow, function() {
 | 
			
		||||
            var delayNode1 = helper.getNode("delayNode1");
 | 
			
		||||
            var helperNode1 = helper.getNode("helperNode1");
 | 
			
		||||
            var t = Date.now();
 | 
			
		||||
            var c = 0;
 | 
			
		||||
            helperNode1.on("input", function(msg) {
 | 
			
		||||
                // console.log("GOT",Date.now() - t,msg)
 | 
			
		||||
                msg.should.have.a.property('payload');
 | 
			
		||||
                msg.should.have.a.property('topic');
 | 
			
		||||
                try {
 | 
			
		||||
                    if (msg.topic === "foo") {
 | 
			
		||||
                        msg.payload.should.equal(1);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(0,50);
 | 
			
		||||
                        c = c + 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (msg.topic === "bar") {
 | 
			
		||||
                        msg.payload.should.equal(2);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(200,100);
 | 
			
		||||
                        c = c + 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (msg.topic === "fob") {
 | 
			
		||||
                        msg.payload.should.equal(5);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(400,100);
 | 
			
		||||
                        c = 5;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (c === 5) { done(); }
 | 
			
		||||
                } catch(e) {
 | 
			
		||||
                    done(e);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // send test messages
 | 
			
		||||
            // delayNode1.receive({payload:1,topic:"foo"});
 | 
			
		||||
            setImmediate( function() { delayNode1.receive({payload:1,topic:"foo"}); }  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:2,topic:"far"}); }, 10  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:3,topic:"boo"}); }, 20  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:4,topic:"bar"}); }, 30  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({flush:2,reset:true});  }, 200);
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:5,topic:"fob"}); }, 300  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({flush:1,reset:true});  }, 400);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('can full flush and reset rate limit queue', function(done) {
 | 
			
		||||
        this.timeout(2000);
 | 
			
		||||
        var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":1,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"wires":[["helperNode1"]]},
 | 
			
		||||
                    {id:"helperNode1", type:"helper", wires:[]}];
 | 
			
		||||
        helper.load(delayNode, flow, function() {
 | 
			
		||||
            var delayNode1 = helper.getNode("delayNode1");
 | 
			
		||||
            var helperNode1 = helper.getNode("helperNode1");
 | 
			
		||||
            var t = Date.now();
 | 
			
		||||
            var c = 0;
 | 
			
		||||
            helperNode1.on("input", function(msg) {
 | 
			
		||||
                // console.log("GOT",Date.now() - t,msg)
 | 
			
		||||
                msg.should.have.a.property('payload');
 | 
			
		||||
                msg.should.have.a.property('topic');
 | 
			
		||||
                try {
 | 
			
		||||
                    if (msg.topic === "foo") {
 | 
			
		||||
                        msg.payload.should.equal(1);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(0,50);
 | 
			
		||||
                        c = c + 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (msg.topic === "bar") {
 | 
			
		||||
                        msg.payload.should.equal(4);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(200,100);
 | 
			
		||||
                        c = c + 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (msg.topic === "all") {
 | 
			
		||||
                        msg.payload.should.equal(5);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(200,100);
 | 
			
		||||
                        c = c + 1;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (msg.topic === "fob") {
 | 
			
		||||
                        msg.payload.should.equal(6);
 | 
			
		||||
                        (Date.now() - t).should.be.approximately(400,100);
 | 
			
		||||
                        c = 5;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (c === 5) { done(); }
 | 
			
		||||
                } catch(e) {
 | 
			
		||||
                    done(e);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // send test messages
 | 
			
		||||
            // delayNode1.receive({payload:1,topic:"foo"});
 | 
			
		||||
            setImmediate( function() { delayNode1.receive({payload:1,topic:"foo"}); }  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:2,topic:"far"}); }, 10  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:3,topic:"boo"}); }, 20  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:4,topic:"bar"}); }, 30  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:5,topic:"last",flush:true,reset:true});  }, 200);
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({payload:6,topic:"fob"}); }, 300  );
 | 
			
		||||
            setTimeout( function() { delayNode1.receive({flush:1,reset:true});  }, 400);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('can part push to front of rate limit queue', function(done) {
 | 
			
		||||
        this.timeout(2000);
 | 
			
		||||
        var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":1,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
 | 
			
		||||
 
 | 
			
		||||
@@ -194,6 +194,55 @@ describe('file Nodes', function() {
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should append to a file and add newline, except last line of multipart input', function(done) {
 | 
			
		||||
            var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false, wires: [["helperNode1"]]},
 | 
			
		||||
                        {id:"helperNode1", type:"helper"}];
 | 
			
		||||
            try {
 | 
			
		||||
                fs.unlinkSync(fileToTest);
 | 
			
		||||
            } catch(err) {
 | 
			
		||||
            }
 | 
			
		||||
            helper.load(fileNode, flow, function() {
 | 
			
		||||
                var n1 = helper.getNode("fileNode1");
 | 
			
		||||
                var n2 = helper.getNode("helperNode1");
 | 
			
		||||
                var count = 0;
 | 
			
		||||
                //var data = ["Line1", "Line2"];
 | 
			
		||||
 | 
			
		||||
                n2.on("input", function (msg) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        msg.should.have.property("payload");
 | 
			
		||||
                        //data.should.containDeep([msg.payload]);
 | 
			
		||||
                        if (count === 3) {
 | 
			
		||||
                            var f = fs.readFileSync(fileToTest).toString();
 | 
			
		||||
                            if (os.type() !== "Windows_NT") {
 | 
			
		||||
                                f.should.have.length(23);
 | 
			
		||||
                                f.should.equal("Line1\nLine2\nLine3\nLine4");
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                                f.should.have.length(23);
 | 
			
		||||
                                f.should.equal("Line1\r\nLine2\r\nLine3\r\nLine4");
 | 
			
		||||
                            }
 | 
			
		||||
                            done();
 | 
			
		||||
                        }
 | 
			
		||||
                        count++;
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (e) {
 | 
			
		||||
                        done(e);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                n1.receive({payload:"Line1",parts:{index:0,type:"string"}});    // string
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    n1.receive({payload:"Line2",parts:{index:1,type:"string"}});    // string
 | 
			
		||||
                },30);
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    n1.receive({payload:"Line3",parts:{index:2,type:"string"}});    // string
 | 
			
		||||
                },60);
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    n1.receive({payload:"Line4",parts:{index:3,type:"string",count:4}});    // string
 | 
			
		||||
                },90);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should append to a file after it has been deleted ', function(done) {
 | 
			
		||||
            var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false, wires: [["helperNode1"]]},
 | 
			
		||||
                        {id:"helperNode1", type:"helper"}];
 | 
			
		||||
 
 | 
			
		||||
@@ -260,8 +260,7 @@ describe('subflow', function() {
 | 
			
		||||
                 {name: "KB", type: "bool", value: "true"},
 | 
			
		||||
                 {name: "KJ", type: "json", value: "[1,2,3]"},
 | 
			
		||||
                 {name: "Kb", type: "bin", value: "[65,65]"},
 | 
			
		||||
                 {name: "Ke", type: "env", value: "KS"},
 | 
			
		||||
                 {name: "Kj", type: "jsonata", value: "1+2"},
 | 
			
		||||
                 {name: "Ke", type: "env", value: "KS"}
 | 
			
		||||
             ],
 | 
			
		||||
             wires:[["n2"]]},
 | 
			
		||||
            {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
 | 
			
		||||
@@ -280,7 +279,7 @@ describe('subflow', function() {
 | 
			
		||||
             ]
 | 
			
		||||
            },
 | 
			
		||||
            {id:"s1-n1", x:10, y:10, z:"s1", type:"function",
 | 
			
		||||
             func:"msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;",
 | 
			
		||||
             func:"msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); return msg;",
 | 
			
		||||
             wires:[]}
 | 
			
		||||
        ];
 | 
			
		||||
        helper.load(functionNode, flow, function() {
 | 
			
		||||
@@ -295,7 +294,6 @@ describe('subflow', function() {
 | 
			
		||||
                    msg.should.have.property("Vb");
 | 
			
		||||
                    should.ok(msg.Vb instanceof Buffer);
 | 
			
		||||
                    msg.should.have.property("VE","STR");
 | 
			
		||||
                    msg.should.have.property("Vj",3);
 | 
			
		||||
                    done();
 | 
			
		||||
                }
 | 
			
		||||
                catch (e) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1322,40 +1322,6 @@ describe('Flow', function() {
 | 
			
		||||
                
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("can define environment variable using JSONata", function (done) {
 | 
			
		||||
            try {
 | 
			
		||||
                after(function() {
 | 
			
		||||
                    delete process.env.V0;
 | 
			
		||||
                })
 | 
			
		||||
                var config = flowUtils.parseConfig([
 | 
			
		||||
                    {id:"t1",type:"tab",env:[
 | 
			
		||||
                        {"name": "V0", value: "1+2", type: "jsonata"}
 | 
			
		||||
                    ]},
 | 
			
		||||
                    {id:"g1",type:"group",z:"t1",env:[
 | 
			
		||||
                        {"name": "V1", value: "2+3", type: "jsonata"},
 | 
			
		||||
                    ]},
 | 
			
		||||
                    {id:"1",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V0)",wires:[]},
 | 
			
		||||
                    {id:"2",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"$(V1)",wires:[]},
 | 
			
		||||
                ]);
 | 
			
		||||
                var flow = Flow.create({getSetting:v=>process.env[v]},config,config.flows["t1"]);
 | 
			
		||||
                flow.start();
 | 
			
		||||
 | 
			
		||||
                var activeNodes = flow.getActiveNodes();
 | 
			
		||||
 | 
			
		||||
                activeNodes["1"].foo.should.equal(3);
 | 
			
		||||
                activeNodes["2"].foo.should.equal(5);
 | 
			
		||||
 | 
			
		||||
                flow.stop().then(function() {
 | 
			
		||||
                    done();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            catch (e) {
 | 
			
		||||
                console.log(e.stack);
 | 
			
		||||
                done(e);
 | 
			
		||||
            }
 | 
			
		||||
                
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||