Compare commits
	
		
			1 Commits
		
	
	
		
			0.20.0-bet
			...
			bidi-17
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1532d9b6e2 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1 +0,0 @@ | ||||
| /packages/node_modules/** linguist-generated=false | ||||
							
								
								
									
										34
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,34 +0,0 @@ | ||||
| <!-- | ||||
| ## Before you hit that Submit button.... | ||||
|  | ||||
| This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes. | ||||
|  | ||||
| If your issue is: | ||||
|   - a general 'how-to' type question, | ||||
|   - a feature request or suggestion for a change, | ||||
|   - or problems with 3rd party (`node-red-contrib-`) nodes | ||||
|  | ||||
| please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack). | ||||
|  | ||||
| You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`. | ||||
|  | ||||
| That way the whole Node-RED user community can help, rather than rely on the core development team. | ||||
|  | ||||
| ## So you have a real issue to raise... | ||||
|  | ||||
| To help us understand the issue, please fill-in as much of the following information as you can: | ||||
| --> | ||||
|  | ||||
| ### What are the steps to reproduce? | ||||
|  | ||||
| ### What happens? | ||||
|  | ||||
| ### What do you expect to happen? | ||||
|  | ||||
| ### Please tell us about your environment: | ||||
|  | ||||
| - [ ] Node-RED version: | ||||
| - [ ] node.js version: | ||||
| - [ ] npm version: | ||||
| - [ ] Platform/OS: | ||||
| - [ ] Browser: | ||||
							
								
								
									
										34
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,34 +0,0 @@ | ||||
| <!-- | ||||
| ## Before you hit that Submit button.... | ||||
|  | ||||
| Please read our [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) | ||||
| before submitting a pull-request. | ||||
|  | ||||
| ## Types of changes | ||||
|  | ||||
| What types of changes does your code introduce? | ||||
| Put an `x` in the boxes that apply | ||||
| --> | ||||
|  | ||||
| - [ ] Bugfix (non-breaking change which fixes an issue) | ||||
| - [ ] New feature (non-breaking change which adds functionality) | ||||
|  | ||||
| <!-- | ||||
| If you want to raise a pull-request with a new feature, or a refactoring | ||||
| of existing code, it **may well get rejected** if it hasn't been discussed on | ||||
| the [forum](https://discourse.nodered.org) or | ||||
| [slack team](https://nodered.org/slack) first. | ||||
|  | ||||
| --> | ||||
|  | ||||
| ## Proposed changes | ||||
|  | ||||
| <!-- Describe the nature of this change. What problem does it address? --> | ||||
|  | ||||
| ## Checklist | ||||
| <!-- Put an `x` in the boxes that apply --> | ||||
|  | ||||
| - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) | ||||
| - [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team. | ||||
| - [ ] I have run `grunt` to verify the unit tests pass | ||||
| - [ ] I have added suitable unit tests to cover the new/changed functionality | ||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -17,8 +17,3 @@ node_modules | ||||
| public | ||||
| locales/zz-ZZ | ||||
| nodes/core/locales/zz-ZZ | ||||
| !packages/node_modules | ||||
| packages/node_modules/@node-red/editor-client/public | ||||
| !test/**/node_modules | ||||
| docs | ||||
| !packages/node_modules/**/docs | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| /Gruntfile.js | ||||
| /.git/* | ||||
| /lib/* | ||||
| *.backup | ||||
| /public/* | ||||
|   | ||||
							
								
								
									
										7
									
								
								.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| .settings | ||||
| .jshintignore | ||||
| .jshintrc | ||||
| .project | ||||
| .tern-project | ||||
| .travis.yml | ||||
| .git | ||||
							
								
								
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						| @@ -1,10 +1,21 @@ | ||||
| sudo: false | ||||
| language: node_js | ||||
| matrix: | ||||
|   include: | ||||
|     - node_js: "10" | ||||
|       script: | ||||
|         - ./node_modules/.bin/grunt && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage | ||||
|       before_script: | ||||
|         - npm install -g istanbul coveralls | ||||
|     - node_js: "8" | ||||
| env: | ||||
|   - CXX="g++-4.8" | ||||
| addons: | ||||
|   apt: | ||||
|     sources: | ||||
|     - ubuntu-toolchain-r-test | ||||
|     packages: | ||||
|     - g++-4.8 | ||||
|     - gcc-4.8 | ||||
| node_js: | ||||
|   - "8" | ||||
|   - "7" | ||||
|   - "6" | ||||
|   - "4" | ||||
| script: | ||||
|   - istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage | ||||
| before_script: | ||||
|   - npm install -g istanbul | ||||
|   - npm install coveralls | ||||
|   | ||||
							
								
								
									
										629
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -1,598 +1,3 @@ | ||||
| #### 0.20.0-beta.5: Beta Release | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Bump dependencies | ||||
|  - Allow `$parent` access of flow context | ||||
|  - Make Node.\_flow a writeable property | ||||
|  - Do not propagate Flow.getNode to parent when called from outside flow | ||||
|  - Add support of subflow env var | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - Properly sanitize node names in deploy warning dialogs | ||||
|  - Fix XSS issues in library ui code | ||||
|  - Add env type to subflow env var types | ||||
|  - Display parent subflow properties in edit dialog | ||||
|  - Fix direction value of subflow output | ||||
|  - Add Status Node to Subflow to allow subflow-specific status Closes #597 | ||||
|  - Better handling of multiple flow merges Fixes #2039 | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Various translation updates | ||||
|  - Catch: Add 'catch uncaught only' mode. Closes #1747 | ||||
|  - Link: scroll to current flow in node list | ||||
|  - HTTPRequest: add option to urlencode cookies | ||||
|  - HTTPRequest: option to use msg.payload as query params on GET. #1981 | ||||
|  - Debug: Add local time display option to numerics in debug window | ||||
|  - MQTT: Add parsed JSON output option | ||||
|  | ||||
| #### 0.20.0-beta.4: Beta Release | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Bump JSONata to 1.6.4 | ||||
|  - Add Flow.getSetting for resolving env-var properties | ||||
|  - Refactor Subflow logic into own class | ||||
|  - Restore RED.auth to node-red module api | ||||
|  - Tidy up when usage in Flow and Node | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - German translation | ||||
|  - Change default dropdown appearance and sidebar tab menu handling | ||||
|  - Handle multiple-select box when nothing selected Fixes #2021 | ||||
|  - Handle i18n properly when key is a valid sub-identifier Fixes #2028 | ||||
|  - Avoid duplicate links when missing node type installed Fixes #2032 | ||||
|  - Add View Tools | ||||
|  - Don't collapse version control header when clicking refresh | ||||
|  - Add fast entry via keyboard for string of nodes | ||||
|  - Check for undeployed change before showing open project dialog | ||||
|  - Add placeholder node when in quick-add mode | ||||
|  - Move nodes to top-left corner when converting to subflow | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Debug: Allow debug edit expression to be sent to status | ||||
|  - WebSocket: Fix missing translated help | ||||
|  | ||||
|  | ||||
| #### 0.20.0-beta.3: Beta Release | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - Update palette manager view properly when module updated | ||||
|  - Add TreeList common widget | ||||
|  - Fix visual jump when opening Comment editor on Safari Part of #2008 | ||||
|  - Fix vertical align of markdown editor in Safari Fixes #2008 | ||||
|  - Avoid marking node as changed if label state is default Fixes #2009 | ||||
|  - Highlight port on node hover while joining | ||||
|  - Support drag-wiring of link nodes | ||||
|  - Allow TypeSearch to include a filter option | ||||
|  - Improve diff colouring | ||||
|  - Allow sections to toggle in 2-element stack | ||||
|  - Add support for ${} env var syntax when skipping validation Closes #1980 | ||||
|  - i18 support for markdown editor tooltip | ||||
|  - Add RED.editor.registerTypeEditor for custom type editors | ||||
|  - Tidy up markdown toolbar handling across all editors | ||||
|  - Added validation while export into library | ||||
|  - Reuse notification boxes rather than stack multiple of the same type | ||||
|  - Make ssh key dialog accessible when opened from new proj dialog | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Bump JSONata to 1.6.4 Fixes #2023 | ||||
|  - Add audit logging to admin api | ||||
|  - Fix failure of RED.require #2010 | ||||
|  - Allow oauth strategy callback method to be customised Closes #1998 | ||||
|  - Ensure fs context cache is flushed on close Fixes #2001 | ||||
|  - Fix library Buffer( to Buffer.alloc( for node 10 | ||||
|  - Catch file-not-found on startup when non-existant flow file specified | ||||
|  - Actively expire login sesssions and notify user | ||||
|  - Add quotation marks for basic auth challenge #1976 | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Change: remove promises to improve performance | ||||
|  - Debug: add ability to apply JSONata expression to message | ||||
|  - Join: remove promises to improve performance | ||||
|  - JSON: delete msg.schema before sending msg to avoid conflicts | ||||
|  - Link: update UI to use common TreeList widget | ||||
|  - Switch: remove promises to improve performance | ||||
|  | ||||
| #### 0.20.0-beta.2: Beta Release | ||||
|  | ||||
|  - Split Node-RED internals into multiple sub-modules | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - Allow the editor to use a custom admin api url root | ||||
|  - Improve performance of Flow Diff dialog - @TothiViseo #1989 | ||||
|  - Add 'open project' option to Projects Welcome dialog | ||||
|  - Add 'type already registered' check in palette editor | ||||
|  - Handle missing tab.disabled property | ||||
|  - Handle missing wires prop and string x/y props on import | ||||
|  - Add RED.notifications.hide flag - for UI testing | ||||
|  - Improve alignment of node label edit inputs | ||||
|  - Show arrow-in node when invalid font-awesome icon name was specified for default icon | ||||
|  - Add ability to delete context values from sidebar | ||||
|  - Allow copy-to-clipboard copy whole tabs | ||||
|  - Make disabled flows more obvious in editor | ||||
|  - Allow import/export from file in editor | ||||
|  - Allow config nodes to be selected in sidebar and deleted | ||||
|  - Show port label of subflow with input port | ||||
|  - Support ctrl-click selection of flow tabs | ||||
|  - Allow left-hand node button to act as toggle | ||||
|  - Support dbl-click in tab bar to add new flow in position | ||||
|  - Fix duplicate subflow detection on import | ||||
|  - Add import notification with info on what has been imported Closes #1862 | ||||
|  - Show error details when trying to import invalid json | ||||
|  - Show default icon when non-existent font-awesome icon was specified | ||||
|  - Add configurable option for showing node label | ||||
|  - Avoid http redirects as Safari doesn't reuse Auth header Fixes #1903 | ||||
|  - Tidy up ace tooltip styling | ||||
|  - Add event log to editor | ||||
|  - Add tooltips to multiple editor elements | ||||
|  - Allow palette to be hidden | ||||
|  - Add node module into to sidebar and palette popover | ||||
|  - Mark all newly imported nodes as changed | ||||
|  - Allow a node label to be hidden | ||||
|  - Add markdown formatting toolbar | ||||
|  - Add markdown toolbar to various editors | ||||
|  - Fix i18n handling for ja-JP locale on Safari/MacOS | ||||
|  - Add node body tooltip | ||||
|  - Decrease opacity of flow-navigator | ||||
|  - Update tooltip style | ||||
|  - Update ACE to 1.4.1-src-min-noconflict | ||||
|  - Cache node locales by language | ||||
|  - Show icon element with either icon image or fa-icon | ||||
|  - Added font-awesome icons to user defined icon | ||||
|  - Update info side bar with node description section | ||||
|  - One-click search of config node users | ||||
|  - Redesign node edit dialog to tabbed style | ||||
|  - Add 'restart flows' option to deploy menu | ||||
|  - Add node description property UI | ||||
|  | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Allow a project to be loaded from cmdline | ||||
|  - Handle lookup of undefined property in Global context Fixes #1978 | ||||
|  - Refuse to enable Manage Palette if npm too old | ||||
|  - Remove restriction on upgrading non-local modules | ||||
|  - Remove deprecated Buffer constructor usage Fixes #1709 | ||||
|  - Update httpServerOptions doc in settings.js | ||||
|  - Exclude non-testable .js files from the unit tests | ||||
|  - Add --safe mode flag to allow starting without flows running | ||||
|  - Add setting-defined accessToken for automated access to the adminAPI - #1789 | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Move all core node EN help to their own locale files - #1990 | ||||
|  - CSV: better regex for number detection | ||||
|  - Debug: hide button if not configured to send to sidebar | ||||
|  - Delay: report queue activity when in by-topic mode | ||||
|  - Delay: add msg.flush mode | ||||
|  - Exec: Preserve existing properties on msg object | ||||
|  - File: remove CR/LF from incoming filename | ||||
|  - Function: create custom ace javascript mode to handle ES6 Fixes #1911 | ||||
|  - Function: add env.get | ||||
|  - HTTP Request: Add http-proxy config #1913 | ||||
|  - HTTP Request: add msg.redirectList to output | ||||
|  - HTTP Request: add msg.requestTimeout option for per-message setting - @natcl #1959 | ||||
|  - MQTT: add auto-detect and base64 output to mqtt node Fixes #1912 - @DurandA | ||||
|  - MQTT: only unsubscribe node that is being removed | ||||
|  - Sentiment: move to node-red-node-sentiment | ||||
|  - Switch: add missing edit dialog icon | ||||
|  - Tail: move to node-red-node-tail | ||||
|  - TCPGet: clear status if user changes target per message | ||||
|  - Template: tidy up edit dialog | ||||
|  - UDP: more resilient binding to correct port for udp, give input side priority | ||||
|  - Split/Join: add msg.reset to info panel | ||||
|  - Split/Join: reset join without sending part array | ||||
|  - Watch: add msg.filename so can feed direct to file in node | ||||
|  - WebSocket: preserve \_session on msg but don't send as part of wholemsg | ||||
|  | ||||
|  | ||||
| #### 0.19.5: Maintenance Release | ||||
|  | ||||
|  - Recognize pip installs of RPi.GPIO (#1934) | ||||
|  - Merge pull request #1941 from node-red-hitachi/master-batch | ||||
|  - Merge pull request #1931 from node-red-hitachi/master-typedinput | ||||
|  - Set min value of properties and spinners for batch | ||||
|  - Fix that unnecessary optionMenu remains | ||||
|  - Merge pull request #1894 from node-red-hitachi/fix-overlapping-file-node-execution | ||||
|  - Merge pull request #1924 from imZack/patch-1 | ||||
|  - Add missing comma | ||||
|  - Do not disable context sidebar during node edit Fixes #1921 | ||||
|  - Don't allow virtual links to be spliced Fixes #1920 | ||||
|  - Merge project package changes to avoid overwritten changes | ||||
|  - Handle manually added project deps that are unused Fixes #1908 | ||||
|  - update close & input handling of File node | ||||
|  - make close handler argument only one | ||||
|  - Merge pull request #1907 from amilajack/patch-2 | ||||
|  - Change repo badge to point to master branch | ||||
|  - invoke callbacks if async handler is specified | ||||
|  - Merge pull request #1891 from camlow325/resolve-example-path-for-windows-support | ||||
|  - Merge pull request #1900 from kazuhitoyokoi/master-addtestcases4settings.js | ||||
|  - wait closing while pending messages exist | ||||
|  - Add test cases for red/api/editor/settings.js | ||||
|  - Ensure all palette categories are opened properly Closes #1893 | ||||
|  - Resolve path when sending example file for Windows support | ||||
|  - fix multiple input message processing of file node | ||||
|  | ||||
| #### 0.19.4: Maintenance Release | ||||
|  | ||||
|  - Fix race condition in non-cache lfs context Fixes #1888 | ||||
|  - LocalFileSystem Context: Remove extra flush code | ||||
|  - Prevent race condition in caching mode of lfs context (#1889) | ||||
|  - Allow context store name to be provided in the key | ||||
|  - Switch node: only use promises when absolutely necessary | ||||
|  - Fix dbl-click handling on webkit-based browsers | ||||
|  - Ensure context.flow/global cannot be deleted or enumerated | ||||
|  - Handle context.get with multiple levels of unknown key Fixes #1883 | ||||
|  - Fix global.get("foo.bar") for functionGlobalContext set values | ||||
|  - Fix node color bug (#1877) | ||||
|  - Merge pull request #1857 from cclauss/patch-1 | ||||
|  - Define raw_input() in Python 3 & fix time.sleep() | ||||
|  | ||||
| #### 0.19.3: Maintenance Release | ||||
|  | ||||
|  - Split node - fix complete to send msg for k/v object | ||||
|  - Remove unused Join node merged object key typed input | ||||
|  - Set the JavaScript editor to full-screen | ||||
|  - Filter global modules installed locally | ||||
|  - Add svg to permitted icon extension list | ||||
|  - Debug node - indicate status all the time if selected to do so | ||||
|  - pi nodes - increase test coverage slightly | ||||
|  - TCP-request node - only write payload | ||||
|  - JSON schema: perform validation when obj -> obj or str -> str | ||||
|  - JSON schema: add draft-06 support (via $schema keyword) | ||||
|  - Mqtt proxy configuration for websocket connection, #1651. | ||||
|  - Allows MQTT Shared Subscriptions for MQTT-In core node | ||||
|  - Fix use of HTML tag or CSS class specification as icon of typedInput | ||||
|  | ||||
| #### 0.19.2: Maintenance Release | ||||
|  | ||||
|  - Ensure node default colour is used if palette.theme has no match | ||||
|  - fix lost messages / properties in TCPRequest Node; closes #1863 (#1864) | ||||
|  - Fix typo in template.html | ||||
|  - Improve error reporting from context plugin loading | ||||
|  - Prevent no-op edit of node marking as changed due to icon | ||||
|  - Change node must handle empty rule set | ||||
|  | ||||
| #### 0.19.1: Maintenance Release | ||||
|  | ||||
|  - Pull in latest twitter node | ||||
|  - Handle windows paths for context storage | ||||
|  - Handle persisting objects with circular refs in context | ||||
|  - Ensure js editor can expand to fill available space | ||||
|  - Add example localfilesystem contextStorage to settings | ||||
|  - Fix template node handling of nested context tags | ||||
|  | ||||
| #### 0.19: Milestone Release | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - Add editorTheme.palette.theme to allow overriding colours | ||||
|  - Index all node properties when searching Fixes #1446 | ||||
|  - Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779 | ||||
|  - Prevent horizontal scroll when palette name cannot wrap | ||||
|  - Ignore middle-click on node/ports to enable panning | ||||
|  - Better wire layout when looping back | ||||
|  - fix appearence of retry button of remote branch management dialog | ||||
|  - Handle releasing ctrl when using quick-add node dialog | ||||
|  - Add $env function to JSONata expressions | ||||
|  - Widen support for env var to use ${} or $() syntax | ||||
|  - Add env-var support to TypedInput | ||||
|  - Show unknown node properties in info tab | ||||
|  - Add node icon picker widget | ||||
|  - Only edit nodes on dbl click on primary button with no modifiers | ||||
|  - Allow subflows to be put in any palette category | ||||
|  - Add flow navigator widget | ||||
|  - Cache flow library result to improve response time Fixes #1753 | ||||
|  - Add middle-button-drag to pan the workspace | ||||
|  - allow multi-line category name in editor | ||||
|  - Redesign sidebar tabs | ||||
|  - Do not disable the export-clipboard menu option with empty selection | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - Change: Ensure runtime errors in Change node can be caught Fixes #1769 | ||||
|  - File: Add output to File Out node | ||||
|  - Function: add expandable JavaScript editor pane | ||||
|  - Function: allow id and name reference in function node code (#1731) | ||||
|  - HTTP Request: Move to request module | ||||
|  - HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278 | ||||
|  - Join: accumulate top level properties | ||||
|  - Join: allow environment variable as reduce init value | ||||
|  - JSON: add JSON schema validation via msg.schema | ||||
|  - Pi: Let nrgpio code work with python 3 | ||||
|  - Pi: let Pi nodes be visible/editable on all platforms | ||||
|  - Switch: add isEmpty rule | ||||
|  - TCP: queue messages while connecting; closes #1414 | ||||
|  - TLS: Add servername option to TLS config node for SNI Fixes #1805 | ||||
|  - UDP: Don't accidentally re-use udp port when set to not do so | ||||
|  | ||||
| Persistent Context | ||||
|  | ||||
|  - Add Context data sidebar | ||||
|  - Add persistable context option | ||||
|  - Add default memory store | ||||
|  - Add file-based context store | ||||
|  - Add async mode to evaluateJSONataExpression | ||||
|  - Update RED.util.evaluateNodeProperty to support context stores | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Support flow.disabled and .info in /flow API | ||||
|  - Node errors should be Strings not Errors Fixes #1781 | ||||
|  - Add detection of connection timeout in git communication Fixes #1770 | ||||
|  - Handle loading empty nodesDir | ||||
|  - Add 'private' property to userDir generated package.json | ||||
|  - Add RED.require to allow nodes to access other modules | ||||
|  - Ensure add/remove modules are run sequentially | ||||
|  | ||||
| #### 0.18.7: Maintenance Release | ||||
|  | ||||
| Editor Fixes | ||||
|  | ||||
|  - Do not trim wires if node declares outputs in defaults but misses value Fixes #1737 | ||||
|  | ||||
| Node Fixes | ||||
|  | ||||
|  - Relax twitter node version ready for major version bump | ||||
|  - Pass Date into the Function node sandbox to fix instanceof tests | ||||
|  - let TCP in node report remote ip and port when in single packet mode | ||||
|  - typo fix in node help (#1735) | ||||
|  | ||||
| Other Fixes | ||||
|  - Tidy up default grunt task and fixup test break due to reorder Fixes #1738 | ||||
|  - Bump jsonata version | ||||
|  | ||||
| #### 0.18.6: Maintenance Release | ||||
|  | ||||
| Editor Fixes | ||||
|  | ||||
|  - Handle a node having wires in the editor on ports it no longer has Fixes #1724 | ||||
|  - Add missing ACE snippet files | ||||
|  - Fix wireClippedNodes is not defined Fixes #1726 | ||||
|  - Split node html to isolate bad nodes when loading | ||||
|  - Avoid unnecessary use of .html() where .text() will do | ||||
|  | ||||
|  - Add editorTheme.projects.enabled to default settings.js" | ||||
|  | ||||
| #### 0.18.5: Maintenance Release | ||||
|  | ||||
| Projects | ||||
|  | ||||
|  - Add clone project to welcome screen | ||||
|  - Handle cloning a project without package.json | ||||
|  - Keep remote branch state in sync between editor and runtime | ||||
|  | ||||
| New Features | ||||
|  | ||||
|  - Add type checks to switch node options (#1714) | ||||
|  - add output property select to HTML parse node (#1701) | ||||
|  - Add Prevent Following Redirect to HTTP Request node (#615) (#1684) | ||||
|  - Add debug and trace functions to function node (#1654) | ||||
|  - Enable user defined icon for subflow | ||||
|  - Add MQTT disconnect message and rework broker node UI (#1719) | ||||
|  - Japanese message catalogue updates (#1723) | ||||
|  - Show node load errors in the Palette Manager view | ||||
|  | ||||
| Editor Fixes | ||||
|  | ||||
|  - Highlight subflow node when log msg comes from inside Fixes #1698 | ||||
|  - Ensure node wires array is not longer than outputs value Fixes #1678 | ||||
|  - Allow importing an unknown config node to be undone Fixes #1681 | ||||
|  - Ensure keyboard shortcuts get saved in runtime settings Fixes #1696 | ||||
|  - Don't mark a subflow changed when actually modified nothing (#1665) | ||||
|  | ||||
| Node Fixes | ||||
|  | ||||
|  - bind to correct port when doing udp broadcast/multicast (#1686) | ||||
|  - Provide full error stack in Function node log message (#1700) | ||||
|  - Fix http request doc type Fixes #1690 | ||||
|  - Make debug slightly larger to pass WCAG AA rating | ||||
|  - Make core nodes labels more consistent, to close #1673 | ||||
|  - Allow template node to be updated more than once Fixes #1671 | ||||
|  - Fix the problem that output labels of switch node sometimes disappear (#1664) | ||||
|  - Chinese translations for core nodes (#1607) | ||||
|  | ||||
| Runtime Fixes | ||||
|  | ||||
|  - Handle and display for invalid flow credentials when project is disabled #1689 (#1694) | ||||
|  - node-red-pi: fix behavior with old bash version (#1713) | ||||
|  - Fix ENOENT error on first start when no user dir (#1711) | ||||
|  - Handle null error object in Flow.handleError Fixes #1721 | ||||
|  - update settings comments to describe how to setup for ipv6 (#1675) | ||||
|  - Remove credential props after diffing flow to prevent future false positives Fixes #1359 | ||||
|  - Log error if settings unavailable when saving user settings Fixes #1645 | ||||
|  - Keep backup of .config.json | ||||
|  - Add warning if using \_credentialSecret from .config.json | ||||
|  - Filter req.user in /settings to prevent potentially leaking info | ||||
|  | ||||
| #### 0.18.4: Maintenance Release | ||||
|  | ||||
| Projects | ||||
|  | ||||
|  - Ensure sshkey file path is properly escaped on Windows | ||||
|  - Normalize ssh key paths for Windows file names | ||||
|  - Ensure userDir is an absolute path when used with sshkeygen | ||||
|  - Detect if there are no existing flows to migrate into a project | ||||
|  - Use relative urls when retriving flow history | ||||
|  - Add credentialSecret to clone pane | ||||
|  - Delay clearing inflight when changing credentials key | ||||
|  - Mark deploy inflight when reverting a file change | ||||
|  - Handle missing_flow_file error on clone properly | ||||
|  - Remote project from cached list on delete so it can be reused | ||||
|  - Fix tests for existing file flag in settings | ||||
|  | ||||
| Editor Fixes | ||||
|  | ||||
|  - Fix merging a remote diff | ||||
|  - Fixed the problems when using a node without defaults | ||||
|  - Disable user defined icon for subflow | ||||
|  - getDefaultNodeIcon should handle subflow instance nodes Fixes #1635 | ||||
|  - Add Japanese info text for core nodes | ||||
|  - Fix message lookup for core nodes in case of i18 locales directory exists | ||||
|  - Prevent the last tab from being deleted | ||||
|  | ||||
| Node Fixes | ||||
|  | ||||
|  - Ensure trigger gets reset when 2nd output is null | ||||
|  | ||||
|  | ||||
| #### 0.18.3: Maintenance Release | ||||
|  | ||||
| Projects | ||||
|  | ||||
|  - Fix permissions on git/ssh scripts | ||||
|  - Add support for GIT_SSH on older levels of git | ||||
|  - Handle host key verification as auth error | ||||
|  - Ensure commit list has a refs object even if empty | ||||
|  - Make git error detection case-insensitive | ||||
|  - Fix up merge conflict handling | ||||
|  - Use flow-diff when looking at flow file changes | ||||
|  | ||||
| Node Fixes | ||||
|  | ||||
|  - Ensure debug tools show for 'complete msg object' | ||||
|  - Fix msg.parts handling in concat mode of Batch node | ||||
|  | ||||
| Editor Fixes | ||||
|  | ||||
|  - Fix offset calculation when dragging node from palette | ||||
|  - Allow a library entry to use non-default node-input- prefixes | ||||
|  - Change remote-diff shortcut and add it to keymap Fixes #1628 | ||||
|  | ||||
| #### 0.18.2: Maintenance Release | ||||
|  | ||||
| Projects | ||||
|  | ||||
|  - Filter out %D from git log command for older git versions | ||||
|  - Ensure projects are created as logged in user | ||||
|  - Better error handling/reporting in project creation | ||||
|  - Add Project Settings menu option | ||||
|  - Refresh vc sidebar on remote add/remove | ||||
|  - Fix auth prompt for ssh repos | ||||
|  - Prevent http git urls from including username/pword | ||||
|  - Fix fetch auth handling on non-default remote | ||||
|  - Avoid exception if git not installed | ||||
|  - Check version of git client on startup | ||||
|  - Fix pull/push when no tracked branch | ||||
|  - Add git_pull_unrelated_history handling | ||||
|  - Handle delete of last remote in project settings | ||||
|  | ||||
| Node Fixes | ||||
|  | ||||
|  - Fix and Add some Chinese translations | ||||
|  - Update sort/batch docs | ||||
|  - Don't assume node has defaults when exporting icon property | ||||
|  - Ensure send is last thing trigger does | ||||
|  - Ensure trigger doesn't set two simultaneous timeouts | ||||
|  - Add missing property select var to HTML node | ||||
|  - Add a default keepalive to tcp client mode | ||||
|  - Move node.send in exec and httprequest nodes | ||||
|  | ||||
|  | ||||
| #### 0.18.1: Maintenance Release | ||||
|  | ||||
| Projects | ||||
|  | ||||
|  - Handle more repo clone error cases | ||||
|  - Relax validation of git urls | ||||
|  - Revalidate project name on return to project-details view | ||||
|  - Avoid unnecessary project refresh on branch-switch Fixes #1597 | ||||
|  - Add support for file:// git urls | ||||
|  - Handle project first-run without existing flow file | ||||
|  - Handle delete of last remote in project settings | ||||
|  - Add git_pull_unrelated_history handling | ||||
|  - Fix pull/push when no tracked branch | ||||
|  - Remember to disable projects in editor when git not found | ||||
|  | ||||
| Node Fixes | ||||
|  | ||||
|  - Trigger node migration - ensure bytopic not blank | ||||
|  - Add HEAD to list of methods with no body in http req node #1598 | ||||
|  - Do not include payload in GET requests Fixes #1598 | ||||
|  - Update sort/batch docs Fixes #1601 | ||||
|  - Don't assume node has defaults when exporting icon property | ||||
|  | ||||
|  | ||||
| #### 0.18: Milestone Release | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Beta: Projects - must be enabled in settings file | ||||
|  - Allow port zero for Express (#1363) | ||||
|  - Better error reporting when module provides duplicate type | ||||
|  - Update jsonata to 1.5.0 | ||||
|  - add express-session memorystore without leaks (#1435) | ||||
|  - Allow adminAuth.user to be a Function Fixes #1461 | ||||
|  - Ensure RED.server is set even if admin api disabled | ||||
|  - Ensure strategy login button uses relative URL Fixes #1481 | ||||
|  - ignore `_msgid` when merging full objects | ||||
|  - Move node install to spawn to allow for big stdout Fixes #1488 | ||||
|  - SIGINT handler should wait for stop to complete before exit | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - allow a node's icon to be set dynamically (#1490) | ||||
|  - Batch messages sent over comms to increase throughput | ||||
|  - Migrate deploy confirmations to notifications | ||||
|  - `oneditdelete` should be available to all node types Closes #1346 | ||||
|  - Sort typeSearch results based on position of match | ||||
|  - Update ACE to test and add python highlighter (#1373) | ||||
|  - Clear mouse state when typeSearch cancelled Fixes #1517 | ||||
|  - Handle scoped modules via palette editor | ||||
|  - TypedInput: handle user defined value/labels options Fixes #1549 | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - add msg. select to range and yaml nodes | ||||
|  - add property choice to xml, sentiment nodes | ||||
|  - mqtt: Add 'name' to mqtt-broker node, and label it by this if it is set. (#1364) | ||||
|  - Add option to JSON node to ensure particular encoding | ||||
|  - add parts support for HTML node (#1495) | ||||
|  - Add passphrase to TLS node | ||||
|  - Add rc property to exec node outputs 1 and 2 (#1401) | ||||
|  - Add skip first n lines capability to csv node (#1535) | ||||
|  - Add support for rejectUnauthorized msg property | ||||
|  - Add TLS options to WebSocket client | ||||
|  - Added parsed YAML support for template node (#1443) | ||||
|  - Allow delay node in rate-limit mode to be reset Fixes #1360 | ||||
|  - Allow setTimeout in Function node to be promisified in node 8 | ||||
|  - Debug to status option (#1499) | ||||
|  - enable template config via msg.template for stored or generated templates (#1503) | ||||
|  - HTTP REQUEST: Adding PROPPATCH and PROPFIND http methods (#1531) | ||||
|  - Initial support of merge & reduce mode for JOIN node (#1546) | ||||
|  - Initial support of new BATCH node (#1548) | ||||
|  - Initial support of sequence rules for SWITCH node (#1545) | ||||
|  - initial support of SORT node (#1500) | ||||
|  - Inject node - let once delay be editable (#1541) | ||||
|  - Introduce `nodeMaxMessageBufferLength` setting for msg sequence nodes | ||||
|  - Let CSV correct parts if we remove header row. | ||||
|  - let default apply if msg.delay not set in override mode. (#1397) | ||||
|  - let trigger node be reset by boolean message (#1554) | ||||
|  - Let trigger node support per topic mode (#1398) | ||||
|  - let HTML node return empty array for no matching input (#1582) | ||||
|  - MQTT node - if Server/URL config contains '//' use it as a complete url; enabled ws:// and wss:// | ||||
|  - clone messages before delayed send (#1474) | ||||
|  - Decrement connected client count rather than show disconnected | ||||
|  - Don't end mqtt client on first error Fixes #1566 | ||||
|  - File out - create dirs synchronously to ensure they exist Fixes #1489 | ||||
|  - Fix debug message format for Buffer (#1444) | ||||
|  - Fix global.keys() bug in function node (#1417) | ||||
|  - Handle escape characters in template node which uses Mustache format and JSON output mode (#1377) | ||||
|  - Move all node.send to end of timer functions in trigger node (issue #1527) (#1539) | ||||
|  - Publish null/undefined to mqtt as blank not toString Fixes #1521 | ||||
|  - remove inject node at specific time spinner | ||||
|  - restrict inject interval to less that 2^31 millisecs | ||||
|  - tag UDP ports in use properly so they get closed correctly (#1508) | ||||
|  | ||||
| #### 0.17.5: Maintenance Release | ||||
|  | ||||
|  - Add express-session missing dependency for oauth | ||||
| @@ -1484,7 +889,7 @@ Fixes | ||||
| #### 0.10.10: Maintenance Release | ||||
|  | ||||
|  - Fix permissions issue with packaged nrgpio script | ||||
|  - Add better help message if deprecated node missing | ||||
| - Add better help message if deprecated node missing | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1496,16 +901,16 @@ Fix packaging of bin scripts | ||||
|  | ||||
| #### 0.10.8: Maintenance Release | ||||
|  | ||||
|  - Nodes moved out of core | ||||
| - Nodes moved out of core | ||||
|   - still included as a dependency: twitter, serial, email, feedparser | ||||
|  - no longer included: mongo, arduino, irc, redis | ||||
|  - node icon defn can be a function | ||||
|  - http_proxy support | ||||
|  - httpNodeMiddleware setting | ||||
|  - Trigger node ui refresh | ||||
|  - editorTheme setting | ||||
|  - Warn on deploy of unused config nodes | ||||
|  - catch node prevents error loops | ||||
| - node icon defn can be a function | ||||
| - http_proxy support | ||||
| - httpNodeMiddleware setting | ||||
| - Trigger node ui refresh | ||||
| - editorTheme setting | ||||
| - Warn on deploy of unused config nodes | ||||
| - catch node prevents error loops | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1528,14 +933,14 @@ Changes: | ||||
|  | ||||
| Changes: | ||||
|  | ||||
|  - http request node passes on request url as msg.url | ||||
|  - handle config nodes appearing out of order in flow file - don't assume they are always at the start | ||||
|  - move subflow palette category to the top, to make it more obvious | ||||
|  - fix labelling of Raspberry Pi pins | ||||
|  - allow email node to mark mail as read | ||||
|  - fix saving library content | ||||
|  - add node-red and node-red-pi start scripts | ||||
|  - use $HOME/.node-red for user data unless specified otherwise (or existing data is found in install dir) | ||||
| - http request node passes on request url as msg.url | ||||
| - handle config nodes appearing out of order in flow file - don't assume they are always at the start | ||||
| - move subflow palette category to the top, to make it more obvious | ||||
| - fix labelling of Raspberry Pi pins | ||||
| - allow email node to mark mail as read | ||||
| - fix saving library content | ||||
| - add node-red and node-red-pi start scripts | ||||
| - use $HOME/.node-red for user data unless specified otherwise (or existing data is found in install dir) | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ We welcome contributions, but request you follow these guidelines. | ||||
|  | ||||
| This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/). | ||||
| By participating, you are expected to uphold this code. Please report unacceptable | ||||
| behavior to the project's core team at team@nodered.org. | ||||
| behavior to any of the [project's core team](https://github.com/orgs/node-red/teams/core). | ||||
|  | ||||
| ## Raising issues | ||||
|  | ||||
| @@ -30,13 +30,13 @@ At a minimum, please include: | ||||
|  | ||||
| ## Feature requests | ||||
|  | ||||
| For feature requests, please raise them on the [forum](https://discourse.nodered.org). | ||||
| For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red). | ||||
|  | ||||
| ## Pull-Requests | ||||
|  | ||||
| If you want to raise a pull-request with a new feature, or a refactoring | ||||
| of existing code, it may well get rejected if you haven't discussed it on | ||||
| the [forum](https://discourse.nodered.org) first. | ||||
| the [mailing list](https://groups.google.com/forum/#!forum/node-red) first. | ||||
|  | ||||
| All contributors need to sign the JS Foundation's Contributor License Agreement. | ||||
| It is an online process and quick to do. You can read the details of the agreement | ||||
|   | ||||
							
								
								
									
										394
									
								
								Gruntfile.js
									
									
									
									
									
								
							
							
						
						| @@ -15,7 +15,6 @@ | ||||
|  **/ | ||||
|  | ||||
| var path = require("path"); | ||||
| var fs = require("fs-extra"); | ||||
|  | ||||
| module.exports = function(grunt) { | ||||
|  | ||||
| @@ -25,10 +24,6 @@ module.exports = function(grunt) { | ||||
|         nodemonArgs.push(flowFile); | ||||
|     } | ||||
|  | ||||
|     var nonHeadless = grunt.option('non-headless'); | ||||
|     if (nonHeadless) { | ||||
|         process.env.NODE_RED_NON_HEADLESS = 'true'; | ||||
|     } | ||||
|     grunt.initConfig({ | ||||
|         pkg: grunt.file.readJSON('package.json'), | ||||
|         paths: { | ||||
| @@ -43,27 +38,19 @@ module.exports = function(grunt) { | ||||
|                 reporter: 'spec' | ||||
|             }, | ||||
|             all: { src: ['test/**/*_spec.js'] }, | ||||
|             core: { src: ["test/_spec.js","test/unit/**/*_spec.js"]}, | ||||
|             core: { src: ["test/_spec.js","test/red/**/*_spec.js"]}, | ||||
|             nodes: { src: ["test/nodes/**/*_spec.js"]} | ||||
|         }, | ||||
|         webdriver: { | ||||
|             all: { | ||||
|                 configFile: 'test/editor/wdio.conf.js' | ||||
|             } | ||||
|         }, | ||||
|         mocha_istanbul: { | ||||
|             options: { | ||||
|                 globals: ['expect'], | ||||
|                 timeout: 3000, | ||||
|                 ignoreLeaks: false, | ||||
|                 ui: 'bdd', | ||||
|                 reportFormats: ['lcov','html'], | ||||
|                 print: 'both', | ||||
|                 istanbulOptions: ['--no-default-excludes', '-i','**/packages/node_modules/**'] | ||||
|                 reportFormats: ['lcov'], | ||||
|                 print: 'both' | ||||
|             }, | ||||
|             all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] }, | ||||
|             core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]}, | ||||
|             nodes: { src: ["test/nodes/**/*_spec.js"]} | ||||
|             coverage: { src: ['test/**/*_spec.js'] } | ||||
|         }, | ||||
|         jshint: { | ||||
|             options: { | ||||
| @@ -82,14 +69,16 @@ module.exports = function(grunt) { | ||||
|             all: [ | ||||
|                 'Gruntfile.js', | ||||
|                 'red.js', | ||||
|                 'packages/**/*.js' | ||||
|                 'red/**/*.js', | ||||
|                 'nodes/core/*/*.js', | ||||
|                 'editor/js/**/*.js' | ||||
|             ], | ||||
|             core: { | ||||
|                 files: { | ||||
|                     src: [ | ||||
|                         'Gruntfile.js', | ||||
|                         'red.js', | ||||
|                         'packages/**/*.js', | ||||
|                         'red/**/*.js' | ||||
|                     ] | ||||
|                 } | ||||
|             }, | ||||
| @@ -120,86 +109,74 @@ module.exports = function(grunt) { | ||||
|                 src: [ | ||||
|                     // Ensure editor source files are concatenated in | ||||
|                     // the right order | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/red.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/events.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/i18n.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/settings.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/user.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/comms.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/text/bidi.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/text/format.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/state.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/nodes.js", | ||||
|                     "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/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", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/checkboxSet.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/panels.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.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/workspaces.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/view.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/palette.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/tray.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/library.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/search.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/touch/radialMenu.js" | ||||
|                     "editor/js/red.js", | ||||
|                     "editor/js/events.js", | ||||
|                     "editor/js/i18n.js", | ||||
|                     "editor/js/settings.js", | ||||
|                     "editor/js/user.js", | ||||
|                     "editor/js/comms.js", | ||||
|                     "editor/js/text/bidi.js", | ||||
|                     "editor/js/text/format.js", | ||||
|                     "editor/js/ui/state.js", | ||||
|                     "editor/js/nodes.js", | ||||
|                     "editor/js/history.js", | ||||
|                     "editor/js/validators.js", | ||||
|                     "editor/js/ui/utils.js", | ||||
|                     "editor/js/ui/common/editableList.js", | ||||
|                     "editor/js/ui/common/checkboxSet.js", | ||||
|                     "editor/js/ui/common/menu.js", | ||||
|                     "editor/js/ui/common/panels.js", | ||||
|                     "editor/js/ui/common/popover.js", | ||||
|                     "editor/js/ui/common/searchBox.js", | ||||
|                     "editor/js/ui/common/tabs.js", | ||||
|                     "editor/js/ui/common/stack.js", | ||||
|                     "editor/js/ui/common/typedInput.js", | ||||
|                     "editor/js/ui/actions.js", | ||||
|                     "editor/js/ui/deploy.js", | ||||
|                     "editor/js/ui/diff.js", | ||||
|                     "editor/js/ui/keyboard.js", | ||||
|                     "editor/js/ui/workspaces.js", | ||||
|                     "editor/js/ui/view.js", | ||||
|                     "editor/js/ui/sidebar.js", | ||||
|                     "editor/js/ui/palette.js", | ||||
|                     "editor/js/ui/tab-info.js", | ||||
|                     "editor/js/ui/tab-config.js", | ||||
|                     "editor/js/ui/palette-editor.js", | ||||
|                     "editor/js/ui/editor.js", | ||||
|                     "editor/js/ui/tray.js", | ||||
|                     "editor/js/ui/clipboard.js", | ||||
|                     "editor/js/ui/library.js", | ||||
|                     "editor/js/ui/notifications.js", | ||||
|                     "editor/js/ui/search.js", | ||||
|                     "editor/js/ui/typeSearch.js", | ||||
|                     "editor/js/ui/subflow.js", | ||||
|                     "editor/js/ui/userSettings.js", | ||||
|                     "editor/js/ui/touch/radialMenu.js" | ||||
|                 ], | ||||
|                 dest: "packages/node_modules/@node-red/editor-client/public/red/red.js" | ||||
|                 dest: "public/red/red.js" | ||||
|             }, | ||||
|             vendor: { | ||||
|                 files: { | ||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [ | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-1.11.3.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/bootstrap/js/bootstrap.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js" | ||||
|                     "public/vendor/vendor.js": [ | ||||
|                         "editor/vendor/jquery/js/jquery-1.11.3.min.js", | ||||
|                         "editor/vendor/bootstrap/js/bootstrap.min.js", | ||||
|                         "editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js", | ||||
|                         "editor/vendor/jquery/js/jquery.ui.touch-punch.min.js", | ||||
|                         "editor/vendor/marked/marked.min.js", | ||||
|                         "editor/vendor/d3/d3.v3.min.js", | ||||
|                         "editor/vendor/i18next/i18next.min.js" | ||||
|                     ], | ||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor/vendor.css": [ | ||||
|                     "public/vendor/vendor.css": [ | ||||
|                         // TODO: resolve relative resource paths in | ||||
|                         //       bootstrap/FA/jquery | ||||
|                     ], | ||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor/jsonata/jsonata.min.js": [ | ||||
|                     "public/vendor/jsonata/jsonata.min.js": [ | ||||
|                         "node_modules/jsonata/jsonata-es5.min.js", | ||||
|                         "packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js" | ||||
|                         "editor/vendor/jsonata/formatter.js" | ||||
|                     ], | ||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor/ace/worker-jsonata.js": [ | ||||
|                     "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" | ||||
|                         "editor/vendor/jsonata/worker-jsonata.js" | ||||
|                     ] | ||||
|                 } | ||||
|             } | ||||
| @@ -207,10 +184,10 @@ module.exports = function(grunt) { | ||||
|         uglify: { | ||||
|             build: { | ||||
|                 files: { | ||||
|                     'packages/node_modules/@node-red/editor-client/public/red/red.min.js': 'packages/node_modules/@node-red/editor-client/public/red/red.js', | ||||
|                     'packages/node_modules/@node-red/editor-client/public/red/main.min.js': 'packages/node_modules/@node-red/editor-client/public/red/main.js', | ||||
|                     'packages/node_modules/@node-red/editor-client/public/vendor/ace/mode-jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js', | ||||
|                     'packages/node_modules/@node-red/editor-client/public/vendor/ace/snippets/jsonata.js': 'packages/node_modules/@node-red/editor-client/src/vendor/jsonata/snippets-jsonata.js' | ||||
|                     'public/red/red.min.js': 'public/red/red.js', | ||||
|                     'public/red/main.min.js': 'public/red/main.js', | ||||
|                     'public/vendor/ace/mode-jsonata.js': 'editor/vendor/jsonata/mode-jsonata.js', | ||||
|                     'public/vendor/ace/snippets/jsonata.js': 'editor/vendor/jsonata/snippets-jsonata.js' | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| @@ -220,50 +197,50 @@ module.exports = function(grunt) { | ||||
|                     outputStyle: 'compressed' | ||||
|                 }, | ||||
|                 files: [{ | ||||
|                     dest: 'packages/node_modules/@node-red/editor-client/public/red/style.min.css', | ||||
|                     src: 'packages/node_modules/@node-red/editor-client/src/sass/style.scss' | ||||
|                     dest: 'public/red/style.min.css', | ||||
|                     src: 'editor/sass/style.scss' | ||||
|                 }, | ||||
|                 { | ||||
|                     dest: 'packages/node_modules/@node-red/editor-client/public/vendor/bootstrap/css/bootstrap.min.css', | ||||
|                     src: 'packages/node_modules/@node-red/editor-client/src/vendor/bootstrap/css/bootstrap.css' | ||||
|                     dest: 'public/vendor/bootstrap/css/bootstrap.min.css', | ||||
|                     src: 'editor/vendor/bootstrap/css/bootstrap.css' | ||||
|                 }] | ||||
|             } | ||||
|         }, | ||||
|         jsonlint: { | ||||
|             messages: { | ||||
|                 src: [ | ||||
|                     'packages/node_modules/@node-red/nodes/locales/**/*.json', | ||||
|                     'packages/node_modules/@node-red/editor-client/locales/**/*.json', | ||||
|                     'packages/node_modules/@node-red/runtime/locales/**/*.json' | ||||
|                     'nodes/core/locales/en-US/messages.json', | ||||
|                     'red/api/locales/en-US/editor.json', | ||||
|                     'red/runtime/locales/en-US/runtime.json' | ||||
|                 ] | ||||
|             }, | ||||
|             keymaps: { | ||||
|                 src: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/src/js/keymap.json' | ||||
|                     'editor/js/keymap.json' | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         attachCopyright: { | ||||
|             js: { | ||||
|                 src: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/public/red/red.min.js', | ||||
|                     'packages/node_modules/@node-red/editor-client/public/red/main.min.js' | ||||
|                     'public/red/red.min.js', | ||||
|                     'public/red/main.min.js' | ||||
|                 ] | ||||
|             }, | ||||
|             css: { | ||||
|                 src: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/public/red/style.min.css' | ||||
|                     'public/red/style.min.css' | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         clean: { | ||||
|             build: { | ||||
|                 src: [ | ||||
|                     "packages/node_modules/@node-red/editor-client/public/red", | ||||
|                     "packages/node_modules/@node-red/editor-client/public/index.html", | ||||
|                     "packages/node_modules/@node-red/editor-client/public/favicon.ico", | ||||
|                     "packages/node_modules/@node-red/editor-client/public/icons", | ||||
|                     "packages/node_modules/@node-red/editor-client/public/vendor" | ||||
|                     "public/red", | ||||
|                     "public/index.html", | ||||
|                     "public/favicon.ico", | ||||
|                     "public/icons", | ||||
|                     "public/vendor" | ||||
|                 ] | ||||
|             }, | ||||
|             release: { | ||||
| @@ -275,27 +252,27 @@ module.exports = function(grunt) { | ||||
|         watch: { | ||||
|             js: { | ||||
|                 files: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/src/js/**/*.js' | ||||
|                     'editor/js/**/*.js' | ||||
|                 ], | ||||
|                 tasks: ['copy:build','concat','uglify','attachCopyright:js'] | ||||
|             }, | ||||
|             sass: { | ||||
|                 files: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/src/sass/**/*.scss' | ||||
|                     'editor/sass/**/*.scss' | ||||
|                 ], | ||||
|                 tasks: ['sass','attachCopyright:css'] | ||||
|             }, | ||||
|             json: { | ||||
|                 files: [ | ||||
|                     'packages/node_modules/@node-red/nodes/locales/**/*.json', | ||||
|                     'packages/node_modules/@node-red/editor-client/locales/**/*.json', | ||||
|                     'packages/node_modules/@node-red/runtime/locales/**/*.json' | ||||
|                     'nodes/core/locales/en-US/messages.json', | ||||
|                     'red/api/locales/en-US/editor.json', | ||||
|                     'red/runtime/locales/en-US/runtime.json' | ||||
|                 ], | ||||
|                 tasks: ['jsonlint:messages'] | ||||
|             }, | ||||
|             keymaps: { | ||||
|                 files: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/src/js/keymap.json' | ||||
|                     'editor/js/keymap.json' | ||||
|                 ], | ||||
|                 tasks: ['jsonlint:keymaps','copy:build'] | ||||
|             }, | ||||
| @@ -310,13 +287,12 @@ module.exports = function(grunt) { | ||||
|         nodemon: { | ||||
|             /* uses .nodemonignore */ | ||||
|             dev: { | ||||
|                 script: 'packages/node_modules/node-red/red.js', | ||||
|                 script: 'red.js', | ||||
|                 options: { | ||||
|                     args: nodemonArgs, | ||||
|                     ext: 'js,html,json', | ||||
|                     watch: [ | ||||
|                         'packages/node_modules', | ||||
|                         '!packages/node_modules/@node-red/editor-client' | ||||
|                         'red','nodes' | ||||
|                     ] | ||||
|                 } | ||||
|             } | ||||
| @@ -335,21 +311,21 @@ module.exports = function(grunt) { | ||||
|             build: { | ||||
|                 files:[ | ||||
|                     { | ||||
|                         src: 'packages/node_modules/@node-red/editor-client/src/js/main.js', | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/red/main.js' | ||||
|                         src: 'editor/js/main.js', | ||||
|                         dest: 'public/red/main.js' | ||||
|                     }, | ||||
|                     { | ||||
|                         src: 'packages/node_modules/@node-red/editor-client/src/js/keymap.json', | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/red/keymap.json' | ||||
|                         src: 'editor/js/keymap.json', | ||||
|                         dest: 'public/red/keymap.json' | ||||
|                     }, | ||||
|                     { | ||||
|                         cwd: 'packages/node_modules/@node-red/editor-client/src/images', | ||||
|                         cwd: 'editor/images', | ||||
|                         src: '**', | ||||
|                         expand: true, | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/red/images/' | ||||
|                         dest: 'public/red/images/' | ||||
|                     }, | ||||
|                     { | ||||
|                         cwd: 'packages/node_modules/@node-red/editor-client/src/vendor', | ||||
|                         cwd: 'editor/vendor', | ||||
|                         src: [ | ||||
|                             'ace/**', | ||||
|                             //'bootstrap/css/**', | ||||
| @@ -358,35 +334,46 @@ module.exports = function(grunt) { | ||||
|                             'font-awesome/**' | ||||
|                         ], | ||||
|                         expand: true, | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/vendor/' | ||||
|                         dest: 'public/vendor/' | ||||
|                     }, | ||||
|                     { | ||||
|                         cwd: 'packages/node_modules/@node-red/editor-client/src/icons', | ||||
|                         cwd: 'editor/icons', | ||||
|                         src: '**', | ||||
|                         expand: true, | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/icons/' | ||||
|                         dest: 'public/icons/' | ||||
|                     }, | ||||
|                     { | ||||
|                         expand: true, | ||||
|                         src: ['packages/node_modules/@node-red/editor-client/src/index.html','packages/node_modules/@node-red/editor-client/src/favicon.ico'], | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/', | ||||
|                         src: ['editor/index.html','editor/favicon.ico'], | ||||
|                         dest: 'public/', | ||||
|                         flatten: true | ||||
|                     }, | ||||
|                     { | ||||
|                         src: 'CHANGELOG.md', | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/red/about' | ||||
|                     }, | ||||
|                     { | ||||
|                         src: 'CHANGELOG.md', | ||||
|                         dest: 'packages/node_modules/node-red/' | ||||
|                     }, | ||||
|                     { | ||||
|                         cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/', | ||||
|                         src: '**', | ||||
|                         expand: true, | ||||
|                         dest: 'packages/node_modules/@node-red/editor-client/public/vendor/ace/' | ||||
|                         dest: 'public/red/about' | ||||
|                     } | ||||
|                 ] | ||||
|             }, | ||||
|             release: { | ||||
|                 files: [{ | ||||
|                     mode: true, | ||||
|                     expand: true, | ||||
|                     src: [ | ||||
|                         '*.md', | ||||
|                         'LICENSE', | ||||
|                         'package.json', | ||||
|                         'settings.js', | ||||
|                         'red.js', | ||||
|                         'lib/.gitignore', | ||||
|                         'nodes/*.demo', | ||||
|                         'nodes/core/**', | ||||
|                         'red/**', | ||||
|                         'public/**', | ||||
|                         'editor/templates/**', | ||||
|                         'bin/**' | ||||
|                     ], | ||||
|                     dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>') | ||||
|                 }] | ||||
|             } | ||||
|         }, | ||||
|         chmod: { | ||||
| @@ -394,92 +381,20 @@ module.exports = function(grunt) { | ||||
|                 mode: '755' | ||||
|             }, | ||||
|             release: { | ||||
|                 // Target-specific file/dir lists and/or options go here. | ||||
|                 src: [ | ||||
|                     "packages/node_modules/@node-red/nodes/core/hardware/nrgpio", | ||||
|                     "packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-*sh" | ||||
|                     path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>/nodes/core/hardware/nrgpio*') | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         'npm-command': { | ||||
|             options: { | ||||
|                 cmd: "pack", | ||||
|                 cwd: "<%= paths.dist %>/modules" | ||||
|             }, | ||||
|             'node-red': { options: { args: [__dirname+'/packages/node_modules/node-red'] } }, | ||||
|             '@node-red/editor-api': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-api'] } }, | ||||
|             '@node-red/editor-client': { options: { args: [__dirname+'/packages/node_modules/@node-red/editor-client'] } }, | ||||
|             '@node-red/nodes': { options: { args: [__dirname+'/packages/node_modules/@node-red/nodes'] } }, | ||||
|             '@node-red/registry': { options: { args: [__dirname+'/packages/node_modules/@node-red/registry'] } }, | ||||
|             '@node-red/runtime': { options: { args: [__dirname+'/packages/node_modules/@node-red/runtime'] } }, | ||||
|             '@node-red/util': { options: { args: [__dirname+'/packages/node_modules/@node-red/util'] } } | ||||
|  | ||||
|  | ||||
|         }, | ||||
|         mkdir: { | ||||
|             release: { | ||||
|                 options: { | ||||
|                     create: ['<%= paths.dist %>/modules'] | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         compress: { | ||||
|             release: { | ||||
|                 options: { | ||||
|                     archive: '<%= paths.dist %>/node-red-<%= pkg.version %>.zip' | ||||
|                 }, | ||||
|                 expand: true, | ||||
|                 cwd: 'packages/node_modules/', | ||||
|                 src: [ | ||||
|                     '**', | ||||
|                     '!@node-red/editor-client/src/**' | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         jsdoc : { | ||||
|             modules: { | ||||
|                 src: [ | ||||
|                     'packages/node_modules/node-red/lib/red.js', | ||||
|                     'packages/node_modules/@node-red/runtime/lib/index.js', | ||||
|                     'packages/node_modules/@node-red/runtime/lib/api/*.js', | ||||
|                     'packages/node_modules/@node-red/runtime/lib/events.js', | ||||
|                     'packages/node_modules/@node-red/util/**/*.js', | ||||
|                     'packages/node_modules/@node-red/editor-api/lib/index.js', | ||||
|                     'packages/node_modules/@node-red/editor-api/lib/auth/index.js' | ||||
|                 ], | ||||
|                 options: { | ||||
|                     destination: 'docs', | ||||
|                     configure: './jsdoc.json' | ||||
|                 } | ||||
|             }, | ||||
|             editor: { | ||||
|                 src: [ | ||||
|                     'packages/node_modules/@node-red/editor-client/src/js' | ||||
|                     ], | ||||
|                 options: { | ||||
|                     destination: 'packages/node_modules/@node-red/editor-client/docs', | ||||
|                     configure: './jsdoc.json' | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         }, | ||||
|         jsdoc2md: { | ||||
|             runtimeAPI: { | ||||
|                 options: { | ||||
|                     separators: true | ||||
|                 }, | ||||
|                 src: [ | ||||
|                     'packages/node_modules/@node-red/runtime/lib/index.js', | ||||
|                     'packages/node_modules/@node-red/runtime/lib/api/*.js', | ||||
|                     'packages/node_modules/@node-red/runtime/lib/events.js' | ||||
|                 ], | ||||
|                 dest: 'packages/node_modules/@node-red/runtime/docs/api.md' | ||||
|             }, | ||||
|             nodeREDUtil: { | ||||
|                 options: { | ||||
|                     separators: true | ||||
|                 }, | ||||
|                 src: 'packages/node_modules/@node-red/util/**/*.js', | ||||
|                 dest: 'packages/node_modules/@node-red/util/docs/api.md' | ||||
|                 cwd: '<%= paths.dist %>/', | ||||
|                 src: ['node-red-<%= pkg.version %>/**'] | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| @@ -498,11 +413,6 @@ module.exports = function(grunt) { | ||||
|     grunt.loadNpmTasks('grunt-chmod'); | ||||
|     grunt.loadNpmTasks('grunt-jsonlint'); | ||||
|     grunt.loadNpmTasks('grunt-mocha-istanbul'); | ||||
|     grunt.loadNpmTasks('grunt-webdriver'); | ||||
|     grunt.loadNpmTasks('grunt-jsdoc'); | ||||
|     grunt.loadNpmTasks('grunt-jsdoc-to-markdown'); | ||||
|     grunt.loadNpmTasks('grunt-npm-command'); | ||||
|     grunt.loadNpmTasks('grunt-mkdir'); | ||||
|  | ||||
|     grunt.registerMultiTask('attachCopyright', function() { | ||||
|         var files = this.data.src; | ||||
| @@ -544,25 +454,6 @@ module.exports = function(grunt) { | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     grunt.registerTask('verifyPackageDependencies', function() { | ||||
|         var done = this.async(); | ||||
|         var verifyDependencies = require("./scripts/verify-package-dependencies.js"); | ||||
|         verifyDependencies().then(function(failures) { | ||||
|             if (failures.length > 0) { | ||||
|                 failures.forEach(f => grunt.log.error(f)); | ||||
|                 grunt.fail.fatal("Failed to verify package dependencies"); | ||||
|             } | ||||
|             done(); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     grunt.registerTask('verifyUiTestDependencies', function() { | ||||
|         if (!fs.existsSync(path.join("node_modules", "chromedriver"))) { | ||||
|             grunt.fail.fatal('You need to run "npm install chromedriver@2" before running UI test.'); | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     grunt.registerTask('setDevEnv', | ||||
|         'Sets NODE_ENV=development so non-minified assets are used', | ||||
|             function () { | ||||
| @@ -571,23 +462,19 @@ module.exports = function(grunt) { | ||||
|  | ||||
|     grunt.registerTask('default', | ||||
|         'Builds editor content then runs code style checks and unit tests on all components', | ||||
|         ['build','verifyPackageDependencies','jshint:editor','mocha_istanbul:all']); | ||||
|         ['build','test-core','test-editor','test-nodes']); | ||||
|  | ||||
|     grunt.registerTask('test-core', | ||||
|         'Runs code style check and unit tests on core runtime code', | ||||
|         ['build','mocha_istanbul:core']); | ||||
|         ['jshint:core','simplemocha:core']); | ||||
|  | ||||
|     grunt.registerTask('test-editor', | ||||
|         'Runs code style check on editor code', | ||||
|         ['jshint:editor']); | ||||
|  | ||||
|     grunt.registerTask('test-ui', | ||||
|         'Builds editor content then runs unit tests on editor ui', | ||||
|         ['verifyUiTestDependencies','build','jshint:editor','webdriver:all']); | ||||
|  | ||||
|     grunt.registerTask('test-nodes', | ||||
|         'Runs unit tests on core nodes', | ||||
|         ['build','mocha_istanbul:nodes']); | ||||
|         ['simplemocha:nodes']); | ||||
|  | ||||
|     grunt.registerTask('build', | ||||
|         'Builds editor content', | ||||
| @@ -599,18 +486,9 @@ module.exports = function(grunt) { | ||||
|  | ||||
|     grunt.registerTask('release', | ||||
|         'Create distribution zip file', | ||||
|         ['build','verifyPackageDependencies','clean:release','mkdir:release','chmod:release','compress:release','pack-modules']); | ||||
|  | ||||
|     grunt.registerTask('pack-modules', | ||||
|         'Create module pack files for release', | ||||
|         ['mkdir:release','npm-command']); | ||||
|  | ||||
|         ['build','clean:release','copy:release','chmod:release','compress:release']); | ||||
|  | ||||
|     grunt.registerTask('coverage', | ||||
|         'Run Istanbul code test coverage task', | ||||
|         ['build','mocha_istanbul:all']); | ||||
|  | ||||
|     grunt.registerTask('docs', | ||||
|         'Generates API documentation', | ||||
|         ['jsdoc','jsdoc2md']); | ||||
|         ['build','mocha_istanbul']); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| http://nodered.org | ||||
|  | ||||
| [](https://travis-ci.org/node-red/node-red) | ||||
| [](https://travis-ci.org/node-red/node-red) | ||||
| [](https://coveralls.io/r/node-red/node-red?branch=master) | ||||
|  | ||||
| A visual tool for wiring the Internet of Things. | ||||
| @@ -14,7 +14,7 @@ A visual tool for wiring the Internet of Things. | ||||
| Check out http://nodered.org/docs/getting-started/ for full instructions on getting | ||||
| started. | ||||
|  | ||||
| 1. `sudo npm install -g --unsafe-perm node-red` | ||||
| 1. `sudo npm install -g node-red` | ||||
| 2. `node-red` | ||||
| 3. Open <http://localhost:1880> | ||||
|  | ||||
| @@ -22,7 +22,8 @@ started. | ||||
|  | ||||
| More documentation can be found [here](http://nodered.org/docs). | ||||
|  | ||||
| For further help, or general discussion, please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack). | ||||
| For further help, or general discussion, please use the | ||||
| [mailing list](https://groups.google.com/forum/#!forum/node-red). | ||||
|  | ||||
| ## Developers | ||||
|  | ||||
| @@ -44,6 +45,9 @@ If you want to run the latest code from git, here's how to get started: | ||||
| 4. Run | ||||
|  | ||||
|         npm start | ||||
|    or | ||||
|  | ||||
|         node red.js | ||||
|  | ||||
| ## Contributing | ||||
|  | ||||
| @@ -52,7 +56,7 @@ Before raising a pull-request, please read our | ||||
|  | ||||
| This project adheres to the [Contributor Covenant 1.4](http://contributor-covenant.org/version/1/4/). | ||||
|  By participating, you are expected to uphold this code. Please report unacceptable | ||||
|  behavior to any of the project's core team at team@nodered.org. | ||||
|  behavior to any of the [project's core team](https://github.com/orgs/node-red/teams/core). | ||||
|  | ||||
| ## Authors | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ done | ||||
| # Find the real location of this script | ||||
| CURRENT_PATH=`pwd` | ||||
| SCRIPT_PATH="${BASH_SOURCE[0]}"; | ||||
| while [ -h "${SCRIPT_PATH}" ]; do | ||||
| while([ -h "${SCRIPT_PATH}" ]); do | ||||
|     cd "`dirname "${SCRIPT_PATH}"`" | ||||
|     SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; | ||||
| done | ||||
| Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB | 
| Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 308 B | 
| Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 603 B | 
| Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 393 B | 
| Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB | 
| Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 508 B | 
| Before Width: | Height: | Size: 575 B After Width: | Height: | Size: 575 B | 
| Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 493 B | 
| Before Width: | Height: | Size: 601 B After Width: | Height: | Size: 601 B | 
| Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 459 B | 
| Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 218 B | 
| Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B | 
| Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B | 
| Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 255 B | 
| Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 457 B | 
| Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 502 B | 
| Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 449 B | 
| Before Width: | Height: | Size: 253 B After Width: | Height: | Size: 253 B | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 639 B After Width: | Height: | Size: 639 B | 
| Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 402 B | 
| Before Width: | Height: | Size: 414 B After Width: | Height: | Size: 414 B | 
| Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 671 B | 
| Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B | 
| Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B | 
| Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 413 B | 
| Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 393 B | 
| Before Width: | Height: | Size: 467 B After Width: | Height: | Size: 467 B | 
| Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 393 B | 
| Before Width: | Height: | Size: 423 B After Width: | Height: | Size: 423 B | 
| Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 360 B | 
| Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 736 B | 
| Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 482 B | 
| Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B | 
| Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B | 
| Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 439 B | 
| Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 592 B | 
| Before Width: | Height: | Size: 509 B After Width: | Height: | Size: 509 B | 
| Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B | 
| Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 628 B | 
| Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 258 B | 
| Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 404 B | 
| Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B | 
| Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 707 B | 
| Before Width: | Height: | Size: 291 B After Width: | Height: | Size: 291 B | 
| Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B | 
| Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 289 B | 
| Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 368 B | 
| Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B | 
| Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B | 
| Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B | 
| Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB | 
| Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB | 
| Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB | 
| Before Width: | Height: | Size: 1019 B After Width: | Height: | Size: 1019 B | 
| Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B | 
| Before Width: | Height: | Size: 410 B After Width: | Height: | Size: 410 B | 
| Before Width: | Height: | Size: 638 B After Width: | Height: | Size: 638 B | 
| Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B | 
| Before Width: | Height: | Size: 638 B After Width: | Height: | Size: 638 B | 
| Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 646 B | 
| Before Width: | Height: | Size: 563 B After Width: | Height: | Size: 563 B | 
| Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 588 B | 
| Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 502 B | 
| @@ -28,15 +28,6 @@ RED.comms = (function() { | ||||
| 
 | ||||
|     function connectWS() { | ||||
|         active = true; | ||||
|         var wspath; | ||||
| 
 | ||||
|         if (RED.settings.apiRootUrl) { | ||||
|             var m = /^(https?):\/\/(.*)$/.exec(RED.settings.apiRootUrl); | ||||
|             if (m) { | ||||
|                 console.log(m); | ||||
|                 wspath = "ws"+(m[1]==="https"?"s":"")+"://"+m[2]+"comms"; | ||||
|             } | ||||
|         } else { | ||||
|         var path = location.hostname; | ||||
|         var port = location.port; | ||||
|         if (port.length !== 0) { | ||||
| @@ -44,8 +35,7 @@ RED.comms = (function() { | ||||
|         } | ||||
|         path = path+document.location.pathname; | ||||
|         path = path+(path.slice(-1) == "/"?"":"/")+"comms"; | ||||
|             wspath = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path; | ||||
|         } | ||||
|         path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path; | ||||
| 
 | ||||
|         var auth_tokens = RED.settings.get("auth-tokens"); | ||||
|         pendingAuth = (auth_tokens!=null); | ||||
| @@ -58,7 +48,7 @@ RED.comms = (function() { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         ws = new WebSocket(wspath); | ||||
|         ws = new WebSocket(path); | ||||
|         ws.onopen = function() { | ||||
|             reconnectAttempts = 0; | ||||
|             if (errornotification) { | ||||
| @@ -74,31 +64,19 @@ RED.comms = (function() { | ||||
|             } | ||||
|         } | ||||
|         ws.onmessage = function(event) { | ||||
|             var message = JSON.parse(event.data); | ||||
|             if (message.auth) { | ||||
|                 if (pendingAuth) { | ||||
|                     if (message.auth === "ok") { | ||||
|             var msg = JSON.parse(event.data); | ||||
|             if (pendingAuth && msg.auth) { | ||||
|                 if (msg.auth === "ok") { | ||||
|                     pendingAuth = false; | ||||
|                     completeConnection(); | ||||
|                     } else if (message.auth === "fail") { | ||||
|                 } else if (msg.auth === "fail") { | ||||
|                     // anything else is an error...
 | ||||
|                     active = false; | ||||
|                     RED.user.login({updateMenu:true},function() { | ||||
|                         connectWS(); | ||||
|                     }) | ||||
|                 } | ||||
|                 } else if (message.auth === "fail") { | ||||
|                     // Our current session has expired
 | ||||
|                     active = false; | ||||
|                     RED.user.login({updateMenu:true},function() { | ||||
|                         connectWS(); | ||||
|                     }) | ||||
|                 } | ||||
|             } else { | ||||
|                 // Otherwise, 'message' is an array of actual comms messages
 | ||||
|                 for (var m = 0; m < message.length; m++) { | ||||
|                     var msg = message[m]; | ||||
|                     if (msg.topic) { | ||||
|             } else if (msg.topic) { | ||||
|                 for (var t in subscriptions) { | ||||
|                     if (subscriptions.hasOwnProperty(t)) { | ||||
|                         var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); | ||||
| @@ -113,8 +91,6 @@ RED.comms = (function() { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         ws.onclose = function() { | ||||
|             if (!active) { | ||||
| @@ -142,10 +118,10 @@ RED.comms = (function() { | ||||
|                         connectWS(); | ||||
|                     } else { | ||||
|                         var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>'; | ||||
|                         errornotification.update(msg,{silent:true}); | ||||
|                         errornotification.update(msg); | ||||
|                         $(errornotification).find("a").click(function(e) { | ||||
|                             e.preventDefault(); | ||||
|                             errornotification.update(RED._("notification.errors.lostConnection"),{silent:true}); | ||||
|                             errornotification.update(RED._("notification.errors.lostConnection")); | ||||
|                             clearInterval(connectCountdownTimer); | ||||
|                             connectWS(); | ||||
|                         }) | ||||
| @@ -91,15 +91,12 @@ RED.history = (function() { | ||||
|             } else if (ev.t == "delete") { | ||||
|                 if (ev.workspaces) { | ||||
|                     for (i=0;i<ev.workspaces.length;i++) { | ||||
|                         RED.nodes.addWorkspace(ev.workspaces[i],ev.workspaces[i]._index); | ||||
|                         RED.workspaces.add(ev.workspaces[i],undefined,ev.workspaces[i]._index); | ||||
|                         delete ev.workspaces[i]._index; | ||||
|                         RED.nodes.addWorkspace(ev.workspaces[i]); | ||||
|                         RED.workspaces.add(ev.workspaces[i]); | ||||
|                     } | ||||
|                 } | ||||
|                 if (ev.subflows) { | ||||
|                     for (i=0;i<ev.subflows.length;i++) { | ||||
|                         RED.nodes.addSubflow(ev.subflows[i]); | ||||
|                     } | ||||
|                 if (ev.subflow && ev.subflow.subflow) { | ||||
|                     RED.nodes.addSubflow(ev.subflow.subflow); | ||||
|                 } | ||||
|                 if (ev.subflowInputs && ev.subflowInputs.length > 0) { | ||||
|                     subflow = RED.nodes.subflow(ev.subflowInputs[0].z); | ||||
| @@ -125,8 +122,7 @@ RED.history = (function() { | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|                 if (ev.subflow) { | ||||
|                     if (ev.subflow.hasOwnProperty('instances')) { | ||||
|                 if (ev.subflow && ev.subflow.hasOwnProperty('instances')) { | ||||
|                     ev.subflow.instances.forEach(function(n) { | ||||
|                         var node = RED.nodes.node(n.id); | ||||
|                         if (node) { | ||||
| @@ -135,11 +131,6 @@ RED.history = (function() { | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|                     if (ev.subflow.hasOwnProperty('status')) { | ||||
|                         subflow = RED.nodes.subflow(ev.subflow.id); | ||||
|                         subflow.status = ev.subflow.status; | ||||
|                     } | ||||
|                 } | ||||
|                 if (subflow) { | ||||
|                     RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { | ||||
|                         n.inputs = subflow.in.length; | ||||
| @@ -200,7 +191,7 @@ RED.history = (function() { | ||||
|             } else if (ev.t == "edit") { | ||||
|                 for (i in ev.changes) { | ||||
|                     if (ev.changes.hasOwnProperty(i)) { | ||||
|                         if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) { | ||||
|                         if (ev.node._def.defaults[i] && ev.node._def.defaults[i].type) { | ||||
|                             // This is a config node property
 | ||||
|                             var currentConfigNode = RED.nodes.node(ev.node[i]); | ||||
|                             if (currentConfigNode) { | ||||
| @@ -238,17 +229,10 @@ RED.history = (function() { | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                     if (ev.subflow.hasOwnProperty('status')) { | ||||
|                         if (ev.subflow.status) { | ||||
|                             delete ev.node.status; | ||||
|                         } | ||||
|                     } | ||||
|                     RED.editor.validateNode(ev.node); | ||||
|                     RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { | ||||
|                         n.inputs = ev.node.in.length; | ||||
|                         n.outputs = ev.node.out.length; | ||||
|                         RED.editor.updateNodeProperties(n); | ||||
|                         RED.editor.validateNode(n); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     var outputMap; | ||||
| @@ -273,8 +257,6 @@ RED.history = (function() { | ||||
|             } else if (ev.t == "createSubflow") { | ||||
|                 if (ev.nodes) { | ||||
|                     RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) { | ||||
|                         n.x += ev.subflow.offsetX; | ||||
|                         n.y += ev.subflow.offsetY; | ||||
|                         n.z = ev.activeWorkspace; | ||||
|                         n.dirty = true; | ||||
|                     }); | ||||
| @@ -301,7 +283,6 @@ RED.history = (function() { | ||||
|                     RED.workspaces.order(ev.order); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             Object.keys(modifiedTabs).forEach(function(id) { | ||||
|                 var subflow = RED.nodes.subflow(id); | ||||
|                 if (subflow) { | ||||
| @@ -310,12 +291,10 @@ RED.history = (function() { | ||||
|             }); | ||||
| 
 | ||||
|             RED.nodes.dirty(ev.dirty); | ||||
|             RED.view.select(null); | ||||
|             RED.view.redraw(true); | ||||
|             RED.palette.refresh(); | ||||
|             RED.workspaces.refresh(); | ||||
|             RED.sidebar.config.refresh(); | ||||
|             RED.subflow.refresh(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| @@ -342,9 +321,6 @@ RED.history = (function() { | ||||
|         }, | ||||
|         peek: function() { | ||||
|             return undo_history[undo_history.length-1]; | ||||
|         }, | ||||
|         clear: function() { | ||||
|             undo_history = []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -16,13 +16,10 @@ | ||||
| 
 | ||||
| RED.i18n = (function() { | ||||
| 
 | ||||
|     var apiRootUrl; | ||||
| 
 | ||||
|     return { | ||||
|         init: function(options, done) { | ||||
|             apiRootUrl = options.apiRootUrl||""; | ||||
|         init: function(done) { | ||||
|             i18n.init({ | ||||
|                 resGetPath: apiRootUrl+'locales/__ns__?lng=__lng__', | ||||
|                 resGetPath: 'locales/__ns__?lng=__lng__', | ||||
|                 dynamicLoad: false, | ||||
|                 load:'current', | ||||
|                 ns: { | ||||
| @@ -30,22 +27,16 @@ RED.i18n = (function() { | ||||
|                     defaultNs: "editor" | ||||
|                 }, | ||||
|                 fallbackLng: ['en-US'], | ||||
|                 useCookie: false, | ||||
|                 returnObjectTrees: true | ||||
|                 useCookie: false | ||||
|             },function() { | ||||
|                 done(); | ||||
|             }); | ||||
|             RED["_"] = function() { | ||||
|                 var v = i18n.t.apply(null,arguments); | ||||
|                 if (typeof v === 'string') { | ||||
|                     return v; | ||||
|                 } else { | ||||
|                     return arguments[0]; | ||||
|                 } | ||||
|                 return i18n.t.apply(null,arguments); | ||||
|             } | ||||
| 
 | ||||
|         }, | ||||
|         loadNodeCatalog: function(namespace,done) { | ||||
|         loadCatalog: function(namespace,done) { | ||||
|             var languageList = i18n.functions.toLanguages(i18n.detectLanguage()); | ||||
|             var toLoad = languageList.length; | ||||
|             languageList.forEach(function(lang) { | ||||
| @@ -54,7 +45,7 @@ RED.i18n = (function() { | ||||
|                         "Accept":"application/json" | ||||
|                     }, | ||||
|                     cache: false, | ||||
|                     url: apiRootUrl+'nodes/'+namespace+'/messages?lng='+lang, | ||||
|                     url: 'locales/'+namespace+'?lng='+lang, | ||||
|                     success: function(data) { | ||||
|                         i18n.addResourceBundle(lang,namespace,data); | ||||
|                         toLoad--; | ||||
| @@ -77,7 +68,7 @@ RED.i18n = (function() { | ||||
|                         "Accept":"application/json" | ||||
|                     }, | ||||
|                     cache: false, | ||||
|                     url: apiRootUrl+'nodes/messages?lng='+lang, | ||||
|                     url: 'locales/nodes?lng='+lang, | ||||
|                     success: function(data) { | ||||
|                         var namespaces = Object.keys(data); | ||||
|                         namespaces.forEach(function(ns) { | ||||
| @@ -10,23 +10,10 @@ | ||||
|         "ctrl-g i": "core:show-info-tab", | ||||
|         "ctrl-g d": "core:show-debug-tab", | ||||
|         "ctrl-g c": "core:show-config-tab", | ||||
|         "ctrl-g x": "core:show-context-tab", | ||||
|         "ctrl-e": "core:show-export-dialog", | ||||
|         "ctrl-i": "core:show-import-dialog", | ||||
|         "ctrl-space": "core:toggle-sidebar", | ||||
|         "ctrl-p": "core:toggle-palette", | ||||
|         "ctrl-,": "core:show-user-settings", | ||||
|         "ctrl-alt-r": "core:show-remote-diff", | ||||
|         "ctrl-alt-n": "core:new-project", | ||||
|         "ctrl-alt-o": "core:open-project", | ||||
|         "ctrl-g v": "core:show-version-control-tab", | ||||
|         "ctrl-shift-l": "core:show-event-log" | ||||
|     }, | ||||
|     "sidebar-node-config": { | ||||
|         "backspace": "core:delete-config-selection", | ||||
|         "delete": "core:delete-config-selection", | ||||
|         "ctrl-a": "core:select-all-config-nodes", | ||||
|         "ctrl-z": "core:undo" | ||||
|         "ctrl-,": "core:show-user-settings" | ||||
|     }, | ||||
|     "workspace": { | ||||
|         "backspace": "core:delete-selection", | ||||
							
								
								
									
										277
									
								
								editor/js/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,277 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
| (function() { | ||||
|  | ||||
|     function loadNodeList() { | ||||
|         $.ajax({ | ||||
|             headers: { | ||||
|                 "Accept":"application/json" | ||||
|             }, | ||||
|             cache: false, | ||||
|             url: 'nodes', | ||||
|             success: function(data) { | ||||
|                 RED.nodes.setNodeList(data); | ||||
|                 RED.i18n.loadNodeCatalogs(loadNodes); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function loadNodes() { | ||||
|         $.ajax({ | ||||
|             headers: { | ||||
|                 "Accept":"text/html" | ||||
|             }, | ||||
|             cache: false, | ||||
|             url: 'nodes', | ||||
|             success: function(data) { | ||||
|                 $("body").append(data); | ||||
|                 $("body").i18n(); | ||||
|                 $("#palette > .palette-spinner").hide(); | ||||
|                 $(".palette-scroll").removeClass("hide"); | ||||
|                 $("#palette-search").removeClass("hide"); | ||||
|                 loadFlows(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function loadFlows() { | ||||
|         $.ajax({ | ||||
|             headers: { | ||||
|                 "Accept":"application/json", | ||||
|             }, | ||||
|             cache: false, | ||||
|             url: 'flows', | ||||
|             success: function(nodes) { | ||||
|                 var currentHash = window.location.hash; | ||||
|                 RED.nodes.version(nodes.rev); | ||||
|                 RED.nodes.import(nodes.flows); | ||||
|                 RED.nodes.dirty(false); | ||||
|                 RED.view.redraw(true); | ||||
|                 if (/^#flow\/.+$/.test(currentHash)) { | ||||
|                     RED.workspaces.show(currentHash.substring(6)); | ||||
|                 } | ||||
|  | ||||
|                 var persistentNotifications = {}; | ||||
|                 RED.comms.subscribe("notification/#",function(topic,msg) { | ||||
|                     var parts = topic.split("/"); | ||||
|                     var notificationId = parts[1]; | ||||
|                     if (notificationId === "runtime-deploy") { | ||||
|                         // handled in ui/deploy.js | ||||
|                         return; | ||||
|                     } | ||||
|                     if (notificationId === "node") { | ||||
|                         // handled below | ||||
|                         return; | ||||
|                     } | ||||
|                     if (msg.text) { | ||||
|                         var text = RED._(msg.text,{default:msg.text}); | ||||
|                         if (!persistentNotifications.hasOwnProperty(notificationId)) { | ||||
|                             persistentNotifications[notificationId] = RED.notify(text,msg.type,msg.timeout === undefined,msg.timeout); | ||||
|                         } else { | ||||
|                             persistentNotifications[notificationId].update(text,msg.timeout); | ||||
|                         } | ||||
|                     } else if (persistentNotifications.hasOwnProperty(notificationId)) { | ||||
|                         persistentNotifications[notificationId].close(); | ||||
|                         delete persistentNotifications[notificationId]; | ||||
|                     } | ||||
|                 }); | ||||
|                 RED.comms.subscribe("status/#",function(topic,msg) { | ||||
|                     var parts = topic.split("/"); | ||||
|                     var node = RED.nodes.node(parts[1]); | ||||
|                     if (node) { | ||||
|                         if (msg.hasOwnProperty("text")) { | ||||
|                             if (msg.text[0] !== ".") { | ||||
|                                 msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()}); | ||||
|                             } | ||||
|                         } | ||||
|                         node.status = msg; | ||||
|                         node.dirty = true; | ||||
|                         RED.view.redraw(); | ||||
|                     } | ||||
|                 }); | ||||
|                 RED.comms.subscribe("notification/node/#",function(topic,msg) { | ||||
|                     var i,m; | ||||
|                     var typeList; | ||||
|                     var info; | ||||
|                     if (topic == "notification/node/added") { | ||||
|                         var addedTypes = []; | ||||
|                         msg.forEach(function(m) { | ||||
|                             var id = m.id; | ||||
|                             RED.nodes.addNodeSet(m); | ||||
|                             addedTypes = addedTypes.concat(m.types); | ||||
|                             RED.i18n.loadCatalog(id, function() { | ||||
|                                 $.get('nodes/'+id, function(data) { | ||||
|                                     $("body").append(data); | ||||
|                                 }); | ||||
|                             }); | ||||
|                         }); | ||||
|                         if (addedTypes.length) { | ||||
|                             typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>"; | ||||
|                             RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success"); | ||||
|                         } | ||||
|                     } else if (topic == "notification/node/removed") { | ||||
|                         for (i=0;i<msg.length;i++) { | ||||
|                             m = msg[i]; | ||||
|                             info = RED.nodes.removeNodeSet(m.id); | ||||
|                             if (info.added) { | ||||
|                                 typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>"; | ||||
|                                 RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success"); | ||||
|                             } | ||||
|                         } | ||||
|                     } else if (topic == "notification/node/enabled") { | ||||
|                         if (msg.types) { | ||||
|                             info = RED.nodes.getNodeSet(msg.id); | ||||
|                             if (info.added) { | ||||
|                                 RED.nodes.enableNodeSet(msg.id); | ||||
|                                 typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; | ||||
|                                 RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success"); | ||||
|                             } else { | ||||
|                                 $.get('nodes/'+msg.id, function(data) { | ||||
|                                     $("body").append(data); | ||||
|                                     typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; | ||||
|                                     RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success"); | ||||
|                                 }); | ||||
|                             } | ||||
|                         } | ||||
|                     } else if (topic == "notification/node/disabled") { | ||||
|                         if (msg.types) { | ||||
|                             RED.nodes.disableNodeSet(msg.id); | ||||
|                             typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; | ||||
|                             RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success"); | ||||
|                         } | ||||
|                     } else if (topic == "node/upgraded") { | ||||
|                         RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success"); | ||||
|                         RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version); | ||||
|                     } | ||||
|                     // Refresh flow library to ensure any examples are updated | ||||
|                     RED.library.loadFlowLibrary(); | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function showAbout() { | ||||
|         $.get('red/about', function(data) { | ||||
|             var aboutHeader = '<div style="text-align:center;">'+ | ||||
|                                 '<img width="50px" src="red/images/node-red-icon.svg" />'+ | ||||
|                               '</div>'; | ||||
|  | ||||
|             RED.sidebar.info.set(aboutHeader+marked(data)); | ||||
|             RED.sidebar.info.show(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function loadEditor() { | ||||
|         var menuOptions = []; | ||||
|         menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[ | ||||
|             // {id:"menu-item-view-show-grid",setting:"view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"}, | ||||
|             // {id:"menu-item-view-snap-grid",setting:"view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"}, | ||||
|             // {id:"menu-item-status",setting:"node-show-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true}, | ||||
|             //null, | ||||
|             // {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[ | ||||
|             //     {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}}, | ||||
|             //     {id:"menu-item-bidi-ltr",toggle:"text-direction",label:RED._("menu.label.view.ltr"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("ltr")}}}, | ||||
|             //     {id:"menu-item-bidi-rtl",toggle:"text-direction",label:RED._("menu.label.view.rtl"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("rtl")}}}, | ||||
|             //     {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}} | ||||
|             // ]}, | ||||
|             // null, | ||||
|             {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true}, | ||||
|             null | ||||
|         ]}); | ||||
|         menuOptions.push(null); | ||||
|         menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[ | ||||
|             {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"}, | ||||
|             {id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]} | ||||
|         ]}); | ||||
|         menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[ | ||||
|             {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:"core:show-export-dialog"}, | ||||
|             {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"} | ||||
|         ]}); | ||||
|         menuOptions.push(null); | ||||
|         menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"}); | ||||
|         menuOptions.push(null); | ||||
|         menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"}); | ||||
|         menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[ | ||||
|             {id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"}, | ||||
|             {id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"}, | ||||
|             {id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"} | ||||
|         ]}); | ||||
|         menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [ | ||||
|             {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"}, | ||||
|             {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"}, | ||||
|         ]}); | ||||
|         menuOptions.push(null); | ||||
|         if (RED.settings.theme('palette.editable') !== false) { | ||||
|             menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); | ||||
|             menuOptions.push(null); | ||||
|         } | ||||
|  | ||||
|         menuOptions.push({id:"menu-item-user-settings",label:RED._("menu.label.settings"),onselect:"core:show-user-settings"}); | ||||
|         menuOptions.push(null); | ||||
|  | ||||
|         menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"}); | ||||
|         menuOptions.push({id:"menu-item-help", | ||||
|             label: RED.settings.theme("menu.menu-item-help.label",RED._("menu.label.help")), | ||||
|             href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs") | ||||
|         }); | ||||
|         menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" }); | ||||
|  | ||||
|  | ||||
|         RED.view.init(); | ||||
|         RED.userSettings.init(); | ||||
|         RED.user.init(); | ||||
|         RED.library.init(); | ||||
|         RED.keyboard.init(); | ||||
|         RED.palette.init(); | ||||
|         if (RED.settings.theme('palette.editable') !== false) { | ||||
|             RED.palette.editor.init(); | ||||
|         } | ||||
|  | ||||
|         RED.sidebar.init(); | ||||
|         RED.subflow.init(); | ||||
|         RED.workspaces.init(); | ||||
|         RED.clipboard.init(); | ||||
|         RED.search.init(); | ||||
|         RED.editor.init(); | ||||
|         RED.diff.init(); | ||||
|  | ||||
|         RED.menu.init({id:"btn-sidemenu",options: menuOptions}); | ||||
|  | ||||
|         RED.deploy.init(RED.settings.theme("deployButton",null)); | ||||
|  | ||||
|         RED.actions.add("core:show-about", showAbout); | ||||
|         RED.nodes.init(); | ||||
|         RED.comms.connect(); | ||||
|  | ||||
|         $("#main-container").show(); | ||||
|         $(".header-toolbar").show(); | ||||
|  | ||||
|         loadNodeList(); | ||||
|     } | ||||
|  | ||||
|     $(function() { | ||||
|  | ||||
|         if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) { | ||||
|             document.title = document.title+" : "+window.location.hostname; | ||||
|         } | ||||
|  | ||||
|         ace.require("ace/ext/language_tools"); | ||||
|  | ||||
|         RED.i18n.init(function() { | ||||
|             RED.settings.init(loadEditor); | ||||
|         }) | ||||
|     }); | ||||
| })(); | ||||
| @@ -40,7 +40,6 @@ RED.nodes = (function() { | ||||
|         var nodeSets = {}; | ||||
|         var typeToId = {}; | ||||
|         var nodeDefinitions = {}; | ||||
|         var iconSets = {}; | ||||
| 
 | ||||
|         nodeDefinitions['tab'] = { | ||||
|             defaults: { | ||||
| @@ -133,7 +132,7 @@ RED.nodes = (function() { | ||||
|             registerNodeType: function(nt,def) { | ||||
|                 nodeDefinitions[nt] = def; | ||||
|                 def.type = nt; | ||||
|                 if (nt.substring(0,8) != "subflow:") { | ||||
|                 if (def.category != "subflows") { | ||||
|                     def.set = nodeSets[typeToId[nt]]; | ||||
|                     nodeSets[typeToId[nt]].added = true; | ||||
|                     nodeSets[typeToId[nt]].enabled = true; | ||||
| @@ -171,13 +170,6 @@ RED.nodes = (function() { | ||||
|             }, | ||||
|             getNodeType: function(nt) { | ||||
|                 return nodeDefinitions[nt]; | ||||
|             }, | ||||
|             setIconSets: function(sets) { | ||||
|                 iconSets = sets; | ||||
|                 iconSets["font-awesome"] = RED.nodes.fontAwesome.getIconList(); | ||||
|             }, | ||||
|             getIconSets: function() { | ||||
|                 return iconSets; | ||||
|             } | ||||
|         }; | ||||
|         return exports; | ||||
| @@ -273,19 +265,10 @@ RED.nodes = (function() { | ||||
|                 if (updatedConfigNode) { | ||||
|                     RED.workspaces.refresh(); | ||||
|                 } | ||||
|                 try { | ||||
|                     if (node._def.oneditdelete) { | ||||
|                         node._def.oneditdelete.call(node); | ||||
|                     } | ||||
|                 } catch(err) { | ||||
|                     console.log("oneditdelete",node.id,node.type,err.toString()); | ||||
|                 } | ||||
|                 RED.events.emit('nodes:remove',node); | ||||
|             } | ||||
|         } | ||||
|         if (node && node._def.onremove) { | ||||
|             // Deprecated: never documented but used by some early nodes
 | ||||
|             console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditremove - please report"); | ||||
|             node._def.onremove.call(n); | ||||
|         } | ||||
|         return {links:removedLinks,nodes:removedNodes}; | ||||
| @@ -298,14 +281,10 @@ RED.nodes = (function() { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function addWorkspace(ws,targetIndex) { | ||||
|     function addWorkspace(ws) { | ||||
|         workspaces[ws.id] = ws; | ||||
|         ws._def = RED.nodes.getType('tab'); | ||||
|         if (targetIndex === undefined) { | ||||
|         workspacesOrder.push(ws.id); | ||||
|         } else { | ||||
|             workspacesOrder.splice(targetIndex,0,ws.id); | ||||
|         } | ||||
|     } | ||||
|     function getWorkspace(id) { | ||||
|         return workspaces[id]; | ||||
| @@ -358,12 +337,10 @@ RED.nodes = (function() { | ||||
|         } | ||||
|         subflows[sf.id] = sf; | ||||
|         RED.nodes.registerType("subflow:"+sf.id, { | ||||
|             defaults:{ | ||||
|                 name:{value:""}, | ||||
|                 env:{value:[]} | ||||
|             }, | ||||
|             icon: function() { return sf.icon||"subflow.png" }, | ||||
|             category: sf.category || "subflows", | ||||
|             defaults:{name:{value:""}}, | ||||
|             info: sf.info, | ||||
|             icon:"subflow.png", | ||||
|             category: "subflows", | ||||
|             inputs: sf.in.length, | ||||
|             outputs: sf.out.length, | ||||
|             color: "#da9", | ||||
| @@ -372,16 +349,6 @@ RED.nodes = (function() { | ||||
|             paletteLabel: function() { return RED.nodes.subflow(sf.id).name }, | ||||
|             inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, | ||||
|             outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, | ||||
|             oneditresize: function(size) { | ||||
|                 var rows = $("#dialog-form>div:not(.node-input-env-container-row)"); | ||||
|                 var height = size.height; | ||||
|                 for (var i=0; i<rows.size(); i++) { | ||||
|                     height -= $(rows[i]).outerHeight(true); | ||||
|                 } | ||||
|                 var editorRow = $("#dialog-form>div.node-input-env-container-row"); | ||||
|                 height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); | ||||
|                 $("#node-input-env-container").editableList('height',height-80); | ||||
|             }, | ||||
|             set:{ | ||||
|                 module: "node-red" | ||||
|             } | ||||
| @@ -508,11 +475,9 @@ RED.nodes = (function() { | ||||
|             for (var j=0;j<wires.length;j++) { | ||||
|                 var w = wires[j]; | ||||
|                 if (w.target.type != "subflow") { | ||||
|                     if (w.sourcePort < node.wires.length) { | ||||
|                     node.wires[w.sourcePort].push(w.target.id); | ||||
|                 } | ||||
|             } | ||||
|             } | ||||
| 
 | ||||
|             if (n.inputs > 0 && n.inputLabels && !/^\s*$/.test(n.inputLabels.join("")))  { | ||||
|                 node.inputLabels = n.inputLabels.slice(); | ||||
| @@ -520,21 +485,6 @@ RED.nodes = (function() { | ||||
|             if (n.outputs > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) { | ||||
|                 node.outputLabels = n.outputLabels.slice(); | ||||
|             } | ||||
|             if ((!n._def.defaults || !n._def.defaults.hasOwnProperty("icon")) && n.icon) { | ||||
|                 var defIcon = RED.utils.getDefaultNodeIcon(n._def, n); | ||||
|                 if (n.icon !== defIcon.module+"/"+defIcon.file) { | ||||
|                     node.icon = n.icon; | ||||
|                 } | ||||
|             } | ||||
|             if ((!n._def.defaults || !n._def.defaults.hasOwnProperty("l")) && n.hasOwnProperty('l')) { | ||||
|                 var isLink = /^link (in|out)$/.test(node.type); | ||||
|                 if (isLink == n.l) { | ||||
|                     node.l = n.l; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (n.info) { | ||||
|             node.info = n.info; | ||||
|         } | ||||
|         return node; | ||||
|     } | ||||
| @@ -545,10 +495,8 @@ RED.nodes = (function() { | ||||
|         node.type = n.type; | ||||
|         node.name = n.name; | ||||
|         node.info = n.info; | ||||
|         node.category = n.category; | ||||
|         node.in = []; | ||||
|         node.out = []; | ||||
|         node.env = n.env; | ||||
| 
 | ||||
|         n.in.forEach(function(p) { | ||||
|             var nIn = {x:p.x,y:p.y,wires:[]}; | ||||
| @@ -580,23 +528,7 @@ RED.nodes = (function() { | ||||
|         if (node.out.length > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) { | ||||
|             node.outputLabels = n.outputLabels.slice(); | ||||
|         } | ||||
|         if (n.icon) { | ||||
|             if (n.icon !== "node-red/subflow.png") { | ||||
|                 node.icon = n.icon; | ||||
|             } | ||||
|         } | ||||
|         if (n.status) { | ||||
|             node.status = {x: n.status.x, y: n.status.y, wires:[]}; | ||||
|             links.forEach(function(d) { | ||||
|                 if (d.target === n.status) { | ||||
|                     if (d.source.type != "subflow") { | ||||
|                         node.status.wires.push({id:d.source.id, port:d.sourcePort}) | ||||
|                     } else { | ||||
|                         node.status.wires.push({id:n.id, port:0}) | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return node; | ||||
|     } | ||||
| @@ -679,9 +611,9 @@ RED.nodes = (function() { | ||||
|     } | ||||
| 
 | ||||
|     function checkForMatchingSubflow(subflow,subflowNodes) { | ||||
|         subflowNodes = subflowNodes || []; | ||||
|         var i; | ||||
|         var match = null; | ||||
|         try { | ||||
|             RED.nodes.eachSubflow(function(sf) { | ||||
|                 if (sf.name != subflow.name || | ||||
|                     sf.info != subflow.info || | ||||
| @@ -710,8 +642,11 @@ RED.nodes = (function() { | ||||
|                 } | ||||
| 
 | ||||
|                 match = sf; | ||||
|             return false; | ||||
|                 throw new Error(); | ||||
|             }); | ||||
|         } catch(err) { | ||||
|             console.log(err.stack); | ||||
|         } | ||||
|         return match; | ||||
|     } | ||||
|     function compareNodes(nodeA,nodeB,idMustMatch) { | ||||
| @@ -766,9 +701,7 @@ RED.nodes = (function() { | ||||
|         if (!$.isArray(newNodes)) { | ||||
|             newNodes = [newNodes]; | ||||
|         } | ||||
|         var isInitialLoad = false; | ||||
|         if (!initialLoad) { | ||||
|             isInitialLoad = true; | ||||
|             initialLoad = JSON.parse(JSON.stringify(newNodes)); | ||||
|         } | ||||
|         var unknownTypes = []; | ||||
| @@ -789,9 +722,10 @@ RED.nodes = (function() { | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|         if (!isInitialLoad && unknownTypes.length > 0) { | ||||
|         if (unknownTypes.length > 0) { | ||||
|             var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>"; | ||||
|             RED.notify("<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,"error",false,10000); | ||||
|             var type = "type"+(unknownTypes.length > 1?"s":""); | ||||
|             RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000); | ||||
|         } | ||||
| 
 | ||||
|         var activeWorkspace = RED.workspaces.active(); | ||||
| @@ -877,12 +811,6 @@ RED.nodes = (function() { | ||||
|                         output.i = i; | ||||
|                         output.id = getID(); | ||||
|                     }); | ||||
|                     if (n.status) { | ||||
|                         n.status.type = "subflow"; | ||||
|                         n.status.direction = "status"; | ||||
|                         n.status.z = n.id; | ||||
|                         n.status.id = getID(); | ||||
|                     } | ||||
|                     new_subflows.push(n); | ||||
|                     addSubflow(n,createNewIds); | ||||
|                 } | ||||
| @@ -944,15 +872,8 @@ RED.nodes = (function() { | ||||
| 
 | ||||
|                 } | ||||
| 
 | ||||
|                 if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
 | ||||
|                     configNode = { | ||||
|                         id:n.id, | ||||
|                         z:n.z, | ||||
|                         type:n.type, | ||||
|                         info: n.info, | ||||
|                         users:[], | ||||
|                         _config:{} | ||||
|                     }; | ||||
|                 if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) {
 | ||||
|                     configNode = {id:n.id, z:n.z, type:n.type, users:[], _config:{}}; | ||||
|                     for (d in def.defaults) { | ||||
|                         if (def.defaults.hasOwnProperty(d)) { | ||||
|                             configNode[d] = n[d]; | ||||
| @@ -987,21 +908,16 @@ RED.nodes = (function() { | ||||
|                 def = registry.getNodeType(n.type); | ||||
|                 if (!def || def.category != "config") { | ||||
|                     var node = { | ||||
|                         x:parseFloat(n.x || 0), | ||||
|                         y:parseFloat(n.y || 0), | ||||
|                         x:n.x, | ||||
|                         y:n.y, | ||||
|                         z:n.z, | ||||
|                         type:0, | ||||
|                         wires:n.wires||[], | ||||
|                         wires:n.wires, | ||||
|                         inputLabels: n.inputLabels, | ||||
|                         outputLabels: n.outputLabels, | ||||
|                         icon: n.icon, | ||||
|                         info: n.info, | ||||
|                         changed:false, | ||||
|                         _config:{} | ||||
|                     }; | ||||
|                     if (n.hasOwnProperty('l')) { | ||||
|                         node.l = n.l; | ||||
|                     } | ||||
|                     if (createNewIds) { | ||||
|                         if (subflow_blacklist[n.z]) { | ||||
|                             continue; | ||||
| @@ -1050,7 +966,6 @@ RED.nodes = (function() { | ||||
|                         node.name = n.name; | ||||
|                         node.outputs = subflow.out.length; | ||||
|                         node.inputs = subflow.in.length; | ||||
|                         node.env = n.env; | ||||
|                     } else { | ||||
|                         if (!node._def) { | ||||
|                             if (node.x && node.y) { | ||||
| @@ -1068,13 +983,6 @@ RED.nodes = (function() { | ||||
|                                     set: registry.getNodeSet("node-red/unknown") | ||||
|                                 }; | ||||
|                                 node.users = []; | ||||
|                                 // This is a config node, so delete the default
 | ||||
|                                 // non-config node properties
 | ||||
|                                 delete node.x; | ||||
|                                 delete node.y; | ||||
|                                 delete node.wires; | ||||
|                                 delete node.inputLabels; | ||||
|                                 delete node.outputLabels; | ||||
|                             } | ||||
|                             var orig = {}; | ||||
|                             for (var p in n) { | ||||
| @@ -1087,31 +995,10 @@ RED.nodes = (function() { | ||||
|                             node.type = "unknown"; | ||||
|                         } | ||||
|                         if (node._def.category != "config") { | ||||
|                             if (n.hasOwnProperty('inputs')) { | ||||
|                                 node.inputs = n.inputs; | ||||
|                                 node._config.inputs = JSON.stringify(n.inputs); | ||||
|                             } else { | ||||
|                                 node.inputs = node._def.inputs; | ||||
|                             } | ||||
|                             if (n.hasOwnProperty('outputs')) { | ||||
|                                 node.outputs = n.outputs; | ||||
|                                 node._config.outputs = JSON.stringify(n.outputs); | ||||
|                             } else { | ||||
|                                 node.outputs = node._def.outputs; | ||||
|                             } | ||||
|                             if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) { | ||||
|                                 if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) { | ||||
|                                     // If 'wires' is longer than outputs, clip wires
 | ||||
|                                     console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs); | ||||
|                                     node.wires = node.wires.slice(0,node.outputs); | ||||
|                                 } else { | ||||
|                                     // The node declares outputs in its defaults, but has not got a valid value
 | ||||
|                                     // Defer to the length of the wires array
 | ||||
|                                     node.outputs = node.wires.length; | ||||
|                                 } | ||||
|                             } | ||||
|                             node.inputs = n.inputs||node._def.inputs; | ||||
|                             node.outputs = n.outputs||node._def.outputs; | ||||
|                             for (d in node._def.defaults) { | ||||
|                                 if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') { | ||||
|                                 if (node._def.defaults.hasOwnProperty(d)) { | ||||
|                                     node[d] = n[d]; | ||||
|                                     node._config[d] = JSON.stringify(n[d]); | ||||
|                                 } | ||||
| @@ -1131,9 +1018,7 @@ RED.nodes = (function() { | ||||
|                     addNode(node); | ||||
|                     RED.editor.validateNode(node); | ||||
|                     node_map[n.id] = node; | ||||
|                     // If an 'unknown' config node, it will not have been caught by the
 | ||||
|                     // proper config node handling, so needs adding to new_nodes here
 | ||||
|                     if (node.type === "unknown" || node._def.category !== "config") { | ||||
|                     if (node._def.category != "config") { | ||||
|                         new_nodes.push(node); | ||||
|                     } | ||||
|                 } | ||||
| @@ -1222,19 +1107,6 @@ RED.nodes = (function() { | ||||
|                 }); | ||||
|                 delete output.wires; | ||||
|             }); | ||||
|             if (n.status) { | ||||
|                 n.status.wires.forEach(function(wire) { | ||||
|                     var link; | ||||
|                     if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) { | ||||
|                         link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status}; | ||||
|                     } else { | ||||
|                         link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status}; | ||||
|                     } | ||||
|                     addLink(link); | ||||
|                     new_links.push(link); | ||||
|                 }); | ||||
|                 delete n.status.wires; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         RED.workspaces.refresh(); | ||||
| @@ -1328,13 +1200,12 @@ RED.nodes = (function() { | ||||
|             RED.workspaces.remove(workspaces[id]); | ||||
|         }); | ||||
|         defaultWorkspace = null; | ||||
|         initialLoad = null; | ||||
|         RED.nodes.dirty(false); | ||||
| 
 | ||||
|         RED.nodes.dirty(true); | ||||
|         RED.view.redraw(true); | ||||
|         RED.palette.refresh(); | ||||
|         RED.workspaces.refresh(); | ||||
|         RED.sidebar.config.refresh(); | ||||
|         RED.sidebar.info.refresh(); | ||||
| 
 | ||||
|         // var node_defs = {};
 | ||||
|         // var nodes = [];
 | ||||
| @@ -1352,23 +1223,21 @@ RED.nodes = (function() { | ||||
|             RED.events.on("registry:node-type-added",function(type) { | ||||
|                 var def = registry.getNodeType(type); | ||||
|                 var replaced = false; | ||||
|                 var replaceNodes = {}; | ||||
|                 var replaceNodes = []; | ||||
|                 RED.nodes.eachNode(function(n) { | ||||
|                     if (n.type === "unknown" && n.name === type) { | ||||
|                         replaceNodes[n.id] = n; | ||||
|                         replaceNodes.push(n); | ||||
|                     } | ||||
|                 }); | ||||
|                 RED.nodes.eachConfig(function(n) { | ||||
|                     if (n.type === "unknown" && n.name === type) { | ||||
|                         replaceNodes[n.id] = n; | ||||
|                         replaceNodes.push(n); | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|                 var replaceNodeIds = Object.keys(replaceNodes); | ||||
|                 if (replaceNodeIds.length > 0) { | ||||
|                 if (replaceNodes.length > 0) { | ||||
|                     var reimportList = []; | ||||
|                     replaceNodeIds.forEach(function(id) { | ||||
|                         var n = replaceNodes[id]; | ||||
|                     replaceNodes.forEach(function(n) { | ||||
|                         if (configNodes.hasOwnProperty(n.id)) { | ||||
|                             delete configNodes[n.id]; | ||||
|                         } else { | ||||
| @@ -1376,18 +1245,6 @@ RED.nodes = (function() { | ||||
|                         } | ||||
|                         reimportList.push(convertNode(n)); | ||||
|                     }); | ||||
| 
 | ||||
|                     // Remove any links between nodes that are going to be reimported.
 | ||||
|                     // This prevents a duplicate link from being added.
 | ||||
|                     var removeLinks = []; | ||||
|                     RED.nodes.eachLink(function(l) { | ||||
|                         if (replaceNodes.hasOwnProperty(l.source.id) && replaceNodes.hasOwnProperty(l.target.id)) { | ||||
|                             removeLinks.push(l); | ||||
|                         } | ||||
|                     }); | ||||
|                     removeLinks.forEach(removeLink); | ||||
| 
 | ||||
| 
 | ||||
|                     RED.view.redraw(true); | ||||
|                     var result = importNodes(reimportList,false); | ||||
|                     var newNodeMap = {}; | ||||
| @@ -1415,9 +1272,6 @@ RED.nodes = (function() { | ||||
|         enableNodeSet: registry.enableNodeSet, | ||||
|         disableNodeSet: registry.disableNodeSet, | ||||
| 
 | ||||
|         setIconSets: registry.setIconSets, | ||||
|         getIconSets: registry.getIconSets, | ||||
| 
 | ||||
|         registerType: registry.registerNodeType, | ||||
|         getType: registry.getNodeType, | ||||
|         convertNode: convertNode, | ||||
| @@ -1442,41 +1296,31 @@ RED.nodes = (function() { | ||||
| 
 | ||||
|         eachNode: function(cb) { | ||||
|             for (var n=0;n<nodes.length;n++) { | ||||
|                 if (cb(nodes[n]) === false) { | ||||
|                     break; | ||||
|                 } | ||||
|                 cb(nodes[n]); | ||||
|             } | ||||
|         }, | ||||
|         eachLink: function(cb) { | ||||
|             for (var l=0;l<links.length;l++) { | ||||
|                 if (cb(links[l]) === false) { | ||||
|                     break; | ||||
|                 } | ||||
|                 cb(links[l]); | ||||
|             } | ||||
|         }, | ||||
|         eachConfig: function(cb) { | ||||
|             for (var id in configNodes) { | ||||
|                 if (configNodes.hasOwnProperty(id)) { | ||||
|                     if (cb(configNodes[id]) === false) { | ||||
|                         break; | ||||
|                     } | ||||
|                     cb(configNodes[id]); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         eachSubflow: function(cb) { | ||||
|             for (var id in subflows) { | ||||
|                 if (subflows.hasOwnProperty(id)) { | ||||
|                     if (cb(subflows[id]) === false) { | ||||
|                         break; | ||||
|                     } | ||||
|                     cb(subflows[id]); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         eachWorkspace: function(cb) { | ||||
|             for (var i=0;i<workspacesOrder.length;i++) { | ||||
|                 if (cb(workspaces[workspacesOrder[i]]) === false) { | ||||
|                     break; | ||||
|                 } | ||||
|                 cb(workspaces[workspacesOrder[i]]); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
| @@ -13,5 +13,4 @@ | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
| 
 | ||||
| module.exports = false | ||||
| var RED = {}; | ||||
| @@ -18,9 +18,6 @@ | ||||
| RED.settings = (function () { | ||||
| 
 | ||||
|     var loadedSettings = {}; | ||||
|     var userSettings = {}; | ||||
|     var settingsDirty = false; | ||||
|     var pendingSave; | ||||
| 
 | ||||
|     var hasLocalStorage = function () { | ||||
|         try { | ||||
| @@ -34,12 +31,7 @@ RED.settings = (function () { | ||||
|         if (!hasLocalStorage()) { | ||||
|             return; | ||||
|         } | ||||
|         if (key === "auth-tokens") { | ||||
|         localStorage.setItem(key, JSON.stringify(value)); | ||||
|         } else { | ||||
|             userSettings[key] = value; | ||||
|             saveUserSettings(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
| @@ -52,23 +44,14 @@ RED.settings = (function () { | ||||
|         if (!hasLocalStorage()) { | ||||
|             return undefined; | ||||
|         } | ||||
|         if (key === "auth-tokens") { | ||||
|         return JSON.parse(localStorage.getItem(key)); | ||||
|         } else { | ||||
|             return userSettings[key]; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     var remove = function (key) { | ||||
|         if (!hasLocalStorage()) { | ||||
|             return; | ||||
|         } | ||||
|         if (key === "auth-tokens") { | ||||
|         localStorage.removeItem(key); | ||||
|         } else { | ||||
|             delete userSettings[key]; | ||||
|             saveUserSettings(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     var setProperties = function(data) { | ||||
| @@ -85,26 +68,18 @@ RED.settings = (function () { | ||||
|         loadedSettings = data; | ||||
|     }; | ||||
| 
 | ||||
|     var setUserSettings = function(data) { | ||||
|         userSettings = data; | ||||
|     } | ||||
| 
 | ||||
|     var init = function (options, done) { | ||||
|     var init = function (done) { | ||||
|         var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search); | ||||
|         if (accessTokenMatch) { | ||||
|             var accessToken = accessTokenMatch[1]; | ||||
|             RED.settings.set("auth-tokens",{access_token: accessToken}); | ||||
|             window.location.search = ""; | ||||
|         } | ||||
|         RED.settings.apiRootUrl = options.apiRootUrl; | ||||
| 
 | ||||
|         $.ajaxSetup({ | ||||
|             beforeSend: function(jqXHR,settings) { | ||||
|                 // Only attach auth header for requests to relative paths
 | ||||
|                 if (!/^\s*(https?:|\/|\.)/.test(settings.url)) { | ||||
|                     if (options.apiRootUrl) { | ||||
|                         settings.url = options.apiRootUrl+settings.url; | ||||
|                     } | ||||
|                     var auth_tokens = RED.settings.get("auth-tokens"); | ||||
|                     if (auth_tokens) { | ||||
|                         jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token); | ||||
| @@ -131,7 +106,7 @@ RED.settings = (function () { | ||||
|                     RED.settings.remove("auth-tokens"); | ||||
|                 } | ||||
|                 console.log("Node-RED: " + data.version); | ||||
|                 loadUserSettings(done); | ||||
|                 done(); | ||||
|             }, | ||||
|             error: function(jqXHR,textStatus,errorThrown) { | ||||
|                 if (jqXHR.status === 401) { | ||||
| @@ -140,52 +115,12 @@ RED.settings = (function () { | ||||
|                     } | ||||
|                     RED.user.login(function() { load(done); }); | ||||
|                 } else { | ||||
|                     console.log("Unexpected error loading settings:",jqXHR.status,textStatus); | ||||
|                     console.log("Unexpected error:",jqXHR.status,textStatus); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     function loadUserSettings(done) { | ||||
|         $.ajax({ | ||||
|             headers: { | ||||
|                 "Accept": "application/json" | ||||
|             }, | ||||
|             dataType: "json", | ||||
|             cache: false, | ||||
|             url: 'settings/user', | ||||
|             success: function (data) { | ||||
|                 setUserSettings(data); | ||||
|                 done(); | ||||
|             }, | ||||
|             error: function(jqXHR,textStatus,errorThrown) { | ||||
|                 console.log("Unexpected error loading user settings:",jqXHR.status,textStatus); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     function saveUserSettings() { | ||||
|         if (RED.user.hasPermission("settings.write")) { | ||||
|             if (pendingSave) { | ||||
|                 clearTimeout(pendingSave); | ||||
|             } | ||||
|             pendingSave = setTimeout(function() { | ||||
|                 pendingSave = null; | ||||
|                 $.ajax({ | ||||
|                     method: 'POST', | ||||
|                     contentType: 'application/json', | ||||
|                     url: 'settings/user', | ||||
|                     data: JSON.stringify(userSettings), | ||||
|                     success: function (data) { | ||||
|                     }, | ||||
|                     error: function(jqXHR,textStatus,errorThrown) { | ||||
|                         console.log("Unexpected error saving user settings:",jqXHR.status,textStatus); | ||||
|                     } | ||||
|                 }); | ||||
|             },300); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function theme(property,defaultValue) { | ||||
|         if (!RED.settings.editorTheme) { | ||||
|             return defaultValue; | ||||
| @@ -208,10 +143,10 @@ RED.settings = (function () { | ||||
|     return { | ||||
|         init: init, | ||||
|         load: load, | ||||
|         loadUserSettings: loadUserSettings, | ||||
|         set: set, | ||||
|         get: get, | ||||
|         remove: remove, | ||||
|         theme: theme | ||||
|     } | ||||
| })(); | ||||
| }) | ||||
| (); | ||||
| @@ -16,6 +16,8 @@ | ||||
| RED.text = {}; | ||||
| RED.text.bidi = (function() { | ||||
|     var textDir = "";     | ||||
|     var textDirPref = "auto"; | ||||
|     var bidiEnabled = false; | ||||
|     var LRE = "\u202A", | ||||
|         RLE = "\u202B", | ||||
|         PDF = "\u202C"; | ||||
| @@ -110,8 +112,8 @@ RED.text.bidi = (function() { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the text direction preference | ||||
|      * @param dir - the text direction preference | ||||
|      * Sets the text direction | ||||
|      * @param dir - the actual text direction | ||||
|      */ | ||||
|     function setTextDirection(dir) { | ||||
|         textDir = dir; | ||||
| @@ -121,8 +123,43 @@ RED.text.bidi = (function() { | ||||
|         enforceTextDirectionOnPage(); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Gets the bidi enabled preference | ||||
|      */ | ||||
|     function getBidiEnabled() { | ||||
|         return bidiEnabled; | ||||
|     } | ||||
|          | ||||
|     /** | ||||
|      * Sets the bidi enabled preference | ||||
|      * @param state - the bidi enabled preference | ||||
|      */ | ||||
|     function setBidiEnabled(state) { | ||||
|         bidiEnabled = state; | ||||
|         setTextDirection((state ? textDirPref : "")); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Gets the text direction preference | ||||
|      */ | ||||
|     function getTextDirPref() {         | ||||
|         return textDirPref; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Sets the text direction preference | ||||
|      * @param dirPref - text direction preference | ||||
|      */ | ||||
|     function setTextDirPref(dirPref) { | ||||
|         textDirPref = dirPref; | ||||
|         setTextDirection(textDirPref); | ||||
|     } | ||||
|     | ||||
|     return { | ||||
|         setTextDirection: setTextDirection, | ||||
|         setBidiEnabled: setBidiEnabled, | ||||
|         setTextDirPref: setTextDirPref, | ||||
|         getBidiEnabled: getBidiEnabled, | ||||
|         getTextDirPref: getTextDirPref, | ||||
|         enforceTextDirectionWithUCC: enforceTextDirectionWithUCC, | ||||
|         resolveBaseTextDir: resolveBaseTextDir, | ||||
|         prepareInput: prepareInput | ||||
| @@ -1304,7 +1304,7 @@ RED.text.format = (function() { | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|         /*! | ||||
|         /** | ||||
|         * Returns the HTML representation of a given structured text | ||||
|         * @param text - the structured text | ||||
|         * @param type - could be one of filepath, url, email | ||||
| @@ -1315,7 +1315,7 @@ RED.text.format = (function() { | ||||
|         getHtml: function (text, type, args, isRtl, locale) { | ||||
|             return getHandler(type).format(text, args, isRtl, true, locale); | ||||
|         }, | ||||
|         /*! | ||||
|         /** | ||||
|         * Handle Structured text correct display for a given HTML element. | ||||
|         * @param element - the element  : should be of type div contenteditable=true | ||||
|         * @param type - could be one of filepath, url, email | ||||
| @@ -12,9 +12,9 @@ RED.actions = (function() { | ||||
|     function getAction(name) { | ||||
|         return actions[name]; | ||||
|     } | ||||
|     function invokeAction(name,args) { | ||||
|     function invokeAction(name) { | ||||
|         if (actions.hasOwnProperty(name)) { | ||||
|             actions[name](args); | ||||
|             actions[name](); | ||||
|         } | ||||
|     } | ||||
|     function listActions() { | ||||
| @@ -22,8 +22,6 @@ RED.clipboard = (function() { | ||||
|     var exportNodesDialog; | ||||
|     var importNodesDialog; | ||||
|     var disabled = false; | ||||
|     var popover; | ||||
|     var currentPopoverError; | ||||
| 
 | ||||
|     function setupDialogs() { | ||||
|         dialog = $('<div id="clipboard-dialog" class="hide node-red-dialog"><form class="dialog-form form-horizontal"></form></div>') | ||||
| @@ -49,21 +47,6 @@ RED.clipboard = (function() { | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "clipboard-dialog-download", | ||||
|                         class: "primary", | ||||
|                         text: RED._("clipboard.download"), | ||||
|                         click: function() { | ||||
|                             var element = document.createElement('a'); | ||||
|                             element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent($("#clipboard-export").val())); | ||||
|                             element.setAttribute('download', "flows.json"); | ||||
|                             element.style.display = 'none'; | ||||
|                             document.body.appendChild(element); | ||||
|                             element.click(); | ||||
|                             document.body.removeChild(element); | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "clipboard-dialog-copy", | ||||
|                         class: "primary", | ||||
| @@ -72,7 +55,7 @@ RED.clipboard = (function() { | ||||
|                             $("#clipboard-export").select(); | ||||
|                             document.execCommand("copy"); | ||||
|                             document.getSelection().removeAllRanges(); | ||||
|                             RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"}); | ||||
|                             RED.notify(RED._("clipboard.nodesExported")); | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     }, | ||||
| @@ -90,10 +73,6 @@ RED.clipboard = (function() { | ||||
|                     $(this).parent().find(".ui-dialog-titlebar-close").hide(); | ||||
|                 }, | ||||
|                 close: function(e) { | ||||
|                     if (popover) { | ||||
|                         popover.close(true); | ||||
|                         currentPopoverError = null; | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
| @@ -118,13 +97,10 @@ RED.clipboard = (function() { | ||||
|                 '</span>'+ | ||||
|             '</div>'; | ||||
| 
 | ||||
|         importNodesDialog = | ||||
|             '<div class="form-row"><span data-i18n="clipboard.pasteNodes"></span>'+ | ||||
|                 ' <a class="editor-button" id="import-file-upload-btn"><i class="fa fa-upload"></i> <span data-i18n="clipboard.selectFile"></span></a>'+ | ||||
|                 '<input type="file" id="import-file-upload" accept=".json" style="display:none">'+ | ||||
|             '</div>'+ | ||||
|             '<div class="form-row">'+ | ||||
|                 '<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5"></textarea>'+ | ||||
|         importNodesDialog = '<div class="form-row">'+ | ||||
|             '<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+ | ||||
|             RED._("clipboard.pasteNodes")+ | ||||
|             '"></textarea>'+ | ||||
|             '</div>'+ | ||||
|             '<div class="form-row">'+ | ||||
|             '<label style="width:auto;margin-right: 10px;" data-i18n="clipboard.import.import"></label>'+ | ||||
| @@ -135,98 +111,21 @@ RED.clipboard = (function() { | ||||
|             '</div>'; | ||||
|     } | ||||
| 
 | ||||
|     var validateImportTimeout; | ||||
| 
 | ||||
|     function validateImport() { | ||||
|         if (validateImportTimeout) { | ||||
|             clearTimeout(validateImportTimeout); | ||||
|         } | ||||
|         validateImportTimeout = setTimeout(function() { | ||||
|         var importInput = $("#clipboard-import"); | ||||
|             var v = importInput.val().trim(); | ||||
|             if (v === "") { | ||||
|                 popover.close(true); | ||||
|                 currentPopoverError = null; | ||||
|                 importInput.removeClass("input-error"); | ||||
|                 $("#clipboard-dialog-ok").button("disable"); | ||||
|                 return; | ||||
|             } | ||||
|         var v = importInput.val(); | ||||
|         v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1); | ||||
|         try { | ||||
|                 if (!/^\[[\s\S]*\]$/m.test(v)) { | ||||
|                     throw new Error(RED._("clipboard.import.errors.notArray")); | ||||
|                 } | ||||
|                 var res = JSON.parse(v); | ||||
|                 for (var i=0;i<res.length;i++) { | ||||
|                     if (typeof res[i] !== "object") { | ||||
|                         throw new Error(RED._("clipboard.import.errors.itemNotObject",{index:i})); | ||||
|                     } | ||||
|                     if (!res[i].hasOwnProperty('id')) { | ||||
|                         throw new Error(RED._("clipboard.import.errors.missingId",{index:i})); | ||||
|                     } | ||||
|                     if (!res[i].hasOwnProperty('type')) { | ||||
|                         throw new Error(RED._("clipboard.import.errors.missingType",{index:i})); | ||||
|                     } | ||||
|                 } | ||||
|                 currentPopoverError = null; | ||||
|                 popover.close(true); | ||||
|             JSON.parse(v); | ||||
|             importInput.removeClass("input-error"); | ||||
|             importInput.val(v); | ||||
|             $("#clipboard-dialog-ok").button("enable"); | ||||
|         } catch(err) { | ||||
|             if (v !== "") { | ||||
|                 importInput.addClass("input-error"); | ||||
|                     var errString = err.toString(); | ||||
|                     if (errString !== currentPopoverError) { | ||||
|                         // Display the error as-is.
 | ||||
|                         // Error messages are only in English. Each browser has its
 | ||||
|                         // own set of messages with very little consistency.
 | ||||
|                         // To provide translated messages this code will either need to:
 | ||||
|                         // - reduce everything down to 'unexpected token at position x'
 | ||||
|                         //   which is the least useful, but most consistent message
 | ||||
|                         // - use a custom/library parser that gives consistent messages
 | ||||
|                         //   which can be translated.
 | ||||
|                         var message = $('<div class="clipboard-import-error"></div>').text(errString); | ||||
|                         var errorPos; | ||||
|                         // Chrome error messages
 | ||||
|                         var m = /at position (\d+)/i.exec(errString); | ||||
|                         if (m) { | ||||
|                             errorPos = parseInt(m[1]); | ||||
|                         } else { | ||||
|                             // Firefox error messages
 | ||||
|                             m = /at line (\d+) column (\d+)/i.exec(errString); | ||||
|                             if (m) { | ||||
|                                 var line = parseInt(m[1])-1; | ||||
|                                 var col = parseInt(m[2])-1; | ||||
|                                 var lines = v.split("\n"); | ||||
|                                 errorPos = 0; | ||||
|                                 for (var i=0;i<line;i++) { | ||||
|                                     errorPos += lines[i].length+1; | ||||
|                                 } | ||||
|                                 errorPos += col; | ||||
|                             } else { | ||||
|                                 // Safari doesn't provide any position information
 | ||||
|                                 // IE: tbd
 | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         if (errorPos !== undefined) { | ||||
|                             v = v.replace(/\n/g,"↵"); | ||||
|                             var index = parseInt(m[1]); | ||||
|                             var parseError = $('<div>').appendTo(message); | ||||
|                             var code = $('<pre>').appendTo(parseError); | ||||
|                             $('<span>').text(v.substring(errorPos-12,errorPos)).appendTo(code) | ||||
|                             $('<span class="error">').text(v.charAt(errorPos)).appendTo(code); | ||||
|                             $('<span>').text(v.substring(errorPos+1,errorPos+12)).appendTo(code); | ||||
|                         } | ||||
|                         popover.close(true).setContent(message).open(); | ||||
|                         currentPopoverError = errString; | ||||
|                     } | ||||
|                 } else { | ||||
|                     currentPopoverError = null; | ||||
|             } | ||||
|             $("#clipboard-dialog-ok").button("disable"); | ||||
|         } | ||||
|         },100); | ||||
|     } | ||||
| 
 | ||||
|     function importNodes() { | ||||
| @@ -241,7 +140,6 @@ RED.clipboard = (function() { | ||||
|         $("#clipboard-dialog-cancel").show(); | ||||
|         $("#clipboard-dialog-close").hide(); | ||||
|         $("#clipboard-dialog-copy").hide(); | ||||
|         $("#clipboard-dialog-download").hide(); | ||||
|         $("#clipboard-dialog-ok").button("disable"); | ||||
|         $("#clipboard-import").keyup(validateImport); | ||||
|         $("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)}); | ||||
| @@ -255,26 +153,7 @@ RED.clipboard = (function() { | ||||
|             $(this).addClass('selected'); | ||||
|         }); | ||||
| 
 | ||||
|         $("#import-file-upload").change(function() { | ||||
|             var fileReader = new FileReader(); | ||||
|             fileReader.onload = function () { | ||||
|                 $("#clipboard-import").val(fileReader.result); | ||||
|                 validateImport(); | ||||
|             }; | ||||
|             fileReader.readAsText($(this).prop('files')[0]); | ||||
|         }) | ||||
|         $("#import-file-upload-btn").click(function(evt) { | ||||
|             evt.preventDefault(); | ||||
|             $("#import-file-upload").click(); | ||||
|         }) | ||||
| 
 | ||||
|         dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open"); | ||||
|         popover = RED.popover.create({ | ||||
|             target: $("#clipboard-import"), | ||||
|             trigger: "manual", | ||||
|             direction: "bottom", | ||||
|             content: "" | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     function exportNodes() { | ||||
| @@ -323,18 +202,9 @@ RED.clipboard = (function() { | ||||
|             var flow = ""; | ||||
|             var nodes = null; | ||||
|             if (type === 'export-range-selected') { | ||||
|                 var selection = RED.workspaces.selection(); | ||||
|                 if (selection.length > 0) { | ||||
|                     nodes = []; | ||||
|                     selection.forEach(function(n) { | ||||
|                         nodes.push(n); | ||||
|                         nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     nodes = RED.view.selection().nodes||[]; | ||||
|                 } | ||||
|                 var selection = RED.view.selection(); | ||||
|                 // Don't include the subflow meta-port nodes in the exported selection
 | ||||
|                 nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'})); | ||||
|                 nodes = RED.nodes.createExportableNodeSet(selection.nodes.filter(function(n) { return n.type !== 'subflow'})); | ||||
|             } else if (type === 'export-range-flow') { | ||||
|                 var activeWorkspace = RED.workspaces.active(); | ||||
|                 nodes = RED.nodes.filterNodes({z:activeWorkspace}); | ||||
| @@ -364,18 +234,13 @@ RED.clipboard = (function() { | ||||
|         $("#clipboard-dialog-cancel").hide(); | ||||
|         $("#clipboard-dialog-copy").hide(); | ||||
|         $("#clipboard-dialog-close").hide(); | ||||
|         var selection = RED.workspaces.selection(); | ||||
|         if (selection.length > 0) { | ||||
|             $("#export-range-selected").click(); | ||||
|         } else { | ||||
|             selection = RED.view.selection(); | ||||
|         var selection = RED.view.selection(); | ||||
|         if (selection.nodes) { | ||||
|             $("#export-range-selected").click(); | ||||
|         } else { | ||||
|             $("#export-range-selected").addClass('disabled').removeClass('selected'); | ||||
|             $("#export-range-flow").click(); | ||||
|         } | ||||
|         } | ||||
|         if (format === "export-format-full") { | ||||
|             $("#export-format-full").click(); | ||||
|         } else { | ||||
| @@ -400,8 +265,6 @@ RED.clipboard = (function() { | ||||
|             $("#clipboard-dialog-cancel").show(); | ||||
|             $("#clipboard-dialog-copy").show(); | ||||
|         } | ||||
|         $("#clipboard-dialog-download").show(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     function hideDropTarget() { | ||||
| @@ -413,21 +276,10 @@ RED.clipboard = (function() { | ||||
|         if (typeof value !== "string" ) { | ||||
|             value = JSON.stringify(value, function(key,value) { | ||||
|                 if (value !== null && typeof value === 'object') { | ||||
|                     if (value.__enc__) { | ||||
|                         if (value.hasOwnProperty('data') && value.hasOwnProperty('length')) { | ||||
|                     if (value.__encoded__ && value.hasOwnProperty('data') && value.hasOwnProperty('length')) { | ||||
|                         truncated = value.data.length !== value.length; | ||||
|                         return value.data; | ||||
|                     } | ||||
|                         if (value.type === 'function' || value.type === 'internal') { | ||||
|                             return undefined | ||||
|                         } | ||||
|                         if (value.type === 'number') { | ||||
|                             // Handle NaN and Infinity - they are not permitted
 | ||||
|                             // in JSON. We can either substitute with a String
 | ||||
|                             // representation or null
 | ||||
|                             return null; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return value; | ||||
|             }); | ||||
| @@ -457,6 +309,18 @@ RED.clipboard = (function() { | ||||
| 
 | ||||
|             $('<input type="text" id="clipboard-hidden">').appendTo("body"); | ||||
| 
 | ||||
|             RED.events.on("view:selection-changed",function(selection) { | ||||
|                 if (!selection.nodes) { | ||||
|                     RED.menu.setDisabled("menu-item-export",true); | ||||
|                     RED.menu.setDisabled("menu-item-export-clipboard",true); | ||||
|                     RED.menu.setDisabled("menu-item-export-library",true); | ||||
|                 } else { | ||||
|                     RED.menu.setDisabled("menu-item-export",false); | ||||
|                     RED.menu.setDisabled("menu-item-export-clipboard",false); | ||||
|                     RED.menu.setDisabled("menu-item-export-library",false); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             RED.actions.add("core:show-export-dialog",exportNodes); | ||||
|             RED.actions.add("core:show-import-dialog",importNodes); | ||||
| 
 | ||||
| @@ -75,7 +75,7 @@ | ||||
|                         addLabel = 'add'; | ||||
|                     } | ||||
|                 } | ||||
|                 $('<a href="#" class="editor-button editor-button-small red-ui-editableList-addButton" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>') | ||||
|                 $('<a href="#" class="editor-button editor-button-small" style="margin-top: 4px;"><i class="fa fa-plus"></i> '+addLabel+'</a>') | ||||
|                     .appendTo(this.topContainer) | ||||
|                     .click(function(evt) { | ||||
|                         evt.preventDefault(); | ||||
| @@ -116,11 +116,6 @@ | ||||
|                 this.uiContainer.css("minHeight",minHeight); | ||||
|                 this.element.css("minHeight",0); | ||||
|             } | ||||
|             var maxHeight = this.element.css("maxHeight"); | ||||
|             if (maxHeight !== '0px') { | ||||
|                 this.uiContainer.css("maxHeight",maxHeight); | ||||
|                 this.element.css("maxHeight",null); | ||||
|             } | ||||
|             if (this.options.height !== 'auto') { | ||||
|                 this.uiContainer.css("overflow-y","scroll"); | ||||
|                 if (!isNaN(this.options.height)) { | ||||
| @@ -327,14 +322,6 @@ | ||||
|         }, | ||||
|         length: function() { | ||||
|             return this.element.children().length; | ||||
|         }, | ||||
|         show: function(item) { | ||||
|             var items = this.element.children().filter(function(f) { | ||||
|                 return item === $(this).find(".red-ui-editableList-item-content").data('data'); | ||||
|             }); | ||||
|             if (items.length > 0) { | ||||
|                 this.uiContainer.scrollTop(this.uiContainer.scrollTop()+items.position().top) | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| })(jQuery); | ||||
| @@ -150,15 +150,14 @@ RED.menu = (function() { | ||||
| 
 | ||||
|     } | ||||
|     function createMenu(options) { | ||||
|         var topMenu = $("<ul/>",{class:"dropdown-menu pull-right"}); | ||||
| 
 | ||||
|         if (options.id) { | ||||
|             topMenu.attr({id:options.id+"-submenu"}); | ||||
|         var menuParent = $("#"+options.id); | ||||
| 
 | ||||
|         var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}); | ||||
| 
 | ||||
|         if (menuParent.length === 1) { | ||||
|             topMenu.insertAfter(menuParent); | ||||
|         } | ||||
|         } | ||||
| 
 | ||||
|         var lastAddedSeparator = false; | ||||
|         for (var i=0;i<options.options.length;i++) { | ||||
							
								
								
									
										81
									
								
								editor/js/ui/common/panels.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,81 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
|  | ||||
| RED.panels = (function() { | ||||
|  | ||||
|     function createPanel(options) { | ||||
|         var container = options.container || $("#"+options.id); | ||||
|         var children = container.children(); | ||||
|         if (children.length !== 2) { | ||||
|             throw new Error("Container must have exactly two children"); | ||||
|         } | ||||
|  | ||||
|         container.addClass("red-ui-panels"); | ||||
|         var separator = $('<div class="red-ui-panels-separator"></div>').insertAfter(children[0]); | ||||
|         var startPosition; | ||||
|         var panelHeights = []; | ||||
|         var modifiedHeights = false; | ||||
|         var panelRatio; | ||||
|  | ||||
|         separator.draggable({ | ||||
|                 axis: "y", | ||||
|                 containment: container, | ||||
|                 scroll: false, | ||||
|                 start:function(event,ui) { | ||||
|                     var height = container.height(); | ||||
|                     startPosition = ui.position.top; | ||||
|                     panelHeights = [$(children[0]).height(),$(children[1]).height()]; | ||||
|                 }, | ||||
|                 drag: function(event,ui) { | ||||
|                     var height = container.height(); | ||||
|                     var delta = ui.position.top-startPosition; | ||||
|                     var newHeights = [panelHeights[0]+delta,panelHeights[1]-delta]; | ||||
|                     $(children[0]).height(newHeights[0]); | ||||
|                     $(children[1]).height(newHeights[1]); | ||||
|                     if (options.resize) { | ||||
|                         options.resize(newHeights[0],newHeights[1]); | ||||
|                     } | ||||
|                     ui.position.top -= delta; | ||||
|                     panelRatio = newHeights[0]/height; | ||||
|                 }, | ||||
|                 stop:function(event,ui) { | ||||
|                     modifiedHeights = true; | ||||
|                 } | ||||
|         }); | ||||
|  | ||||
|         return { | ||||
|             resize: function(height) { | ||||
|                 var panelHeights = [$(children[0]).height(),$(children[1]).height()]; | ||||
|                 container.height(height); | ||||
|                 if (modifiedHeights) { | ||||
|                     var topPanelHeight = panelRatio*height; | ||||
|                     var bottomPanelHeight = height - topPanelHeight - 48; | ||||
|                     panelHeights = [topPanelHeight,bottomPanelHeight]; | ||||
|                     $(children[0]).height(panelHeights[0]); | ||||
|                     $(children[1]).height(panelHeights[1]); | ||||
|                 } | ||||
|                 if (options.resize) { | ||||
|                     options.resize(panelHeights[0],panelHeights[1]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         create: createPanel | ||||
|     } | ||||
| })(); | ||||
							
								
								
									
										135
									
								
								editor/js/ui/common/popover.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,135 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| RED.popover = (function() { | ||||
|     var deltaSizes = { | ||||
|         "default": { | ||||
|             top: 10, | ||||
|             leftRight: 17, | ||||
|             leftLeft: 25 | ||||
|         }, | ||||
|         "small": { | ||||
|             top: 5, | ||||
|             leftRight: 8, | ||||
|             leftLeft: 16 | ||||
|         } | ||||
|     } | ||||
|     function createPopover(options) { | ||||
|         var target = options.target; | ||||
|         var direction = options.direction || "right"; | ||||
|         var trigger = options.trigger; | ||||
|         var content = options.content; | ||||
|         var delay = options.delay; | ||||
|         var width = options.width||"auto"; | ||||
|         var size = options.size||"default"; | ||||
|         if (!deltaSizes[size]) { | ||||
|             throw new Error("Invalid RED.popover size value:",size); | ||||
|         } | ||||
|  | ||||
|         var timer = null; | ||||
|         var active; | ||||
|         var div; | ||||
|  | ||||
|         var openPopup = function() { | ||||
|             if (active) { | ||||
|                 div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>').appendTo("body"); | ||||
|                 if (size !== "default") { | ||||
|                     div.addClass("red-ui-popover-size-"+size); | ||||
|                 } | ||||
|                 if (typeof content === 'function') { | ||||
|                     content.call(res).appendTo(div); | ||||
|                 } else { | ||||
|                     div.html(content); | ||||
|                 } | ||||
|                 if (width !== "auto") { | ||||
|                     div.width(width); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 var targetPos = target.offset(); | ||||
|                 var targetWidth = target.width(); | ||||
|                 var targetHeight = target.height(); | ||||
|  | ||||
|                 var divHeight = div.height(); | ||||
|                 var divWidth = div.width(); | ||||
|                 if (direction === 'right') { | ||||
|                     div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left+targetWidth+deltaSizes[size].leftRight}); | ||||
|                 } else if (direction === 'left') { | ||||
|                     div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth}); | ||||
|                 } | ||||
|  | ||||
|                 div.fadeIn("fast"); | ||||
|             } | ||||
|         } | ||||
|         var closePopup = function() { | ||||
|             if (!active) { | ||||
|                 if (div) { | ||||
|                     div.fadeOut("fast",function() { | ||||
|                         $(this).remove(); | ||||
|                     }); | ||||
|                     div = null; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (trigger === 'hover') { | ||||
|  | ||||
|             target.on('mouseenter',function(e) { | ||||
|                 clearTimeout(timer); | ||||
|                 active = true; | ||||
|                 timer = setTimeout(openPopup,delay.show); | ||||
|             }); | ||||
|             target.on('mouseleave', function(e) { | ||||
|                 if (timer) { | ||||
|                     clearTimeout(timer); | ||||
|                 } | ||||
|                 active = false; | ||||
|                 setTimeout(closePopup,delay.hide); | ||||
|             }); | ||||
|         } else if (trigger === 'click') { | ||||
|             target.click(function(e) { | ||||
|                 e.preventDefault(); | ||||
|                 e.stopPropagation(); | ||||
|                 active = !active; | ||||
|                 if (!active) { | ||||
|                     closePopup(); | ||||
|                 } else { | ||||
|                     openPopup(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         var res = { | ||||
|             setContent: function(_content) { | ||||
|                 content = _content; | ||||
|             }, | ||||
|             open: function () { | ||||
|                 active = true; | ||||
|                 openPopup(); | ||||
|             }, | ||||
|             close: function () { | ||||
|                 active = false; | ||||
|                 closePopup(); | ||||
|             } | ||||
|         } | ||||
|         return res; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         create: createPopover | ||||
|     } | ||||
|  | ||||
| })(); | ||||
| @@ -17,31 +17,11 @@ | ||||
| RED.stack = (function() { | ||||
|     function createStack(options) { | ||||
|         var container = options.container; | ||||
|         container.addClass("red-ui-stack"); | ||||
|         var contentHeight = 0; | ||||
| 
 | ||||
|         var entries = []; | ||||
| 
 | ||||
|         var visible = true; | ||||
|         // TODO: make this a singleton function - and watch out for stacks no longer
 | ||||
|         //       in the DOM
 | ||||
|         var resizeStack = function() { | ||||
|             if (entries.length > 0) { | ||||
|                 var headerHeight = 0; | ||||
|                 entries.forEach(function(entry) { | ||||
|                     headerHeight += entry.header.outerHeight(); | ||||
|                 }); | ||||
| 
 | ||||
|                 var height = container.innerHeight(); | ||||
|                 contentHeight = height - headerHeight - (entries.length-1); | ||||
|                 entries.forEach(function(e) { | ||||
|                     e.contentWrap.height(contentHeight); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         if (options.fill && options.singleExpanded) { | ||||
|             $(window).resize(resizeStack); | ||||
|             $(window).focus(resizeStack); | ||||
|         } | ||||
|         return { | ||||
|             add: function(entry) { | ||||
|                 entries.push(entry); | ||||
| @@ -50,12 +30,7 @@ RED.stack = (function() { | ||||
|                     entry.container.hide(); | ||||
|                 } | ||||
|                 var header = $('<div class="palette-header"></div>').appendTo(entry.container); | ||||
|                 entry.header = header; | ||||
|                 entry.contentWrap = $('<div></div>',{style:"position:relative"}).appendTo(entry.container); | ||||
|                 if (options.fill) { | ||||
|                     entry.contentWrap.css("height",contentHeight); | ||||
|                 } | ||||
|                 entry.content = $('<div></div>').appendTo(entry.contentWrap); | ||||
|                 entry.content = $('<div></div>').appendTo(entry.container); | ||||
|                 if (entry.collapsible !== false) { | ||||
|                     header.click(function() { | ||||
|                         if (options.singleExpanded) { | ||||
| @@ -66,14 +41,6 @@ RED.stack = (function() { | ||||
|                                     } | ||||
|                                 } | ||||
|                                 entry.expand(); | ||||
|                             } else if (entries.length === 2) { | ||||
|                                 if (entries[0] === entry) { | ||||
|                                     entries[0].collapse(); | ||||
|                                     entries[1].expand(); | ||||
|                                 } else { | ||||
|                                     entries[1].collapse(); | ||||
|                                     entries[0].expand(); | ||||
|                                 } | ||||
|                             } | ||||
|                         } else { | ||||
|                             entry.toggle(); | ||||
| @@ -82,13 +49,11 @@ RED.stack = (function() { | ||||
|                     var icon = $('<i class="fa fa-angle-down"></i>').appendTo(header); | ||||
| 
 | ||||
|                     if (entry.expanded) { | ||||
|                         entry.container.addClass("palette-category-expanded"); | ||||
|                         icon.addClass("expanded"); | ||||
|                     } else { | ||||
|                         entry.contentWrap.hide(); | ||||
|                         entry.content.hide(); | ||||
|                     } | ||||
|                 } else { | ||||
|                     $('<i style="opacity: 0.5;" class="fa fa-angle-down expanded"></i>').appendTo(header); | ||||
|                     header.css("cursor","default"); | ||||
|                 } | ||||
|                 entry.title = $('<span></span>').html(entry.title).appendTo(header); | ||||
| @@ -109,35 +74,24 @@ RED.stack = (function() { | ||||
|                         if (entry.onexpand) { | ||||
|                             entry.onexpand.call(entry); | ||||
|                         } | ||||
|                         if (options.singleExpanded) { | ||||
|                             entries.forEach(function(e) { | ||||
|                                 if (e !== entry) { | ||||
|                                     e.collapse(); | ||||
|                                 } | ||||
|                             }) | ||||
|                         } | ||||
| 
 | ||||
|                         icon.addClass("expanded"); | ||||
|                         entry.container.addClass("palette-category-expanded"); | ||||
|                         entry.contentWrap.slideDown(200); | ||||
|                         entry.content.slideDown(200); | ||||
|                         return true; | ||||
|                     } | ||||
|                 }; | ||||
|                 entry.collapse = function() { | ||||
|                     if (entry.isExpanded()) { | ||||
|                         icon.removeClass("expanded"); | ||||
|                         entry.container.removeClass("palette-category-expanded"); | ||||
|                         entry.contentWrap.slideUp(200); | ||||
|                         entry.content.slideUp(200); | ||||
|                         return true; | ||||
|                     } | ||||
|                 }; | ||||
|                 entry.isExpanded = function() { | ||||
|                     return entry.container.hasClass("palette-category-expanded"); | ||||
|                     return icon.hasClass("expanded"); | ||||
|                 }; | ||||
|                 if (options.fill && options.singleExpanded) { | ||||
|                     resizeStack(); | ||||
|                 } | ||||
| 
 | ||||
|                 return entry; | ||||
| 
 | ||||
|             }, | ||||
| 
 | ||||
|             hide: function() { | ||||
| @@ -154,13 +108,9 @@ RED.stack = (function() { | ||||
|                     entry.container.show(); | ||||
|                 }); | ||||
|                 return this; | ||||
|             }, | ||||
|             resize: function() { | ||||
|                 if (resizeStack) { | ||||
|                     resizeStack(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
							
								
								
									
										379
									
								
								editor/js/ui/common/tabs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,379 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
|  | ||||
|  | ||||
| RED.tabs = (function() { | ||||
|     function createTabs(options) { | ||||
|         var tabs = {}; | ||||
|         var currentTabWidth; | ||||
|         var currentActiveTabWidth = 0; | ||||
|  | ||||
|         var ul = options.element || $("#"+options.id); | ||||
|         var wrapper = ul.wrap( "<div>" ).parent(); | ||||
|         var scrollContainer = ul.wrap( "<div>" ).parent(); | ||||
|         wrapper.addClass("red-ui-tabs"); | ||||
|         if (options.vertical) { | ||||
|             wrapper.addClass("red-ui-tabs-vertical"); | ||||
|         } | ||||
|         if (options.addButton && typeof options.addButton === 'function') { | ||||
|             wrapper.addClass("red-ui-tabs-add"); | ||||
|             var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper); | ||||
|             addButton.find('a').click(function(evt) { | ||||
|                 evt.preventDefault(); | ||||
|                 options.addButton(); | ||||
|             }) | ||||
|  | ||||
|         } | ||||
|         var scrollLeft; | ||||
|         var scrollRight; | ||||
|  | ||||
|         if (options.scrollable) { | ||||
|             wrapper.addClass("red-ui-tabs-scrollable"); | ||||
|             scrollContainer.addClass("red-ui-tabs-scroll-container"); | ||||
|             scrollContainer.scroll(updateScroll); | ||||
|             scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a"); | ||||
|             scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();}); | ||||
|             scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a"); | ||||
|             scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();}); | ||||
|         } | ||||
|         function scrollEventHandler(evt,dir) { | ||||
|             evt.preventDefault(); | ||||
|             if ($(this).hasClass('disabled')) { | ||||
|                 return; | ||||
|             } | ||||
|             var currentScrollLeft = scrollContainer.scrollLeft(); | ||||
|             scrollContainer.animate( { scrollLeft: dir }, 100); | ||||
|             var interval = setInterval(function() { | ||||
|                 var newScrollLeft = scrollContainer.scrollLeft() | ||||
|                 if (newScrollLeft === currentScrollLeft) { | ||||
|                     clearInterval(interval); | ||||
|                     return; | ||||
|                 } | ||||
|                 currentScrollLeft = newScrollLeft; | ||||
|                 scrollContainer.animate( { scrollLeft: dir }, 100); | ||||
|             },100); | ||||
|             $(this).one('mouseup',function() { | ||||
|                 clearInterval(interval); | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|  | ||||
|         ul.children().first().addClass("active"); | ||||
|         ul.children().addClass("red-ui-tab"); | ||||
|  | ||||
|         function onTabClick() { | ||||
|             if (options.onclick) { | ||||
|                 options.onclick(tabs[$(this).attr('href').slice(1)]); | ||||
|             } | ||||
|             activateTab($(this)); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         function updateScroll() { | ||||
|             if (ul.children().length !== 0) { | ||||
|                 var sl = scrollContainer.scrollLeft(); | ||||
|                 var scWidth = scrollContainer.width(); | ||||
|                 var ulWidth = ul.width(); | ||||
|                 if (sl === 0) { | ||||
|                     scrollLeft.hide(); | ||||
|                 } else { | ||||
|                     scrollLeft.show(); | ||||
|                 } | ||||
|                 if (sl === ulWidth-scWidth) { | ||||
|                     scrollRight.hide(); | ||||
|                 } else { | ||||
|                     scrollRight.show(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         function onTabDblClick() { | ||||
|             if (options.ondblclick) { | ||||
|                 options.ondblclick(tabs[$(this).attr('href').slice(1)]); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         function activateTab(link) { | ||||
|             if (typeof link === "string") { | ||||
|                 link = ul.find("a[href='#"+link+"']"); | ||||
|             } | ||||
|             if (link.length === 0) { | ||||
|                 return; | ||||
|             } | ||||
|             if (!link.parent().hasClass("active")) { | ||||
|                 ul.children().removeClass("active"); | ||||
|                 ul.children().css({"transition": "width 100ms"}); | ||||
|                 link.parent().addClass("active"); | ||||
|                 if (options.scrollable) { | ||||
|                     var pos = link.parent().position().left; | ||||
|                     if (pos-21 < 0) { | ||||
|                         scrollContainer.animate( { scrollLeft: '+='+(pos-50) }, 300); | ||||
|                     } else if (pos + 120 > scrollContainer.width()) { | ||||
|                         scrollContainer.animate( { scrollLeft: '+='+(pos + 140-scrollContainer.width()) }, 300); | ||||
|                     } | ||||
|                 } | ||||
|                 if (options.onchange) { | ||||
|                     options.onchange(tabs[link.attr('href').slice(1)]); | ||||
|                 } | ||||
|                 updateTabWidths(); | ||||
|                 setTimeout(function() { | ||||
|                     ul.children().css({"transition": ""}); | ||||
|                 },100); | ||||
|             } | ||||
|         } | ||||
|         function activatePreviousTab() { | ||||
|             var previous = ul.find("li.active").prev(); | ||||
|             if (previous.length > 0) { | ||||
|                 activateTab(previous.find("a")); | ||||
|             } | ||||
|         } | ||||
|         function activateNextTab() { | ||||
|             var next = ul.find("li.active").next(); | ||||
|             if (next.length > 0) { | ||||
|                 activateTab(next.find("a")); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function updateTabWidths() { | ||||
|             if (options.vertical) { | ||||
|                 return; | ||||
|             } | ||||
|             var tabs = ul.find("li.red-ui-tab"); | ||||
|             var width = wrapper.width(); | ||||
|             var tabCount = tabs.size(); | ||||
|             var tabWidth = (width-12-(tabCount*6))/tabCount; | ||||
|             currentTabWidth = (100*tabWidth/width)+"%"; | ||||
|             currentActiveTabWidth = currentTabWidth+"%"; | ||||
|             if (options.scrollable) { | ||||
|                 tabWidth = Math.max(tabWidth,140); | ||||
|                 currentTabWidth = tabWidth+"px"; | ||||
|                 currentActiveTabWidth = 0; | ||||
|                 var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount); | ||||
|                 ul.width(listWidth); | ||||
|                 updateScroll(); | ||||
|             } else if (options.hasOwnProperty("minimumActiveTabWidth")) { | ||||
|                 if (tabWidth < options.minimumActiveTabWidth) { | ||||
|                     tabCount -= 1; | ||||
|                     tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount; | ||||
|                     currentTabWidth = (100*tabWidth/width)+"%"; | ||||
|                     currentActiveTabWidth = options.minimumActiveTabWidth+"px"; | ||||
|                 } else { | ||||
|                     currentActiveTabWidth = 0; | ||||
|                 } | ||||
|             } | ||||
|             tabs.css({width:currentTabWidth}); | ||||
|             if (tabWidth < 50) { | ||||
|                 ul.find(".red-ui-tab-close").hide(); | ||||
|                 ul.find(".red-ui-tab-icon").hide(); | ||||
|                 ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"}) | ||||
|             } else { | ||||
|                 ul.find(".red-ui-tab-close").show(); | ||||
|                 ul.find(".red-ui-tab-icon").show(); | ||||
|                 ul.find(".red-ui-tab-label").css({paddingLeft:""}) | ||||
|             } | ||||
|             if (currentActiveTabWidth !== 0) { | ||||
|                 ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth}); | ||||
|                 ul.find("li.red-ui-tab.active .red-ui-tab-close").show(); | ||||
|                 ul.find("li.red-ui-tab.active .red-ui-tab-icon").show(); | ||||
|                 ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""}) | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         ul.find("li.red-ui-tab a").on("click",onTabClick).on("dblclick",onTabDblClick); | ||||
|         setTimeout(function() { | ||||
|             updateTabWidths(); | ||||
|         },0); | ||||
|  | ||||
|  | ||||
|         function removeTab(id) { | ||||
|             var li = ul.find("a[href='#"+id+"']").parent(); | ||||
|             if (li.hasClass("active")) { | ||||
|                 var tab = li.prev(); | ||||
|                 if (tab.size() === 0) { | ||||
|                     tab = li.next(); | ||||
|                 } | ||||
|                 activateTab(tab.find("a")); | ||||
|             } | ||||
|             li.remove(); | ||||
|             if (options.onremove) { | ||||
|                 options.onremove(tabs[id]); | ||||
|             } | ||||
|             delete tabs[id]; | ||||
|             updateTabWidths(); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             addTab: function(tab) { | ||||
|                 tabs[tab.id] = tab; | ||||
|                 var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul); | ||||
|                 li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-"))); | ||||
|                 li.data("tabId",tab.id); | ||||
|                 var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li); | ||||
|                 if (tab.icon) { | ||||
|                     $('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link); | ||||
|                 } | ||||
|                 var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link); | ||||
|                 span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label)); | ||||
|  | ||||
|                 link.on("click",onTabClick); | ||||
|                 link.on("dblclick",onTabDblClick); | ||||
|                 if (tab.closeable) { | ||||
|                     var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li); | ||||
|                     closeLink.append('<i class="fa fa-times" />'); | ||||
|  | ||||
|                     closeLink.on("click",function(event) { | ||||
|                         event.preventDefault(); | ||||
|                         removeTab(tab.id); | ||||
|                     }); | ||||
|                 } | ||||
|                 updateTabWidths(); | ||||
|                 if (options.onadd) { | ||||
|                     options.onadd(tab); | ||||
|                 } | ||||
|                 link.attr("title",tab.label); | ||||
|                 if (ul.find("li.red-ui-tab").size() == 1) { | ||||
|                     activateTab(link); | ||||
|                 } | ||||
|                 if (options.onreorder) { | ||||
|                     var originalTabOrder; | ||||
|                     var tabDragIndex; | ||||
|                     var tabElements = []; | ||||
|                     var startDragIndex; | ||||
|  | ||||
|                     li.draggable({ | ||||
|                         axis:"x", | ||||
|                         distance: 20, | ||||
|                         start: function(event,ui) { | ||||
|                             originalTabOrder = []; | ||||
|                             tabElements = []; | ||||
|                             ul.children().each(function(i) { | ||||
|                                 tabElements[i] = { | ||||
|                                     el:$(this), | ||||
|                                     text: $(this).text(), | ||||
|                                     left: $(this).position().left, | ||||
|                                     width: $(this).width() | ||||
|                                 }; | ||||
|                                 if ($(this).is(li)) { | ||||
|                                     tabDragIndex = i; | ||||
|                                     startDragIndex = i; | ||||
|                                 } | ||||
|                                 originalTabOrder.push($(this).data("tabId")); | ||||
|                             }); | ||||
|                             ul.children().each(function(i) { | ||||
|                                 if (i!==tabDragIndex) { | ||||
|                                     $(this).css({ | ||||
|                                         position: 'absolute', | ||||
|                                         left: tabElements[i].left+"px", | ||||
|                                         width: tabElements[i].width+2, | ||||
|                                         transition: "left 0.3s" | ||||
|                                     }); | ||||
|                                 } | ||||
|  | ||||
|                             }) | ||||
|                             if (!li.hasClass('active')) { | ||||
|                                 li.css({'zIndex':1}); | ||||
|                             } | ||||
|                         }, | ||||
|                         drag: function(event,ui) { | ||||
|                             ui.position.left += tabElements[tabDragIndex].left+scrollContainer.scrollLeft(); | ||||
|                             var tabCenter = ui.position.left + tabElements[tabDragIndex].width/2 - scrollContainer.scrollLeft(); | ||||
|                             for (var i=0;i<tabElements.length;i++) { | ||||
|                                 if (i === tabDragIndex) { | ||||
|                                     continue; | ||||
|                                 } | ||||
|                                 if (tabCenter > tabElements[i].left && tabCenter < tabElements[i].left+tabElements[i].width) { | ||||
|                                     if (i < tabDragIndex) { | ||||
|                                         tabElements[i].left += tabElements[tabDragIndex].width+8; | ||||
|                                         tabElements[tabDragIndex].el.detach().insertBefore(tabElements[i].el); | ||||
|                                     } else { | ||||
|                                         tabElements[i].left -= tabElements[tabDragIndex].width+8; | ||||
|                                         tabElements[tabDragIndex].el.detach().insertAfter(tabElements[i].el); | ||||
|                                     } | ||||
|                                     tabElements[i].el.css({left:tabElements[i].left+"px"}); | ||||
|  | ||||
|                                     tabElements.splice(i, 0, tabElements.splice(tabDragIndex, 1)[0]); | ||||
|  | ||||
|                                     tabDragIndex = i; | ||||
|                                     break; | ||||
|                                 } | ||||
|                             } | ||||
|                         }, | ||||
|                         stop: function(event,ui) { | ||||
|                             ul.children().css({position:"relative",left:"",transition:""}); | ||||
|                             if (!li.hasClass('active')) { | ||||
|                                 li.css({zIndex:""}); | ||||
|                             } | ||||
|                             updateTabWidths(); | ||||
|                             if (startDragIndex !== tabDragIndex) { | ||||
|                                 options.onreorder(originalTabOrder, $.makeArray(ul.children().map(function() { return $(this).data('tabId');}))); | ||||
|                             } | ||||
|                             activateTab(tabElements[tabDragIndex].el.data('tabId')); | ||||
|                         } | ||||
|                     }) | ||||
|                 } | ||||
|             }, | ||||
|             removeTab: removeTab, | ||||
|             activateTab: activateTab, | ||||
|             nextTab: activateNextTab, | ||||
|             previousTab: activatePreviousTab, | ||||
|             resize: updateTabWidths, | ||||
|             count: function() { | ||||
|                 return ul.find("li.red-ui-tab").size(); | ||||
|             }, | ||||
|             contains: function(id) { | ||||
|                 return ul.find("a[href='#"+id+"']").length > 0; | ||||
|             }, | ||||
|             renameTab: function(id,label) { | ||||
|                 tabs[id].label = label; | ||||
|                 var tab = ul.find("a[href='#"+id+"']"); | ||||
|                 tab.attr("title",label); | ||||
|                 tab.find("span.bidiAware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label)); | ||||
|                 updateTabWidths(); | ||||
|             }, | ||||
|             order: function(order) { | ||||
|                 var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');})); | ||||
|                 if (existingTabOrder.length !== order.length) { | ||||
|                     return | ||||
|                 } | ||||
|                 var i; | ||||
|                 var match = true; | ||||
|                 for (i=0;i<order.length;i++) { | ||||
|                     if (order[i] !== existingTabOrder[i]) { | ||||
|                         match = false; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (match) { | ||||
|                     return; | ||||
|                 } | ||||
|                 var existingTabMap = {}; | ||||
|                 var existingTabs = ul.children().detach().each(function() { | ||||
|                     existingTabMap[$(this).data("tabId")] = $(this); | ||||
|                 }); | ||||
|                 for (i=0;i<order.length;i++) { | ||||
|                     existingTabMap[order[i]].appendTo(ul); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         create: createTabs | ||||
|     } | ||||
| })(); | ||||
| @@ -14,38 +14,10 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
| (function($) { | ||||
|     var contextParse = function(v) { | ||||
|         var parts = RED.utils.parseContextKey(v); | ||||
|         return { | ||||
|             option: parts.store, | ||||
|             value: parts.key | ||||
|         } | ||||
|     } | ||||
|     var contextExport = function(v,opt) { | ||||
|         if (!opt) { | ||||
|             return v; | ||||
|         } | ||||
|         var store = ((typeof opt === "string")?opt:opt.value) | ||||
|         if (store !== RED.settings.context.default) { | ||||
|             return "#:("+store+")::"+v; | ||||
|         } else { | ||||
|             return v; | ||||
|         } | ||||
|     } | ||||
|     var allOptions = { | ||||
|         msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression}, | ||||
|         flow: {value:"flow",label:"flow.",hasValue:true, | ||||
|             options:[], | ||||
|             validate:RED.utils.validatePropertyExpression, | ||||
|             parse: contextParse, | ||||
|             export: contextExport | ||||
|         }, | ||||
|         global: {value:"global",label:"global.",hasValue:true, | ||||
|             options:[], | ||||
|             validate:RED.utils.validatePropertyExpression, | ||||
|             parse: contextParse, | ||||
|             export: contextExport | ||||
|         }, | ||||
|         flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression}, | ||||
|         global: {value:"global",label:"global.",validate:RED.utils.validatePropertyExpression}, | ||||
|         str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"}, | ||||
|         num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/}, | ||||
|         bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]}, | ||||
| @@ -104,51 +76,31 @@ | ||||
|                     } | ||||
|                 }) | ||||
|             } | ||||
|         }, | ||||
|         env: { | ||||
|             value: "env", | ||||
|             label: "env variable", | ||||
|             icon: "red/images/typedInput/env.png" | ||||
|         } | ||||
|     }; | ||||
|     var nlsd = false; | ||||
| 
 | ||||
|     $.widget( "nodered.typedInput", { | ||||
|         _create: function() { | ||||
|             try { | ||||
|             if (!nlsd && RED && RED._) { | ||||
|                 for (var i in allOptions) { | ||||
|                     if (allOptions.hasOwnProperty(i)) { | ||||
|                         allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label}); | ||||
|                     } | ||||
|                 } | ||||
|                 var contextStores = RED.settings.context.stores; | ||||
|                 var contextOptions = contextStores.map(function(store) { | ||||
|                     return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database" style="color: #'+(store==='memory'?'ddd':'777')+'"></i>'} | ||||
|                 }) | ||||
|                 if (contextOptions.length < 2) { | ||||
|                     allOptions.flow.options = []; | ||||
|                     allOptions.global.options = []; | ||||
|                 } else { | ||||
|                     allOptions.flow.options = contextOptions; | ||||
|                     allOptions.global.options = contextOptions; | ||||
|                 } | ||||
|             } | ||||
|             nlsd = true; | ||||
|             var that = this; | ||||
| 
 | ||||
|             this.disarmClick = false; | ||||
|             this.input = $('<input type="text"></input>'); | ||||
|             this.input.insertAfter(this.element); | ||||
|             this.input.val(this.element.val()); | ||||
|             this.element.addClass('red-ui-typedInput'); | ||||
|             this.uiWidth = this.element.outerWidth(); | ||||
|             this.elementDiv = this.input.wrap("<div>").parent().addClass('red-ui-typedInput-input'); | ||||
|             this.elementDiv = this.element.wrap("<div>").parent().addClass('red-ui-typedInput-input'); | ||||
|             this.uiSelect = this.elementDiv.wrap( "<div>" ).parent(); | ||||
|             var attrStyle = this.element.attr('style'); | ||||
|             var m; | ||||
|             if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) { | ||||
|                 this.input.css('width','100%'); | ||||
|             if ((m = /width\s*:\s*(\d+(%|px))/i.exec(attrStyle)) !== null) { | ||||
|                 this.element.css('width','100%'); | ||||
|                 this.uiSelect.width(m[1]); | ||||
|                 this.uiWidth = null; | ||||
|             } else { | ||||
| @@ -157,19 +109,15 @@ | ||||
|             ["Right","Left"].forEach(function(d) { | ||||
|                 var m = that.element.css("margin"+d); | ||||
|                 that.uiSelect.css("margin"+d,m); | ||||
|                 that.input.css("margin"+d,0); | ||||
|                 that.element.css("margin"+d,0); | ||||
|             }); | ||||
| 
 | ||||
|             this.uiSelect.addClass("red-ui-typedInput-container"); | ||||
| 
 | ||||
|             this.element.attr('type','hidden'); | ||||
| 
 | ||||
|             this.options.types = this.options.types||Object.keys(allOptions); | ||||
| 
 | ||||
|             this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect); | ||||
|             $('<i class="red-ui-typedInput-icon fa fa-sort-desc"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger); | ||||
| 
 | ||||
|             this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger); | ||||
|             $('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger); | ||||
|             this.selectLabel = $('<span></span>').appendTo(this.selectTrigger); | ||||
| 
 | ||||
|             this.types(this.options.types); | ||||
| 
 | ||||
| @@ -183,16 +131,14 @@ | ||||
|                 this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect); | ||||
|             } | ||||
| 
 | ||||
|             this.input.on('focus', function() { | ||||
|             this.element.on('focus', function() { | ||||
|                 that.uiSelect.addClass('red-ui-typedInput-focus'); | ||||
|             }); | ||||
|             this.input.on('blur', function() { | ||||
|             this.element.on('blur', function() { | ||||
|                 that.uiSelect.removeClass('red-ui-typedInput-focus'); | ||||
|             }); | ||||
|             this.input.on('change', function() { | ||||
|             this.element.on('change', function() { | ||||
|                 that.validate(); | ||||
|                 that.element.val(that.value()); | ||||
|                 that.element.trigger('change',that.propertyType,that.value()); | ||||
|             }) | ||||
|             this.selectTrigger.click(function(event) { | ||||
|                 event.preventDefault(); | ||||
| @@ -208,11 +154,8 @@ | ||||
|             }) | ||||
| 
 | ||||
|             // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
 | ||||
|             this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect); | ||||
|             this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect); | ||||
|             this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger); | ||||
|             RED.popover.tooltip(this.optionSelectLabel,function() { | ||||
|                 return that.optionValue; | ||||
|             }); | ||||
|             this.optionSelectTrigger.click(function(event) { | ||||
|                 event.preventDefault(); | ||||
|                 that._showOptionSelectMenu(); | ||||
| @@ -227,18 +170,17 @@ | ||||
|                 that.uiSelect.addClass('red-ui-typedInput-focus'); | ||||
|             }); | ||||
| 
 | ||||
|             this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect); | ||||
|             this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect); | ||||
| 
 | ||||
| 
 | ||||
|             this.type(this.options.default||this.typeList[0].value); | ||||
|         }catch(err) { | ||||
|             console.log(err.stack); | ||||
|         } | ||||
|         }, | ||||
|         _showTypeMenu: function() { | ||||
|             if (this.typeList.length > 1) { | ||||
|                 this._showMenu(this.menu,this.selectTrigger); | ||||
|                 this.menu.find("[value='"+this.propertyType+"']").focus(); | ||||
|             } else { | ||||
|                 this.input.focus(); | ||||
|                 this.element.focus(); | ||||
|             } | ||||
|         }, | ||||
|         _showOptionSelectMenu: function() { | ||||
| @@ -247,8 +189,8 @@ | ||||
|                     minWidth:this.optionSelectLabel.width() | ||||
|                 }); | ||||
| 
 | ||||
|                 this._showMenu(this.optionMenu,this.optionSelectTrigger); | ||||
|                 var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']"); | ||||
|                 this._showMenu(this.optionMenu,this.optionSelectLabel); | ||||
|                 var selectedOption = this.optionMenu.find("[value='"+this.value()+"']"); | ||||
|                 if (selectedOption.length === 0) { | ||||
|                     selectedOption = this.optionMenu.children(":first"); | ||||
|                 } | ||||
| @@ -260,7 +202,7 @@ | ||||
|             $(document).off("mousedown.close-property-select"); | ||||
|             menu.hide(); | ||||
|             if (this.elementDiv.is(":visible")) { | ||||
|                 this.input.focus(); | ||||
|                 this.element.focus(); | ||||
|             } else if (this.optionSelectTrigger.is(":visible")){ | ||||
|                 this.optionSelectTrigger.focus(); | ||||
|             } else { | ||||
| @@ -279,19 +221,10 @@ | ||||
|                     op.text(opt.label); | ||||
|                 } | ||||
|                 if (opt.icon) { | ||||
|                     if (opt.icon.indexOf("<") === 0) { | ||||
|                         $(opt.icon).prependTo(op); | ||||
|                     } else if (opt.icon.indexOf("/") !== -1) { | ||||
|                     $('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op); | ||||
|                     } else { | ||||
|                         $('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op); | ||||
|                     } | ||||
|                 } else { | ||||
|                     op.css({paddingLeft: "18px"}); | ||||
|                 } | ||||
|                 if (!opt.icon && !opt.label) { | ||||
|                     op.text(opt.value); | ||||
|                 } | ||||
| 
 | ||||
|                 op.click(function(event) { | ||||
|                     event.preventDefault(); | ||||
| @@ -370,8 +303,7 @@ | ||||
|             if (this.uiWidth !== null) { | ||||
|                 this.uiSelect.width(this.uiWidth); | ||||
|             } | ||||
|             var type = this.typeMap[this.propertyType]; | ||||
|             if (type && type.hasValue === false) { | ||||
|             if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) { | ||||
|                 this.selectTrigger.addClass("red-ui-typedInput-full-width"); | ||||
|             } else { | ||||
|                 this.selectTrigger.removeClass("red-ui-typedInput-full-width"); | ||||
| @@ -381,68 +313,13 @@ | ||||
|                     this.elementDiv.css('right',"22px"); | ||||
|                 } else { | ||||
|                     this.elementDiv.css('right','0'); | ||||
|                     this.input.css({ | ||||
|                         'border-top-right-radius': '4px', | ||||
|                         'border-bottom-right-radius': '4px' | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
|                 // if (this.optionSelectTrigger) {
 | ||||
|                 //     this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
 | ||||
|                 // }
 | ||||
| 
 | ||||
|                 if (this.optionSelectTrigger) { | ||||
|                     if (type && type.options && type.hasValue === true) { | ||||
|                         this.optionSelectLabel.css({'left':'auto'}) | ||||
|                         var lw = this._getLabelWidth(this.optionSelectLabel); | ||||
|                         this.optionSelectTrigger.css({'width':(23+lw)+"px"}); | ||||
|                         this.elementDiv.css('right',(23+lw)+"px"); | ||||
|                         this.input.css({ | ||||
|                             'border-top-right-radius': 0, | ||||
|                             'border-bottom-right-radius': 0 | ||||
|                         }); | ||||
|                     } else { | ||||
|                         this.optionSelectLabel.css({'left':'0'}) | ||||
|                         this.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'}); | ||||
|                         if (!this.optionExpandButton.is(":visible")) { | ||||
|                             this.elementDiv.css({'right':0}); | ||||
|                             this.input.css({ | ||||
|                                 'border-top-right-radius': '4px', | ||||
|                                 'border-bottom-right-radius': '4px' | ||||
|                             }); | ||||
|                     this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'}); | ||||
|                 } | ||||
|             } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         _updateOptionSelectLabel: function(o) { | ||||
|             var opt = this.typeMap[this.propertyType]; | ||||
|             this.optionSelectLabel.empty(); | ||||
|             if (o.icon) { | ||||
|                 if (o.icon.indexOf("<") === 0) { | ||||
|                     $(o.icon).prependTo(this.optionSelectLabel); | ||||
|                 } else if (o.icon.indexOf("/") !== -1) { | ||||
|                     // url
 | ||||
|                     $('<img>',{src:o.icon,style:"height: 18px;"}).prependTo(this.optionSelectLabel); | ||||
|                 } else { | ||||
|                     // icon class
 | ||||
|                     $('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); | ||||
|                 } | ||||
|             } else if (o.label) { | ||||
|                 this.optionSelectLabel.text(o.label); | ||||
|             } else { | ||||
|                 this.optionSelectLabel.text(o.value); | ||||
|             } | ||||
|             if (opt.hasValue) { | ||||
|                 this.optionValue = o.value; | ||||
|                 this._resize(); | ||||
|                 this.input.trigger('change',this.propertyType,this.value()); | ||||
|             } | ||||
|         }, | ||||
|         _destroy: function() { | ||||
|             if (this.optionMenu) { | ||||
|                 this.optionMenu.remove(); | ||||
|             } | ||||
|             this.menu.remove(); | ||||
|         }, | ||||
|         types: function(types) { | ||||
| @@ -460,18 +337,13 @@ | ||||
|                 return result; | ||||
|             }); | ||||
|             this.selectTrigger.toggleClass("disabled", this.typeList.length === 1); | ||||
|             this.selectTrigger.find(".fa-sort-desc").toggle(this.typeList.length > 1) | ||||
|             if (this.menu) { | ||||
|                 this.menu.remove(); | ||||
|             } | ||||
|             this.menu = this._createMenu(this.typeList, function(v) { that.type(v) }); | ||||
|             if (currentType && !this.typeMap.hasOwnProperty(currentType)) { | ||||
|                 this.type(this.typeList[0].value); | ||||
|             } else { | ||||
|                 this.propertyType = null; | ||||
|                 this.type(currentType); | ||||
|             } | ||||
|             setTimeout(function() {that._resize();},0); | ||||
|         }, | ||||
|         width: function(desiredWidth) { | ||||
|             this.uiWidth = desiredWidth; | ||||
| @@ -479,33 +351,16 @@ | ||||
|         }, | ||||
|         value: function(value) { | ||||
|             if (!arguments.length) { | ||||
|                 var v = this.input.val(); | ||||
|                 if (this.typeMap[this.propertyType].export) { | ||||
|                     v = this.typeMap[this.propertyType].export(v,this.optionValue) | ||||
|                 } | ||||
|                 return v; | ||||
|                 return this.element.val(); | ||||
|             } else { | ||||
|                 var selectedOption; | ||||
|                 if (this.typeMap[this.propertyType].options) { | ||||
|                     for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) { | ||||
|                         var op = this.typeMap[this.propertyType].options[i]; | ||||
|                         if (typeof op === "string") { | ||||
|                             if (op === value) { | ||||
|                                 selectedOption = this.activeOptions[op]; | ||||
|                                 break; | ||||
|                     if (this.typeMap[this.propertyType].options.indexOf(value) === -1) { | ||||
|                         value = ""; | ||||
|                     } | ||||
|                         } else if (op.value === value) { | ||||
|                             selectedOption = op; | ||||
|                             break; | ||||
|                     this.optionSelectLabel.text(value); | ||||
|                 } | ||||
|                     } | ||||
|                     if (!selectedOption) { | ||||
|                         selectedOption = {value:""} | ||||
|                     } | ||||
|                     this._updateOptionSelectLabel(selectedOption) | ||||
|                 } | ||||
|                 this.input.val(value); | ||||
|                 this.input.trigger('change',this.type(),value); | ||||
|                 this.element.val(value); | ||||
|                 this.element.trigger('change',this.type(),value); | ||||
|             } | ||||
|         }, | ||||
|         type: function(type) { | ||||
| @@ -516,136 +371,54 @@ | ||||
|                 var opt = this.typeMap[type]; | ||||
|                 if (opt && this.propertyType !== type) { | ||||
|                     this.propertyType = type; | ||||
|                     if (this.typeField) { | ||||
|                     this.typeField.val(type); | ||||
|                     } | ||||
|                     this.selectLabel.empty(); | ||||
|                     var image; | ||||
|                     if (opt.icon) { | ||||
|                         if (opt.icon.indexOf("<") === 0) { | ||||
|                             $(opt.icon).prependTo(this.selectLabel); | ||||
|                         } | ||||
|                         else if (opt.icon.indexOf("/") !== -1) { | ||||
|                         image = new Image(); | ||||
|                         image.name = opt.icon; | ||||
|                         image.src = opt.icon; | ||||
|                         $('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); | ||||
|                         } | ||||
|                         else { | ||||
|                             $('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); | ||||
|                         } | ||||
|                     } else { | ||||
|                         this.selectLabel.text(opt.label); | ||||
|                     } | ||||
|                     if (this.optionMenu) { | ||||
|                         this.optionMenu.remove(); | ||||
|                         this.optionMenu = null; | ||||
|                     } | ||||
|                     if (opt.options) { | ||||
|                         if (this.optionExpandButton) { | ||||
|                             this.optionExpandButton.hide(); | ||||
|                         } | ||||
|                         if (this.optionSelectTrigger) { | ||||
|                             this.optionSelectTrigger.show(); | ||||
|                             if (!opt.hasValue) { | ||||
|                             this.elementDiv.hide(); | ||||
|                             } else { | ||||
|                                 this.elementDiv.show(); | ||||
|                             } | ||||
|                             this.activeOptions = {}; | ||||
|                             opt.options.forEach(function(o) { | ||||
|                                 if (typeof o === 'string') { | ||||
|                                     that.activeOptions[o] = {label:o,value:o}; | ||||
|                                 } else { | ||||
|                                     that.activeOptions[o.value] = o; | ||||
|                                 } | ||||
|                             }); | ||||
| 
 | ||||
|                             if (!that.activeOptions.hasOwnProperty(that.optionValue)) { | ||||
|                                 that.optionValue = null; | ||||
|                             } | ||||
|                             this.optionMenu = this._createMenu(opt.options,function(v){ | ||||
|                                 that._updateOptionSelectLabel(that.activeOptions[v]); | ||||
|                                 if (!opt.hasValue) { | ||||
|                                     that.value(that.activeOptions[v].value) | ||||
|                                 } | ||||
|                                 that.optionSelectLabel.text(v); | ||||
|                                 that.value(v); | ||||
|                             }); | ||||
|                             var op; | ||||
|                             if (!opt.hasValue) { | ||||
|                                 var currentVal = this.input.val(); | ||||
|                                 var validValue = false; | ||||
|                                 for (var i=0;i<opt.options.length;i++) { | ||||
|                                     op = opt.options[i]; | ||||
|                                     if (typeof op === "string" && op === currentVal) { | ||||
|                                         that._updateOptionSelectLabel({value:currentVal}); | ||||
|                                         validValue = true; | ||||
|                                         break; | ||||
|                                     } else if (op.value === currentVal) { | ||||
|                                         that._updateOptionSelectLabel(op); | ||||
|                                         validValue = true; | ||||
|                                         break; | ||||
|                                     } | ||||
|                                 } | ||||
|                                 if (!validValue) { | ||||
|                                     op = opt.options[0]; | ||||
|                                     if (typeof op === "string") { | ||||
|                                         this.value(op); | ||||
|                                         that._updateOptionSelectLabel({value:op}); | ||||
|                             var currentVal = this.element.val(); | ||||
|                             if (opt.options.indexOf(currentVal) !== -1) { | ||||
|                                 this.optionSelectLabel.text(currentVal); | ||||
|                             } else { | ||||
|                                         this.value(op.value); | ||||
|                                         that._updateOptionSelectLabel(op); | ||||
|                                 this.value(opt.options[0]); | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|                                 var selectedOption = this.optionValue||opt.options[0]; | ||||
|                                 if (opt.parse) { | ||||
|                                     var parts = opt.parse(this.input.val()); | ||||
|                                     if (parts.option) { | ||||
|                                         selectedOption = parts.option; | ||||
|                                         if (!this.activeOptions.hasOwnProperty(selectedOption)) { | ||||
|                                             parts.option = Object.keys(this.activeOptions)[0]; | ||||
|                                             selectedOption = parts.option | ||||
|                         if (this.optionMenu) { | ||||
|                             this.optionMenu.remove(); | ||||
|                             this.optionMenu = null; | ||||
|                         } | ||||
|                                     } | ||||
|                                     this.input.val(parts.value); | ||||
|                                     if (opt.export) { | ||||
|                                         this.element.val(opt.export(parts.value,parts.option||selectedOption)); | ||||
|                                     } | ||||
|                                 } | ||||
|                                 if (typeof selectedOption === "string") { | ||||
|                                     this.optionValue = selectedOption; | ||||
|                                     if (!this.activeOptions.hasOwnProperty(selectedOption)) { | ||||
|                                         selectedOption = Object.keys(this.activeOptions)[0]; | ||||
|                                     } | ||||
|                                     if (!selectedOption) { | ||||
|                                         this.optionSelectTrigger.hide(); | ||||
|                                     } else { | ||||
|                                         this._updateOptionSelectLabel(this.activeOptions[selectedOption]); | ||||
|                                     } | ||||
|                                 } else if (selectedOption) { | ||||
|                                     this.optionValue = selectedOption.value; | ||||
|                                     this._updateOptionSelectLabel(selectedOption); | ||||
|                                 } else { | ||||
|                                     this.optionSelectTrigger.hide(); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|                         if (this.optionSelectTrigger) { | ||||
|                             this.optionSelectTrigger.hide(); | ||||
|                         } | ||||
|                         if (opt.hasValue === false) { | ||||
|                             this.oldValue = this.input.val(); | ||||
|                             this.input.val(""); | ||||
|                             this.oldValue = this.element.val(); | ||||
|                             this.element.val(""); | ||||
|                             this.elementDiv.hide(); | ||||
|                         } else { | ||||
|                             if (this.oldValue !== undefined) { | ||||
|                                 this.input.val(this.oldValue); | ||||
|                                 this.element.val(this.oldValue); | ||||
|                                 delete this.oldValue; | ||||
|                             } | ||||
|                             this.elementDiv.show(); | ||||
|                         } | ||||
|                         if (this.optionExpandButton) { | ||||
|                         if (opt.expand && typeof opt.expand === 'function') { | ||||
|                             this.optionExpandButton.show(); | ||||
|                             this.optionExpandButton.off('click'); | ||||
| @@ -656,8 +429,7 @@ | ||||
|                         } else { | ||||
|                             this.optionExpandButton.hide(); | ||||
|                         } | ||||
|                         } | ||||
|                         this.input.trigger('change',this.propertyType,this.value()); | ||||
|                         this.element.trigger('change',this.propertyType,this.value()); | ||||
|                     } | ||||
|                     if (image) { | ||||
|                         image.onload = function() { that._resize(); } | ||||
| @@ -67,10 +67,7 @@ RED.deploy = (function() { | ||||
|                   options: [ | ||||
|                       {id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}}, | ||||
|                       {id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}}, | ||||
|                       {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, | ||||
|                       null, | ||||
|                       {id:"deploymenu-item-reload", icon:"red/images/deploy-reload.png",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"}, | ||||
| 
 | ||||
|                       {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}} | ||||
|                   ] | ||||
|               }); | ||||
|         } else if (type == "simple") { | ||||
| @@ -99,8 +96,114 @@ RED.deploy = (function() { | ||||
|         }); | ||||
| 
 | ||||
|         RED.actions.add("core:deploy-flows",save); | ||||
|         RED.actions.add("core:restart-flows",restart); | ||||
| 
 | ||||
|         $( "#node-dialog-confirm-deploy" ).dialog({ | ||||
|                 title: RED._('deploy.confirm.button.confirm'), | ||||
|                 modal: true, | ||||
|                 autoOpen: false, | ||||
|                 width: 550, | ||||
|                 height: "auto", | ||||
|                 buttons: [ | ||||
|                     { | ||||
|                         text: RED._("common.label.cancel"), | ||||
|                         click: function() { | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "node-dialog-confirm-deploy-review", | ||||
|                         text: RED._("deploy.confirm.button.review"), | ||||
|                         class: "primary disabled", | ||||
|                         click: function() { | ||||
|                             if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) { | ||||
|                                 RED.diff.showRemoteDiff(); | ||||
|                                 $( this ).dialog( "close" ); | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "node-dialog-confirm-deploy-merge", | ||||
|                         text: RED._("deploy.confirm.button.merge"), | ||||
|                         class: "primary disabled", | ||||
|                         click: function() { | ||||
|                             RED.diff.mergeDiff(currentDiff); | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "node-dialog-confirm-deploy-deploy", | ||||
|                         text: RED._("deploy.confirm.button.confirm"), | ||||
|                         class: "primary", | ||||
|                         click: function() { | ||||
| 
 | ||||
|                             var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked"); | ||||
|                             if (ignoreChecked) { | ||||
|                                 ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true; | ||||
|                             } | ||||
|                             save(true,/conflict/.test($("#node-dialog-confirm-deploy-type" ).val())); | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         id: "node-dialog-confirm-deploy-overwrite", | ||||
|                         text: RED._("deploy.confirm.button.overwrite"), | ||||
|                         class: "primary", | ||||
|                         click: function() { | ||||
|                             save(true,/conflict/.test($("#node-dialog-confirm-deploy-type" ).val())); | ||||
|                             $( this ).dialog( "close" ); | ||||
|                         } | ||||
|                     } | ||||
|                 ], | ||||
|                 create: function() { | ||||
|                     $("#node-dialog-confirm-deploy").parent().find("div.ui-dialog-buttonpane") | ||||
|                         .prepend('<div style="height:0; vertical-align: middle; display:inline-block; margin-top: 13px; float:left;">'+ | ||||
|                                    '<input style="vertical-align:top;" type="checkbox" id="node-dialog-confirm-deploy-hide"> '+ | ||||
|                                    '<label style="display:inline;" for="node-dialog-confirm-deploy-hide" data-i18n="deploy.confirm.doNotWarn"></label>'+ | ||||
|                                    '<input type="hidden" id="node-dialog-confirm-deploy-type">'+ | ||||
|                                    '</div>'); | ||||
|                 }, | ||||
|                 open: function() { | ||||
|                     var deployType = $("#node-dialog-confirm-deploy-type" ).val(); | ||||
|                     if (/conflict/.test(deployType)) { | ||||
|                         $( "#node-dialog-confirm-deploy" ).dialog('option','title', RED._('deploy.confirm.button.review')); | ||||
|                         $("#node-dialog-confirm-deploy-deploy").hide(); | ||||
|                         $("#node-dialog-confirm-deploy-review").addClass('disabled').show(); | ||||
|                         $("#node-dialog-confirm-deploy-merge").addClass('disabled').show(); | ||||
|                         $("#node-dialog-confirm-deploy-overwrite").toggle(deployType === "deploy-conflict"); | ||||
|                         currentDiff = null; | ||||
|                         $("#node-dialog-confirm-deploy-conflict-checking").show(); | ||||
|                         $("#node-dialog-confirm-deploy-conflict-auto-merge").hide(); | ||||
|                         $("#node-dialog-confirm-deploy-conflict-manual-merge").hide(); | ||||
| 
 | ||||
|                         var now = Date.now(); | ||||
|                         RED.diff.getRemoteDiff(function(diff) { | ||||
|                             var ellapsed = Math.max(1000 - (Date.now()-now), 0); | ||||
|                             currentDiff = diff; | ||||
|                             setTimeout(function() { | ||||
|                                 $("#node-dialog-confirm-deploy-conflict-checking").hide(); | ||||
|                                 var d = Object.keys(diff.conflicts); | ||||
|                                 if (d.length === 0) { | ||||
|                                     $("#node-dialog-confirm-deploy-conflict-auto-merge").show(); | ||||
|                                     $("#node-dialog-confirm-deploy-merge").removeClass('disabled') | ||||
|                                 } else { | ||||
|                                     $("#node-dialog-confirm-deploy-conflict-manual-merge").show(); | ||||
|                                 } | ||||
|                                 $("#node-dialog-confirm-deploy-review").removeClass('disabled') | ||||
|                             },ellapsed); | ||||
|                         }) | ||||
| 
 | ||||
| 
 | ||||
|                         $("#node-dialog-confirm-deploy-hide").parent().hide(); | ||||
|                     } else { | ||||
|                         $( "#node-dialog-confirm-deploy" ).dialog('option','title', RED._('deploy.confirm.button.confirm')); | ||||
|                         $("#node-dialog-confirm-deploy-deploy").show(); | ||||
|                         $("#node-dialog-confirm-deploy-overwrite").hide(); | ||||
|                         $("#node-dialog-confirm-deploy-review").hide(); | ||||
|                         $("#node-dialog-confirm-deploy-merge").hide(); | ||||
|                         $("#node-dialog-confirm-deploy-hide").parent().show(); | ||||
|                     } | ||||
|                 } | ||||
|         }); | ||||
| 
 | ||||
|         RED.events.on('nodes:change',function(state) { | ||||
|             if (state.dirty) { | ||||
| @@ -121,30 +224,24 @@ RED.deploy = (function() { | ||||
|                 if (currentRev === null || deployInflight || currentRev === msg.revision) { | ||||
|                     return; | ||||
|                 } | ||||
|                 var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate')); | ||||
|                 activeNotifyMessage = RED.notify(message,{ | ||||
|                     modal: true, | ||||
|                     fixed: true, | ||||
|                     buttons: [ | ||||
|                         { | ||||
|                             text: RED._('deploy.confirm.button.ignore'), | ||||
|                             click: function() { | ||||
|                 var message = $('<div>'+RED._('deploy.confirm.backgroundUpdate')+ | ||||
|                     '<br><br><div class="ui-dialog-buttonset">'+ | ||||
|                     '<button>'+RED._('deploy.confirm.button.ignore')+'</button>'+ | ||||
|                     '<button class="primary">'+RED._('deploy.confirm.button.review')+'</button>'+ | ||||
|                     '</div></div>'); | ||||
|                 $(message.find('button')[0]).click(function(evt) { | ||||
|                     evt.preventDefault(); | ||||
|                     activeNotifyMessage.close(); | ||||
|                     activeNotifyMessage = null; | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             text: RED._('deploy.confirm.button.review'), | ||||
|                             class: "primary", | ||||
|                             click: function() { | ||||
|                 }) | ||||
|                 $(message.find('button')[1]).click(function(evt) { | ||||
|                     evt.preventDefault(); | ||||
|                     activeNotifyMessage.close(); | ||||
|                     var nns = RED.nodes.createCompleteNodeSet(); | ||||
|                     resolveConflict(nns,false); | ||||
|                     activeNotifyMessage = null; | ||||
|                             } | ||||
|                         } | ||||
|                     ] | ||||
|                 }); | ||||
|                 }) | ||||
|                 activeNotifyMessage = RED.notify(message,null,true); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| @@ -174,151 +271,16 @@ RED.deploy = (function() { | ||||
|     } | ||||
| 
 | ||||
|     function resolveConflict(currentNodes, activeDeploy) { | ||||
|         var message = $('<div>'); | ||||
|         $('<p data-i18n="deploy.confirm.conflict"></p>').appendTo(message); | ||||
|         var conflictCheck = $('<div id="node-dialog-confirm-deploy-conflict-checking" class="node-dialog-confirm-conflict-row">'+ | ||||
|             '<img src="red/images/spin.svg"/><div data-i18n="deploy.confirm.conflictChecking"></div>'+ | ||||
|         '</div>').appendTo(message); | ||||
|         var conflictAutoMerge = $('<div class="node-dialog-confirm-conflict-row">'+ | ||||
|             '<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>'+ | ||||
|             '</div>').hide().appendTo(message); | ||||
|         var conflictManualMerge = $('<div id="node-dialog-confirm-deploy-conflict-manual-merge" class="node-dialog-confirm-conflict-row">'+ | ||||
|             '<i style="color: #999;" class="fa fa-exclamation"></i><div data-i18n="deploy.confirm.conflictManualMerge"></div>'+ | ||||
|             '</div>').hide().appendTo(message); | ||||
|         $( "#node-dialog-confirm-deploy-config" ).hide(); | ||||
|         $( "#node-dialog-confirm-deploy-unknown" ).hide(); | ||||
|         $( "#node-dialog-confirm-deploy-unused" ).hide(); | ||||
|         $( "#node-dialog-confirm-deploy-conflict" ).show(); | ||||
|         $( "#node-dialog-confirm-deploy-type" ).val(activeDeploy?"deploy-conflict":"background-conflict"); | ||||
|         $( "#node-dialog-confirm-deploy" ).dialog( "open" ); | ||||
|     } | ||||
| 
 | ||||
|         message.i18n(); | ||||
|         currentDiff = null; | ||||
|         var buttons = [ | ||||
|             { | ||||
|                 text: RED._("common.label.cancel"), | ||||
|                 click: function() { | ||||
|                     conflictNotification.close(); | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 id: "node-dialog-confirm-deploy-review", | ||||
|                 text: RED._("deploy.confirm.button.review"), | ||||
|                 class: "primary disabled", | ||||
|                 click: function() { | ||||
|                     if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) { | ||||
|                         RED.diff.showRemoteDiff(); | ||||
|                         conflictNotification.close(); | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 id: "node-dialog-confirm-deploy-merge", | ||||
|                 text: RED._("deploy.confirm.button.merge"), | ||||
|                 class: "primary disabled", | ||||
|                 click: function() { | ||||
|                     if (!$("#node-dialog-confirm-deploy-merge").hasClass('disabled')) { | ||||
|                         RED.diff.mergeDiff(currentDiff); | ||||
|                         conflictNotification.close(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ]; | ||||
|         if (activeDeploy) { | ||||
|             buttons.push({ | ||||
|                 id: "node-dialog-confirm-deploy-overwrite", | ||||
|                 text: RED._("deploy.confirm.button.overwrite"), | ||||
|                 class: "primary", | ||||
|                 click: function() { | ||||
|                     save(true,activeDeploy); | ||||
|                     conflictNotification.close(); | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|         var conflictNotification = RED.notify(message,{ | ||||
|             modal: true, | ||||
|             fixed: true, | ||||
|             width: 600, | ||||
|             buttons: buttons | ||||
|         }); | ||||
| 
 | ||||
|         var now = Date.now(); | ||||
|         RED.diff.getRemoteDiff(function(diff) { | ||||
|             var ellapsed = Math.max(1000 - (Date.now()-now), 0); | ||||
|             currentDiff = diff; | ||||
|             setTimeout(function() { | ||||
|                 conflictCheck.hide(); | ||||
|                 var d = Object.keys(diff.conflicts); | ||||
|                 if (d.length === 0) { | ||||
|                     conflictAutoMerge.show(); | ||||
|                     $("#node-dialog-confirm-deploy-merge").removeClass('disabled') | ||||
|                 } else { | ||||
|                     conflictManualMerge.show(); | ||||
|                 } | ||||
|                 $("#node-dialog-confirm-deploy-review").removeClass('disabled') | ||||
|             },ellapsed); | ||||
|         }) | ||||
|     } | ||||
|     function cropList(list) { | ||||
|         if (list.length > 5) { | ||||
|             var remainder = list.length - 5; | ||||
|             list = list.slice(0,5); | ||||
|             list.push(RED._("deploy.confirm.plusNMore",{count:remainder})); | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
|     function sanitize(html) { | ||||
|         return html.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">") | ||||
|     } | ||||
|     function restart() { | ||||
|         var startTime = Date.now(); | ||||
|         $(".deploy-button-content").css('opacity',0); | ||||
|         $(".deploy-button-spinner").show(); | ||||
|         var deployWasEnabled = !$("#btn-deploy").hasClass("disabled"); | ||||
|         $("#btn-deploy").addClass("disabled"); | ||||
|         deployInflight = true; | ||||
|         $("#header-shade").show(); | ||||
|         $("#editor-shade").show(); | ||||
|         $("#palette-shade").show(); | ||||
|         $("#sidebar-shade").show(); | ||||
| 
 | ||||
|         $.ajax({ | ||||
|             url:"flows", | ||||
|             type: "POST", | ||||
|             headers: { | ||||
|                 "Node-RED-Deployment-Type":"reload" | ||||
|             } | ||||
|         }).done(function(data,textStatus,xhr) { | ||||
|             if (deployWasEnabled) { | ||||
|                 $("#btn-deploy").removeClass("disabled"); | ||||
|             } | ||||
|             RED.notify('<p>'+RED._("deploy.successfulRestart")+'</p>',"success"); | ||||
|         }).fail(function(xhr,textStatus,err) { | ||||
|             if (deployWasEnabled) { | ||||
|                 $("#btn-deploy").removeClass("disabled"); | ||||
|             } | ||||
|             if (xhr.status === 401) { | ||||
|                 RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error"); | ||||
|             } else if (xhr.status === 409) { | ||||
|                 resolveConflict(nns, true); | ||||
|             } else if (xhr.responseText) { | ||||
|                 RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error"); | ||||
|             } else { | ||||
|                 RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error"); | ||||
|             } | ||||
|         }).always(function() { | ||||
|             deployInflight = false; | ||||
|             var delta = Math.max(0,300-(Date.now()-startTime)); | ||||
|             setTimeout(function() { | ||||
|                 $(".deploy-button-content").css('opacity',1); | ||||
|                 $(".deploy-button-spinner").hide(); | ||||
|                 $("#header-shade").hide(); | ||||
|                 $("#editor-shade").hide(); | ||||
|                 $("#palette-shade").hide(); | ||||
|                 $("#sidebar-shade").hide(); | ||||
|             },delta); | ||||
|         }); | ||||
|     } | ||||
|     function save(skipValidation,force) { | ||||
|         if (!$("#btn-deploy").hasClass("disabled")) { | ||||
|             if (!RED.user.hasPermission("flows.write")) { | ||||
|                 RED.notify(RED._("user.errors.deploy"),"error"); | ||||
|                 return; | ||||
|             } | ||||
|             if (!skipValidation) { | ||||
|                 var hasUnknown = false; | ||||
|                 var hasInvalid = false; | ||||
| @@ -342,68 +304,45 @@ RED.deploy = (function() { | ||||
| 
 | ||||
|                 var unusedConfigNodes = []; | ||||
|                 RED.nodes.eachConfig(function(node) { | ||||
|                     if ((node._def.hasUsers !== false) && (node.users.length === 0)) { | ||||
|                     if (node.users.length === 0 && (node._def.hasUsers !== false)) { | ||||
|                         unusedConfigNodes.push(getNodeInfo(node)); | ||||
|                         hasUnusedConfig = true; | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|                 $( "#node-dialog-confirm-deploy-config" ).hide(); | ||||
|                 $( "#node-dialog-confirm-deploy-unknown" ).hide(); | ||||
|                 $( "#node-dialog-confirm-deploy-unused" ).hide(); | ||||
|                 $( "#node-dialog-confirm-deploy-conflict" ).hide(); | ||||
| 
 | ||||
|                 var showWarning = false; | ||||
|                 var notificationMessage; | ||||
|                 var notificationButtons = []; | ||||
|                 var notification; | ||||
| 
 | ||||
|                 if (hasUnknown && !ignoreDeployWarnings.unknown) { | ||||
|                     showWarning = true; | ||||
|                     notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+ | ||||
|                         '<ul class="node-dialog-configm-deploy-list"><li>'+cropList(unknownNodes).map(function(n) { return sanitize(n) }).join("</li><li>")+"</li></ul><p>"+ | ||||
|                         RED._('deploy.confirm.confirm')+ | ||||
|                         "</p>"; | ||||
| 
 | ||||
|                     notificationButtons= [ | ||||
|                         { | ||||
|                             id: "node-dialog-confirm-deploy-deploy", | ||||
|                             text: RED._("deploy.confirm.button.confirm"), | ||||
|                             class: "primary", | ||||
|                             click: function() { | ||||
|                                 save(true); | ||||
|                                 notification.close(); | ||||
|                             } | ||||
|                         } | ||||
|                     ]; | ||||
|                     $( "#node-dialog-confirm-deploy-type" ).val("unknown"); | ||||
|                     $( "#node-dialog-confirm-deploy-unknown" ).show(); | ||||
|                     $( "#node-dialog-confirm-deploy-unknown-list" ) | ||||
|                         .html("<li>"+unknownNodes.join("</li><li>")+"</li>"); | ||||
|                 } else if (hasInvalid && !ignoreDeployWarnings.invalid) { | ||||
|                     showWarning = true; | ||||
|                     $( "#node-dialog-confirm-deploy-type" ).val("invalid"); | ||||
|                     $( "#node-dialog-confirm-deploy-config" ).show(); | ||||
|                     invalidNodes.sort(sortNodeInfo); | ||||
|                     $( "#node-dialog-confirm-deploy-invalid-list" ) | ||||
|                         .html("<li>"+invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>"); | ||||
| 
 | ||||
|                     notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+ | ||||
|                         '<ul class="node-dialog-configm-deploy-list"><li>'+cropList(invalidNodes.map(function(A) { return sanitize( (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")")})).join("</li><li>")+"</li></ul><p>"+ | ||||
|                         RED._('deploy.confirm.confirm')+ | ||||
|                         "</p>"; | ||||
|                     notificationButtons= [ | ||||
|                         { | ||||
|                             id: "node-dialog-confirm-deploy-deploy", | ||||
|                             text: RED._("deploy.confirm.button.confirm"), | ||||
|                             class: "primary", | ||||
|                             click: function() { | ||||
|                                 save(true); | ||||
|                                 notification.close(); | ||||
|                             } | ||||
|                         } | ||||
|                     ]; | ||||
|                 } else if (hasUnusedConfig && !ignoreDeployWarnings.unusedConfig) { | ||||
|                     // showWarning = true;
 | ||||
|                     // $( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
 | ||||
|                     // $( "#node-dialog-confirm-deploy-unused" ).show();
 | ||||
|                     //
 | ||||
|                     // unusedConfigNodes.sort(sortNodeInfo);
 | ||||
|                     // $( "#node-dialog-confirm-deploy-unused-list" )
 | ||||
|                     //     .html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
 | ||||
|                 } | ||||
|                 if (showWarning) { | ||||
|                     notificationButtons.unshift( | ||||
|                         { | ||||
|                             text: RED._("common.label.cancel"), | ||||
|                             click: function() { | ||||
|                                 notification.close(); | ||||
|                             } | ||||
|                         } | ||||
|                     ); | ||||
|                     notification = RED.notify(notificationMessage,{ | ||||
|                         modal: true, | ||||
|                         fixed: true, | ||||
|                         buttons:notificationButtons | ||||
|                     }); | ||||
|                     $( "#node-dialog-confirm-deploy-hide" ).prop("checked",false); | ||||
|                     $( "#node-dialog-confirm-deploy" ).dialog( "open" ); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| @@ -443,7 +382,7 @@ RED.deploy = (function() { | ||||
|                     '<p>'+RED._("deploy.successfulDeploy")+'</p>'+ | ||||
|                     '<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000); | ||||
|                 } else { | ||||
|                     RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success"); | ||||
|                     RED.notify(RED._("deploy.successfulDeploy"),"success"); | ||||
|                 } | ||||
|                 RED.nodes.eachNode(function(node) { | ||||
|                     if (node.changed) { | ||||
| @@ -464,12 +403,9 @@ RED.deploy = (function() { | ||||
|                         delete confNode.credentials; | ||||
|                     } | ||||
|                 }); | ||||
|                 RED.nodes.eachSubflow(function(subflow) { | ||||
|                     subflow.changed = false; | ||||
|                 }); | ||||
|                 RED.nodes.eachWorkspace(function(ws) { | ||||
|                     ws.changed = false; | ||||
|                 }); | ||||
|                 }) | ||||
|                 // Once deployed, cannot undo back to a clean state
 | ||||
|                 RED.history.markAllDirty(); | ||||
|                 RED.view.redraw(); | ||||
| @@ -501,10 +437,6 @@ RED.deploy = (function() { | ||||
|         } | ||||
|     } | ||||
|     return { | ||||
|         init: init, | ||||
|         setDeployInflight: function(state) { | ||||
|             deployInflight = state; | ||||
|         } | ||||
| 
 | ||||
|         init: init | ||||
|     } | ||||
| })(); | ||||