Compare commits

..

272 Commits

Author SHA1 Message Date
Dave Conway-Jones
e86f6a841a fix numeric status not displaying by ensuring it's a string 2021-02-05 11:36:26 +00:00
Nick O'Leary
23f0cd3a26 Bump for 1.2.8 2021-02-02 13:11:33 +00:00
Nick O'Leary
74db3e17d0 Restrict project file access to inside the project directory 2021-02-01 13:39:39 +00:00
Matthias Radde
7bde7f0cfd fixing minor typo in node's documentation (#2848) 2021-01-30 18:30:29 +00:00
Nick O'Leary
7068c175f2 Ensure subflow help is picked up for palette tooltip
Fixes #2834
2021-01-27 22:53:06 +00:00
Nick O'Leary
9b5ed8407f Broaden lang verification to include * 2021-01-27 22:06:12 +00:00
Nick O'Leary
37935bf388 Merge pull request #2748 from neuroforgede/master
make split node work with out of order messages
2021-01-27 20:35:32 +00:00
Alex Kaul
70554e24b1 Improve Ru locale (#2826)
* Update Russian Locale

* Upd ru translation for "timestamp"

* Improve node help texts for ru locale

* Improve editor texts for ru locale
2021-01-25 17:25:06 +00:00
Nick O'Leary
a0f736bb88 Validate user-provided language parameter before passing to i18n 2021-01-25 17:06:27 +00:00
Alex Kaul
79473c243d Fix grunt release mkdir issue on Node.js 14 (#2827)
* Update Russian Locale

* Upd grunt-mkdir
2021-01-25 11:03:51 +00:00
Alex Kaul
441eb3bb29 Fix scrollbars (#2825)
* Update Russian Locale

* Fix scrollbars
2021-01-25 11:02:43 +00:00
Ben Hardill
ca44af0625 Prevent crash when coreNodesDir is empty (#2831)
* Fix for HTTP-Request not sending body for GET

Background in SO question:
https://stackoverflow.com/q/60356824/504554

* Prevent crash when coreNodesDir points to empty dir

This should prevent a crash when you point to an empty core nodes
directory.

* Matching upstream master
2021-01-25 10:56:23 +00:00
martinb
b0acb58442 Merge tag '1.2.7' into release/1.2.7 2021-01-14 15:46:22 +01:00
Nick O'Leary
06ceb056f3 Update build.yml 2021-01-07 10:09:49 +00:00
Nick O'Leary
abe77ab96f Bump for 1.2.7 2021-01-06 11:49:31 +00:00
Nick O'Leary
ea720bb4a5 Bump dependencies 2021-01-06 11:41:17 +00:00
Nick O'Leary
6ee2e2b570 Merge pull request #2777 from aaronmyatt/improve-test-coverage-in-editor-api-index
Improve editor api index test coverage
2021-01-06 11:39:30 +00:00
Nick O'Leary
3885107e6e Merge pull request #2805 from kelvininc/user_menu_theme_improvement
Allow to explicit use userMenu in the theme configuration
2021-01-06 11:37:22 +00:00
Nick O'Leary
30a68fefec Ensure subflow-scoped config nodes do not get moved on import
Fixes #2789
2021-01-06 11:22:52 +00:00
Tiago Ferreira
fa84c4e461 Allow to explicit use userMenu in the theme configuration
Unit test to ensure that   works after the theme is initialize

Allow to explicti use userMenu in the theme configuration
2020-12-29 22:06:02 +00:00
David Mödinger
c433f736a5 Improvements to DE translation (#2192)
* Gitter->Raster inconsistency

* Set to -> Festlegen bei // Setzen als
2020-12-17 08:25:35 +00:00
aaronmyatt
55e6c6e01a adds tests for editor-api.start() 2020-12-16 21:53:52 +08:00
Nick O'Leary
496b5a092f Ensure subflow credential objects exist
Fixes #2783
2020-12-15 17:20:22 +00:00
Nick O'Leary
02510efda1 Merge pull request #2781 from johnwang71/flow-undefined-474
Fix bug: Crash & quit while handling exception with undefine msg.error
2020-12-15 13:37:07 +00:00
johnwang71
be828af3e2 Fix bug: Crash & quit while handling exception with undefine msg.error. i.e. flow with 3 nodes, http-in, delay 5-10s, http-out; client with 3s timeout request the flow; TypeError: Cannot read property 'hasOwnProperty' of undefined\r at Flow.handleError (/usr/src/node-red/node_modules/@node-red/runtime/lib/flows/Flow.js:474:27) 2020-12-14 18:18:50 +08:00
Nick O'Leary
df1eb631e1 Merge pull request #2752 from bartbutenaers/readonly-typedinput
Allow TypedInput to be disabled
2020-12-07 12:21:53 +00:00
Nick O'Leary
9f3e9786a8 Disable nyc coverage reporting on older node versions 2020-12-07 11:46:14 +00:00
aaronmyatt
c9bc530df0 tests custom cors settings 2020-12-06 15:29:54 +08:00
aaronmyatt
0b569a4120 exercise admin auth pathways 2020-12-05 23:06:18 +08:00
aaronmyatt
950fd7d2cf removes unused dependencies 2020-12-05 15:15:36 +08:00
aaronmyatt
50dd0354d1 adds admin middleware tests 2020-12-04 23:10:28 +08:00
Dave Conway-Jones
78f1cb8a66 ensure trigger timestamp option sends .now()
To close #2771
2020-12-01 23:05:22 +00:00
Nick O'Leary
4bfe9a9ae9 Bump for 1.2.6 2020-11-25 21:09:45 +00:00
Nathanaël Lécaudé
c5d38d8962 Library: properly handle symlinked folders 2020-11-25 21:08:43 +00:00
Nick O'Leary
2f86bb1ca5 Update MQTT to latest to fix Node 8 URL breakage 2020-11-25 21:05:31 +00:00
Nick O'Leary
3999690062 Support Windows paths when installing tarball by path name
Fixes #2769
2020-11-25 21:04:24 +00:00
Kazuhito Yokoi
d57edaa4c1 Update Japanese translations for 1.2.5 (#2764) 2020-11-19 15:09:30 +00:00
Nick O'Leary
088419b38e Fix unsecure command usage in GH Action 2020-11-18 11:02:09 +00:00
Nick O'Leary
8ebcee32c2 Bump for 1.2.5 2020-11-17 23:09:07 +00:00
Nick O'Leary
2b801a756a Fix import of config nodes with unknown z property 2020-11-17 23:07:43 +00:00
Nick O'Leary
98b639540b Set ACTIONS_ALLOW_UNSECURE_COMMANDS in GH Action 2020-11-17 22:09:17 +00:00
Nick O'Leary
e0b797fc7e Update changelog 2020-11-17 21:06:21 +00:00
Nick O'Leary
795416a84d Bump for 1.2.4 2020-11-17 21:03:24 +00:00
Nick O'Leary
545dda166f Support bigint types in Debug sidebar 2020-11-17 20:50:29 +00:00
Nick O'Leary
f19ec5d9b6 Clear retained status of deleted nodes 2020-11-17 13:29:13 +00:00
Nick O'Leary
6ea978d83d Prevent needless retention of node status messages 2020-11-16 21:05:13 +00:00
Alex Kaul
42f3b70a22 Update Russian Locale (#2761) 2020-11-16 18:44:18 +00:00
Nick O'Leary
1cd10f074b Update projects dialogs to use TypedInput-cred input 2020-11-16 11:37:32 +00:00
Nick O'Leary
bed1d31bc8 Restore cursor position in TypedInput cred-mode 2020-11-16 11:37:04 +00:00
Nick O'Leary
99478897c5 Ensure config nodes with invalid z are imported somewhere 2020-11-14 14:10:32 +00:00
Nick O'Leary
d79cd463a0 Disable projects when flowFile passed into grunt dev
Useful for quickly testing a standalone flow file
2020-11-14 14:09:24 +00:00
martinb
ccf4e73701 cleanup test case for support of out of order messages 2020-11-12 18:56:43 +01:00
martinb
01b67c692b add test case for support of out of order messages support in auto mode of join node if exactly one message has count set 2020-11-12 18:51:14 +01:00
Alex Kaul
4023ab3f28 Add Russian Locale (#2531) 2020-11-12 17:01:44 +00:00
Kazuhito Yokoi
70f3b7450f Add Japanese translation for http-in node (#2758) 2020-11-12 10:21:45 +00:00
Dave Conway-Jones
ca4960e097 Fix CSV node repeating array output
and add tests to cover it
2020-11-10 14:43:59 +00:00
Nick O'Leary
ebe604e1af Ensure user keyboard shortcuts override defaults
Fixes #2753
2020-11-09 21:13:20 +00:00
bartbutenaers
32b04cd32f Disable TypedInput 2020-11-06 08:48:14 +01:00
bartbutenaers
e149174696 Disable TypedInput 2020-11-06 08:45:50 +01:00
Nick O'Leary
f878ffc01b Update changelog 2020-11-05 13:53:22 +00:00
Nick O'Leary
15b49f4db8 Disable 'use strict' checking in Function node
Fixes #2743
2020-11-05 13:48:55 +00:00
Dave Conway-Jones
b1cc7b3296 de-duplicate colour keys 2020-11-05 09:38:34 +00:00
Dave Conway-Jones
65d90a6dff Add gray/grey alternate options for status 2020-11-05 09:20:47 +00:00
Dave Conway-Jones
a58f4c2ec2 remove " from npm install prefix option
to fix npm 7
2020-11-05 09:19:47 +00:00
Nick O'Leary
75d7ac2d8a Merge pull request #2747 from node-red-hitachi/update-message-jp
update Japanese message catalogue for 1.2.3 release
2020-11-03 10:42:45 +00:00
martinb
468cfeffb6 make split node work with out of order messages as long as one of the messages has msg.parts.count set to the proper value 2020-11-03 09:35:21 +01:00
Hiroyasu Nishiyama
6720c1aa46 update Japanese message catalogue for 1.2.3 release 2020-11-03 09:16:13 +09:00
Nick O'Leary
280203e64e Move mosca to ui-test-dependencies list 2020-11-02 21:32:20 +00:00
Nick O'Leary
281d8b7cec Bump for 1.2.3 2020-11-02 21:31:27 +00:00
Nick O'Leary
2c6cda1f27 Handle import errors on initial load and report to user 2020-11-02 21:14:24 +00:00
Nick O'Leary
fa532da8c7 Merge pull request #2739 from node-red/settings-file
Modify default settings comment
2020-10-29 15:42:00 +00:00
Nick O'Leary
cbf84647de Modify default settings comment 2020-10-29 11:51:30 +00:00
Nick O'Leary
c38a490a6f Merge pull request #2737 from node-red/fix-async-settings
Add mutex lock to saveSettings storage call
2020-10-28 22:14:25 +00:00
Nick O'Leary
9d7a450821 Add mutex lock to saveSettings storage call
Fixes #2736
2020-10-28 21:59:22 +00:00
Nick O'Leary
0ecd9673b8 Only apply recovery tab on initial load
Fixes #2731
2020-10-21 10:36:47 +01:00
Nick O'Leary
97aa1230ef Reinstate coveralls reporting to travis build 2020-10-19 21:22:15 +01:00
Nick O'Leary
ff0be73b1f Migrate to nyc instead of istanbul for code coverage 2020-10-19 21:10:34 +01:00
Nick O'Leary
8049e44dec Update CHANGELOG for 1.2.2 2020-10-19 13:25:38 +01:00
Nick O'Leary
dc26022fb4 Prevent node z property getting set to 0 or "" 2020-10-19 13:24:04 +01:00
Nick O'Leary
e8e44f9a32 Only apply z-recovery logic to flow nodes 2020-10-19 13:23:43 +01:00
Nick O'Leary
12d56b8b03 Fix api call to reload flows
Fixes #2726
2020-10-19 12:56:40 +01:00
Nick O'Leary
e62fd7ed15 Remove bad z property from import config nodes 2020-10-19 12:53:03 +01:00
Nick O'Leary
978eb95acd Bump for 1.2.1 2020-10-15 16:22:37 +01:00
Nick O'Leary
e34f4acb22 Fix race condition in .config file migration
Fixes #2724
2020-10-15 16:21:28 +01:00
Nick O'Leary
15a600c763 Fix tab selection after sidebar tab reorder 2020-10-14 22:10:03 +01:00
Nick O'Leary
82ad5839fa Update changelog and bump dependencies 2020-10-13 21:49:11 +01:00
Nick O'Leary
9af883231d Merge pull request #2722 from node-red-hitachi/fix-link-selection
fix selection of link node not existing within active workspace
2020-10-13 13:56:53 +01:00
Hiroyasu Nishiyama
9bfe8ac007 fix selection of link node not existing within active workspace 2020-10-12 20:16:21 +09:00
Nick O'Leary
f46367d77b Fix import of merged flow 2020-10-12 11:20:44 +01:00
Nick O'Leary
eb2e1c0c45 Merge pull request #2718 from node-red-hitachi/fix-upload-button-width-on-safari
Fix upload button width on Safari
2020-10-09 17:12:35 +01:00
Jiye Yu
baffc2d6ca update Chinese translation for NodeRed v1.2 (#2719) 2020-10-08 13:17:04 +01:00
Hiroyasu Nishiyama
96ab508c91 move width specification of upload button to scss 2020-10-08 09:07:11 +09:00
Nick O'Leary
57e42659e3 Merge pull request #2716 from node-red-hitachi/fix-sidebar-tab-popup
Fix unexpected line break of sidebar tab name popover
2020-10-07 12:33:02 +01:00
Nick O'Leary
f059e97697 Merge pull request #2717 from node-red-hitachi/i18n-module-list-refresh-tooltip
i18n module refresh tooltip
2020-10-07 11:37:01 +01:00
Hiroyasu Nishiyama
516e6430eb fix upload button width on safari 2020-10-07 13:42:43 +09:00
Hiroyasu Nishiyama
f194a8ecf4 i18n module refresh tooltip 2020-10-07 11:08:23 +09:00
Hiroyasu Nishiyama
13f046f310 fix unexpected line break of sidebar tab name popover 2020-10-07 09:57:34 +09:00
Nick O'Leary
1edf5acb87 Merge pull request #2714 from node-red-hitachi/update-function-node-help-text
Update info text of Function node
2020-10-06 17:46:11 +01:00
Nick O'Leary
af636870d4 Add better error message if context file gets corrupted 2020-10-06 15:42:52 +01:00
Hiroyasu Nishiyama
379b8ada61 update info text of function node 2020-10-06 13:45:00 +09:00
Nick O'Leary
5e63471983 Use markdown editor if editText called with md mode 2020-10-05 20:38:05 +01:00
Nick O'Leary
086f0f8450 Prevent group actions when in non-default mouse mode 2020-10-02 16:07:22 +01:00
Nick O'Leary
97a4b3dc2a Merge branch 'pr_2709' into dev 2020-10-02 11:14:16 +01:00
Kazuhito Yokoi
4eb8d681c1 Update Japanese translations needed for 1.2 (#2710) 2020-10-02 11:07:01 +01:00
Hiroyasu Nishiyama
2066584164 fix to make Japanese import dialogue message single line 2020-10-01 19:25:21 +09:00
Nick O'Leary
a954c198fb Bump version for 1.2.0-beta.1 2020-09-30 10:20:41 +01:00
Nick O'Leary
bb1f8cd5e8 Update CHANGELOG and bump package.json 2020-09-29 20:43:03 +01:00
Nick O'Leary
101e96dcb3 Merge pull request #2665 from node-red/msg-router
Pluggable Message Routing
2020-09-29 20:36:23 +01:00
Nick O'Leary
59adf82895 Merge pull request #2707 from node-red/trigger-delay-override
Allow trigger node delay to be overridden with msg.delay
2020-09-29 20:29:10 +01:00
Nick O'Leary
3b68f56b15 Update top-level copyright statements 2020-09-29 20:28:15 +01:00
Nick O'Leary
2962c4372c Support setting trigger loop interval with msg.delay 2020-09-29 17:47:09 +01:00
Nick O'Leary
517e376582 Restore support for runtimeSyncDelivery flag 2020-09-29 17:39:29 +01:00
Nick O'Leary
7a90fe5aec Fix flow api unit tests 2020-09-29 17:35:43 +01:00
Nick O'Leary
ea45dde63a Remove when.js from runtime/lib/flow/index 2020-09-29 17:20:01 +01:00
Nick O'Leary
22a301b55e Add flows:* events and deprecate nodes-* events 2020-09-29 16:29:10 +01:00
Nick O'Leary
605177dcf0 Validate hook names when they are added 2020-09-29 16:28:52 +01:00
Nick O'Leary
460e1f5563 Fixup merge error 2020-09-29 12:19:27 +01:00
Nick O'Leary
6f25337b99 Add docs for RED.hooks 2020-09-29 12:19:27 +01:00
Nick O'Leary
08148a07b2 Update Node/Flow to trigger msg routing hooks 2020-09-29 12:19:27 +01:00
Nick O'Leary
27c0e45940 Remove unused router component 2020-09-29 12:19:27 +01:00
Nick O'Leary
bdd736315a Add RED.hooks engine 2020-09-29 12:19:27 +01:00
Nick O'Leary
d57ec0cd53 Refactor lib/flows code to include initial router component 2020-09-29 12:19:26 +01:00
Nick O'Leary
952c9d8bdb Upgrade to latest nodemon to fix restart
The grunt-nodemon module we were using is no longer
maintained and is stuck on a 1.x version of nodemon.

At that version, node-red doesn't restart properly
due to our increased signal handling in the core.

This change removes grunt-nodemon and replaces it
with nodemon itself, with a custom task to wrap it
that does the same work as grunt-nodemon was doing.
2020-09-29 12:11:10 +01:00
Nick O'Leary
cf84ec78fa Allow trigger node delay to be overridden with msg.delay 2020-09-28 21:10:23 +01:00
Nick O'Leary
b595e84c30 Update jsdoc of util.getMessageProperty 2020-09-28 14:26:20 +01:00
Nick O'Leary
6e5f115bd5 Improve jsdoc of util.getObjectProperty to clarify thrown error
See #2703
2020-09-28 14:23:23 +01:00
Nick O'Leary
a1ab00c93e sort package dependencies 2020-09-28 14:13:53 +01:00
Nick O'Leary
6e5c4e832e Update dependencies 2020-09-28 11:58:22 +01:00
Nick O'Leary
48ea487974 Update changelog 2020-09-28 11:40:38 +01:00
Nick O'Leary
54dc98a90b Merge pull request #2035 from node-red/simple-git
Add option for simplified git workglow
2020-09-28 11:39:17 +01:00
Nick O'Leary
c5bdd3d056 Allow user to manage project version string 2020-09-28 11:30:46 +01:00
Nick O'Leary
64d6e1f8e1 Changing timing in trigger node test 2020-09-28 10:50:19 +01:00
Nick O'Leary
69d60ffb24 Add simplified git workflow to auto-commit changes 2020-09-28 10:41:33 +01:00
Nick O'Leary
e6ffa3d143 Cache settings when doing initial load 2020-09-28 10:41:05 +01:00
Nick O'Leary
bb4330e486 Clone settings before passing to storage layer
Avoids the storage layer modifying the in-memory object
2020-09-28 10:40:03 +01:00
Nick O'Leary
1a4d720978 Improve timings in trigger node test 2020-09-25 23:32:59 +01:00
Nick O'Leary
91c2f479bb Fix settings file migration test 2020-09-25 18:29:47 +01:00
Nick O'Leary
b61701fa17 Remove UI tests from travis config 2020-09-25 18:00:32 +01:00
Nick O'Leary
cb9f910642 Update changelog 2020-09-25 17:19:02 +01:00
Nick O'Leary
4b8d07f301 Merge branch 'pr_2644' into dev 2020-09-25 17:11:39 +01:00
Nick O'Leary
2db3a4f1ef Add unit tests for function node init code 2020-09-25 17:11:10 +01:00
Nick O'Leary
ead1f65887 Disable selection FA icons when dbl clicking node 2020-09-25 16:03:21 +01:00
Nick O'Leary
aae9866866 Update changelog 2020-09-25 15:58:33 +01:00
Nick O'Leary
085ff84bc9 Merge pull request #2704 from node-red/split-config
Split .config.json into separate files
2020-09-25 15:56:32 +01:00
Nick O'Leary
e12975cf0b Do not remove old config.json file to ease downgrade path 2020-09-25 15:50:26 +01:00
Nick O'Leary
a33cf6b532 Merge branch 'master' into dev 2020-09-25 11:53:37 +01:00
Nick O'Leary
5be25d9538 Merge branch 'pr_2705' 2020-09-25 11:53:15 +01:00
Nick O'Leary
2b29eeb795 Add unit test for module path 2020-09-25 11:52:48 +01:00
t.kawamorita
785561a0cc fix getModuleFiles function 2020-09-25 11:58:03 +09:00
Nick O'Leary
b46dc88346 Update changelog 2020-09-24 18:12:52 +01:00
Nick O'Leary
96d81ef72b Add slight delay to fix config.json file tests 2020-09-24 16:54:24 +01:00
Nick O'Leary
81dc3de26a Ensure errors in ACE NRJavaScript mode are on valid lines
In the case of an "Unmatched {" error, it flags the { on
the line we wrap the user's code in. That doesn't help the
user.

This fix moves such an error to the first valid { in the file.

It handles ignoring { in comments or strings. It fails to ignore
{ inside regex. But that's an edge case on top of an edge case.
2020-09-24 16:29:05 +01:00
Nick O'Leary
4d0c572c2e Fix .config.json unit tests 2020-09-24 15:42:52 +01:00
Nick O'Leary
fb2da0ee9e Split .config.json into separate files 2020-09-23 17:29:09 +01:00
Nick O'Leary
b8b0247717 Default flowFilePretty to true if projects enabled 2020-09-23 10:57:58 +01:00
Nick O'Leary
103e212aee Fix size of context sidebar refresh toggle buttons 2020-09-22 23:44:10 +01:00
Nick O'Leary
2f33575907 Merge branch 'dev' of github.com:node-red/node-red into dev 2020-09-21 20:12:42 +01:00
Nick O'Leary
576c528573 Merge pull request #2698 from node-red/import-dupes
Improved handling of importing duplicate subflow/config nodes
2020-09-21 18:30:15 +01:00
Nick O'Leary
3c444d3fb3 Merge branch 'master' into dev 2020-09-21 14:25:30 +01:00
Nick O'Leary
7cb499cde9 Sanitize unknown node type when displaying 2020-09-21 14:00:01 +01:00
Nick O'Leary
5a174ba014 Add comment highlighting to JSONata and fix regex handling
Closes #2701
2020-09-21 11:52:33 +01:00
Nick O'Leary
041feb4e86 Ensure sf instance nodes update in outliner when import-replace sf 2020-09-18 20:56:16 +01:00
Nick O'Leary
19726cf428 Fix setting of esversion in nrjavascript worker 2020-09-16 23:21:52 +01:00
Nick O'Leary
aaf134b1c5 Update ACE to 1.4.12-src-min-noconflict
Fixes #1988
2020-09-16 19:13:40 +01:00
Nick O'Leary
9d5f5ee94b Update to MQTT 4.2.1
Closes #2694
2020-09-16 16:56:39 +01:00
Nick O'Leary
a48f0827ae Detect importing duplicate nodes and help user resolve 2020-09-16 11:59:13 +01:00
Nick O'Leary
5686158245 Allow toggleButton icons to be optional 2020-09-16 11:59:13 +01:00
Nick O'Leary
3824cdde68 Allow treeList to have a header component 2020-09-16 11:59:13 +01:00
Nick O'Leary
e619b9bf7b Merge pull request #2691 from node-red/recover-nodes
Recover nodes with invalid z property
2020-09-16 11:56:29 +01:00
Nick O'Leary
b7243c2226 Merge branch 'master' into dev 2020-09-16 11:54:34 +01:00
Nick O'Leary
70b6674f44 Replace Math.random with crypto.getBytes for session tokens 2020-09-11 14:09:54 +01:00
Nick O'Leary
ef67b8481e Check file exists before trying to take backup 2020-09-11 13:26:00 +01:00
Nick O'Leary
baffe4861c Handle undefined value in Debug view of Array and Object
Fixes #2696
2020-09-07 21:05:27 +01:00
Nick O'Leary
5cf489a270 Fixup node test 2020-09-04 09:52:30 +01:00
Nick O'Leary
44b1819926 Merge pull request #2693 from mgroenhoff/master
Fix `this` context when calling multiple event listeners (fixes #2692).
2020-09-04 09:33:23 +01:00
Melvin Groenhoff
d84c2b780b Fix this context when calling multiple event listeners (fixes #2692). 2020-09-04 10:20:30 +02:00
Nick O'Leary
dc8991a1da Ensure recoverd nodes tab is added to outliner properly 2020-09-03 20:24:50 +01:00
Nick O'Leary
7bd0ca2212 Handle nodes with invalid z property
Closes #2170
2020-09-03 18:31:33 +01:00
Nick O'Leary
4dd619b8c6 Merge branch 'master' into dev 2020-09-03 16:14:55 +01:00
Nick O'Leary
3a86ab186c Merge branch 'dev' of github.com:node-red/node-red into dev 2020-09-03 15:23:41 +01:00
Nick O'Leary
2f2a6367c2 Merge pull request #2684 from node-red/skip-html
Avoid loading node html if disableEditor set
2020-09-03 15:02:09 +01:00
Nick O'Leary
be880c25f9 Merge pull request #2682 from node-red/upload-npm
Add support for file upload on /nodes api
2020-09-03 15:01:31 +01:00
Nick O'Leary
17812f0d77 Merge pull request #2683 from node-red/write-to-temp
Update util.writeFile to write to tmp file before rename
2020-09-03 15:00:31 +01:00
Nick O'Leary
0c5eae2349 Merge pull request #2679 from rorysavage77/mutex-for-flow-modification
Updated flow modification methods to support mutex serialization
2020-09-03 14:17:56 +01:00
Nick O'Leary
3ad1803057 Merge pull request #2655 from node-red/reorder-sidebar
Reorderable sidebar tabs
2020-09-03 14:07:17 +01:00
Nick O'Leary
02c20e97b7 Only recalculate group label offsets when needed 2020-09-03 13:49:42 +01:00
Nick O'Leary
716dc781e4 Reuse first group name/style when merging elements
Fixes #2680
2020-09-03 13:28:35 +01:00
Nick O'Leary
d9900d8e4c Fix copy/paste of node into active group
Fixes #2686
2020-09-03 13:12:08 +01:00
Nick O'Leary
3b9065b057 Prevent Enter on search box from reloading page
Fixes #2678
2020-09-03 11:22:41 +01:00
Nick O'Leary
e73b748b95 Merge branch 'dev' of github.com:node-red/node-red into dev 2020-09-03 11:17:09 +01:00
Nick O'Leary
b309161f00 Merge pull request #2685 from node-red-hitachi/dev-tracemetric
Add 'done' metric log for message tracing
2020-09-03 11:16:26 +01:00
Nick O'Leary
b21667834e Tweak upload dialog margin 2020-08-26 10:37:21 +01:00
Nick O'Leary
183fa59c83 Add tgz upload button to palette manager 2020-08-26 00:15:36 +01:00
Kunihiko Toumura
0c5586ddfb Add 'done' metric log for message tracing 2020-08-18 09:28:50 +09:00
Nick O'Leary
33855bcb8b Skip loading node html if disableEditor set 2020-08-14 00:20:44 +01:00
Nick O'Leary
dc81b7a699 Add --userDir=/tmp/foo support to grunt dev 2020-08-14 00:08:09 +01:00
Nick O'Leary
b0b2c32654 Update util.writeFile to write to tmp file before rename 2020-08-13 17:17:40 +01:00
Nick O'Leary
6f1ed76b4c Add support for file upload in /nodes api 2020-08-13 15:54:54 +01:00
Rory A. Svage
f81cee0be2 Message 2020-08-07 16:44:52 -04:00
Nick O'Leary
bcd85b11a1 Merge branch 'master' into dev 2020-08-05 15:19:54 +01:00
Nick O'Leary
ec368ae3fd Bump for 1.1.3 2020-08-05 14:59:25 +01:00
Nick O'Leary
d28c264422 Fix jshint error on polyfill 2020-08-05 14:58:43 +01:00
Nick O'Leary
fba505bc90 Fix vertical align of fa node icons
Fixes #2670
2020-08-05 13:53:49 +01:00
Kazuhito Yokoi
c50ed2c328 Add Japanese translation for empty rules in switch node 2020-08-05 11:22:23 +01:00
Nick O'Leary
7e11ff2b20 Update packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html 2020-08-05 11:22:14 +01:00
Nathanaël Lécaudé
3fb83c46e2 Update 10-switch.html
Clarify switch help regarding booleans for the is empty / is not empty rules.
2020-08-05 11:22:04 +01:00
Nick O'Leary
763f2bd5c5 Merge pull request #2669 from kazuhitoyokoi/dev-jpn4switch
Update Japanese translation for switch node
2020-08-05 11:20:17 +01:00
Nick O'Leary
85edee288f Allow lasso selection to be restricted to active group 2020-08-05 11:16:53 +01:00
Nick O'Leary
1aa494a97a Make ctrl-click on nexted group more intuitive 2020-08-05 10:38:14 +01:00
Nick O'Leary
a8e7627184 Support select-all inside active group 2020-08-04 21:01:08 +01:00
Nick O'Leary
d590bbdd2c Fix copy/paste of nested groups 2020-08-04 20:59:51 +01:00
Nick O'Leary
80d65b5acb Add Set(iterable) polyfill for IE11 2020-08-04 20:59:32 +01:00
Kazuhito Yokoi
1d250f7491 Add Japanese translation for empty rules in switch node 2020-08-04 21:41:23 +09:00
Nick O'Leary
dd741ec6d8 Merge pull request #2649 from natcl/patch-5
Clarify empty rules in switch node documentation
2020-08-03 16:59:25 +01:00
Nick O'Leary
e741af6d55 Update packages/node_modules/@node-red/nodes/locales/en-US/function/10-switch.html 2020-08-03 16:58:09 +01:00
Nick O'Leary
e691b1b7c3 Add additional check for git auth failure response
Fixes #2656
2020-08-03 16:55:36 +01:00
Sebastian Raff
29142128f2 german translation, wording (#2660) (#2666)
to also close #2660
2020-08-02 18:34:10 +01:00
Nick O'Leary
758f44e25f Improve performance of moving groups 2020-07-31 23:22:33 +01:00
Nick O'Leary
16c26d8098 Move runtime settings back to adminApi from editorApi
Fixes #2662
2020-07-31 15:26:21 +01:00
Nick O'Leary
a004c61afc Merge pull request #2653 from node-red-hitachi/dev-messaging-api-cherrypick
Messaging API support of core nodes (cherrypicked)
2020-07-23 15:19:52 +01:00
Nick O'Leary
a9d1a64c32 Merge pull request #2661 from node-red-hitachi/master-zh
Update Chinese message for debug node
2020-07-23 15:09:36 +01:00
Dave Conway-Jones
889224715b Fix hhp-in to handle application/cbor as binary
as per discussion https://discourse.nodered.org/t/http-request-node-invalid-message-body-was-specified-to-be-cbor-but-could-not-decode-message-failed-to-parse/30503
2020-07-23 10:04:24 +01:00
Nick O'Leary
442b9d23f1 Remove filtering of duplicate fa icons 2020-07-22 14:51:07 +01:00
JIYE YU
e4dd895709 update Chinese message for debug node 2020-07-22 12:54:07 +09:00
Nick O'Leary
82677c304e Show node help when switching node edit dialogs
Fixes #2652
2020-07-16 16:12:16 +01:00
Nick O'Leary
73d8dfe381 Allow sidebar tabs to be reordered
The sidebar tab buttons can now be dragged to reorder them.

Changes to the order are stored in user preferences.
2020-07-15 11:26:08 +01:00
Kunihiko Toumura
1177aa8aca new-style callback function (yaml node) 2020-07-14 19:20:37 +09:00
Kunihiko Toumura
0eda0a4935 new-style callback function (xml node) 2020-07-14 19:18:05 +09:00
Kunihiko Toumura
a19dab0dc9 new-style callback function (json node) 2020-07-14 19:15:31 +09:00
Kunihiko Toumura
d8eb80b72e new-style callback function (html node) 2020-07-14 19:10:56 +09:00
Kunihiko Toumura
4f3a6821d1 new-style callback function (http response node) 2020-07-14 19:08:22 +09:00
Kunihiko Toumura
77bd7541ca new-style callback function (template node) 2020-07-14 19:05:41 +09:00
Kunihiko Toumura
ca46bc5366 new-style callback function (range node) 2020-07-14 19:02:41 +09:00
Kunihiko Toumura
2e19bc07df new-style callback function (link in/out node) 2020-07-14 18:59:09 +09:00
Kunihiko Toumura
3f4de43b67 new-style callback function (status node) 2020-07-14 18:56:16 +09:00
Kunihiko Toumura
0d0bf62fc4 new-style callback function (catch node) 2020-07-14 18:53:21 +09:00
Kunihiko Toumura
3c8654fa25 new-style callback function (complete node) 2020-07-14 18:50:03 +09:00
Kunihiko Toumura
756a6ec5aa new-style callback function (inject node) 2020-07-14 18:46:38 +09:00
Nick O'Leary
98c7364924 Ensure group theme picks up theme defaults properly
Fixes #2651
2020-07-13 20:44:53 +01:00
Nick O'Leary
62c01b59b2 Extend release action to update website 2020-07-10 21:46:00 +01:00
Nick O'Leary
43db1824be Bump for 1.1.2 2020-07-10 20:13:05 +01:00
Nick O'Leary
7f671c9f3f Ensure unknown nodes removed from outliner when node registers
Fixes #2646
2020-07-10 20:00:18 +01:00
Nathanaël Lécaudé
410009dd61 Update 10-switch.html
Clarify switch help regarding booleans for the is empty / is not empty rules.
2020-07-10 13:08:40 -04:00
Nick O'Leary
580cc00967 Fix all the touch screen issues 2020-07-10 16:00:38 +01:00
Nick O'Leary
612c565cfd Add RED.view.redrawStatus to avoid full redraw on update 2020-07-10 16:00:18 +01:00
Nick O'Leary
979c5351a8 Ensure node/group xrefs are consistent on import 2020-07-10 15:59:28 +01:00
Nick O'Leary
97b7479081 Disable keyboard handler when dialogs are open 2020-07-09 20:41:55 +01:00
Nick O'Leary
1df2f5e96a Allow Comms websocket auth to be done via token header
Fixes #2642
2020-07-09 19:07:51 +01:00
cinhcet
0601833387 msgid added in vm to avoid unneccesary if statement 2020-07-08 19:09:07 +02:00
Nick O'Leary
8b36279e52 Bump version 1.2.0-alpha.1 2020-07-08 15:57:32 +01:00
Nick O'Leary
32163d5f21 Update changelog 2020-07-08 13:26:48 +01:00
Nick O'Leary
1c337f6817 Fix connecting wires to subflow status or io ports on touchscreen 2020-07-08 13:22:26 +01:00
Nick O'Leary
6df26f2400 Bump for 1.1.1 2020-07-08 10:30:20 +01:00
Nick O'Leary
a9431a5aee Merge pull request #2645 from kazuhitoyokoi/master-addjpn
Update Japanese message for debug node
2020-07-08 10:08:49 +01:00
Kazuhito Yokoi
5a3c832a98 Update Japanese message for debug node 2020-07-08 16:20:02 +09:00
Nick O'Leary
c4b5bb22db Tidy up commit msg on docker update action 2020-07-08 01:13:14 +01:00
Nick O'Leary
2b5a976f35 Add github action to auto-update docker repo version on release publish 2020-07-08 01:02:18 +01:00
cinhcet
5d7a625883 expose send, status, log, warn, error, debug, trace, name, id in setup code of function node 2020-07-08 01:30:29 +02:00
Nick O'Leary
ae1ca85924 Update Debug status label 2020-07-07 22:10:47 +01:00
Nick O'Leary
c9acfdb1d7 Set apiRootUrl for debug pop-out to load locales properly
Fixes #2629, #2630
2020-07-07 21:14:44 +01:00
Nick O'Leary
11ac8fbf13 Ensure groups are removed when deleting subflows 2020-07-07 18:23:42 +01:00
Nick O'Leary
dc541444ba Merge pull request #2638 from node-red/Clean-up-debug-status-/-remove-loops
fix debug status to not loop, make migration more seamless, detect status type objects
2020-07-07 13:37:05 +01:00
Nick O'Leary
8ea25bcd1c Update build-custom-theme to handle keyframes properly
Fixes #2636

Also adds a header to the generated CSS identifing the version
of NR and date/time it was generated.
2020-07-07 11:38:07 +01:00
Nick O'Leary
f5e46a663a Remove hardcoded css and allow group to default from theme
Fixes #2633
2020-07-07 11:01:05 +01:00
Nick O'Leary
64ec415a54 Add RED.view.DEBUG_SYNC_REDRAW to disable requestAnimationFrame
References #2631
2020-07-06 21:01:14 +01:00
Nick O'Leary
57154b2853 Authenticate websocket comms using user-provided token if present
Fixes #2642
2020-07-06 20:45:07 +01:00
Nick O'Leary
0243a902b2 Fix up subflow port wiring 2020-07-06 18:11:47 +01:00
Nick O'Leary
6c04402a98 Prevent wiring to node with no corresponding port
Fixes #2641
2020-07-06 17:32:44 +01:00
Nick O'Leary
ef7c9b5c2a Get group order right in history events to ensure proper handling 2020-07-06 16:00:15 +01:00
Nick O'Leary
73448a6039 Avoid copying duplicate nodes to internal clipboard 2020-07-06 16:00:15 +01:00
Dave Conway-Jones
176a0ff99b add words avout indepedence of messages being delayed. 2020-07-06 10:33:26 +01:00
Dave Conway-Jones
b96d562700 fix debug status to not loop, make migration more seamless, detect status type objects 2020-07-04 15:26:02 +01:00
255 changed files with 11225 additions and 2088 deletions

View File

@@ -0,0 +1,29 @@
const fs = require("fs");
const newVersion = require("../../package.json").version;
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
process.exit(0);
}
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
console.log(`Not updating for a non-stable release - ${newVersion}`);
process.exit(0);
}
const currentVersion = require("../../../node-red-docker/package.json").version;
console.log(`Update from ${currentVersion} to ${newVersion}`)
updateFile(__dirname+"/../../../node-red-docker/package.json", currentVersion, newVersion);
updateFile(__dirname+"/../../../node-red-docker/docker-custom/package.json", currentVersion, newVersion);
updateFile(__dirname+"/../../../node-red-docker/README.md", currentVersion, newVersion);
console.log(`::set-env name=newVersion::${newVersion}`);
function updateFile(path,from,to) {
let contents = fs.readFileSync(path,"utf8");
contents = contents.replace(new RegExp(from.replace(/\./g,"\\."),"g"), to);
fs.writeFileSync(path, contents);
}

View File

@@ -0,0 +1,18 @@
const fs = require("fs");
const newVersion = require("../../package.json").version;
if (process.env.GITHUB_REF !== "refs/tags/"+newVersion) {
console.log(`GITHUB_REF doesn't match the package.json version: ${process.env.GITHUB_REF} !== ${newVersion}`);
process.exit(0);
}
if (!/^\d+\.\d+\.\d+$/.test(newVersion)) {
console.log(`Not updating for a non-stable release - ${newVersion}`);
process.exit(0);
}
const path = __dirname+"/../../../node-red.github.io/index.html";
let contents = fs.readFileSync(path, "utf8");
contents = contents.replace(/<span class="node-red-latest-version">v\d+\.\d+\.\d+<\/span>/, `<span class="node-red-latest-version">v${newVersion}<\/span>` );
fs.writeFileSync(path, contents);

59
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: PublishDockerImage
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
on:
release:
types: [published]
jobs:
generate:
name: 'Update node-red-docker image'
runs-on: ubuntu-latest
steps:
- name: Check out node-red repository
uses: actions/checkout@v2
with:
path: 'node-red'
- name: Check out node-red-docker repository
uses: actions/checkout@v2
with:
repository: 'node-red/node-red-docker'
path: 'node-red-docker'
- name: Check out node-red.github.io repository
uses: actions/checkout@v2
with:
repository: 'node-red/node-red.github.io'
path: 'node-red.github.io'
- uses: actions/setup-node@v1
with:
node-version: '12'
- run: node ./node-red/.github/scripts/update-node-red-docker.js
- name: Create Docker Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
path: 'node-red-docker'
commit-message: 'Bump to ${{ env.newVersion }}'
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
body: |
Updates the Node-RED Docker repo for the ${{ env.newVersion }} release.
Once this is merged, you will need to create a new release with the tag `v${{ env.newVersion }}`.
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
- run: node ./node-red/.github/scripts/update-node-red-website.js
- name: Create Website Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
path: 'node-red.github.io'
commit-message: 'Bump to ${{ env.newVersion }}'
title: '🚀 Update to Node-RED ${{ env.newVersion }} release'
body: |
Updates the Node-RED Website repo for the ${{ env.newVersion }} release.
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary

3
.gitignore vendored
View File

@@ -22,4 +22,5 @@ packages/node_modules/@node-red/editor-client/public
!test/**/node_modules
docs
!packages/node_modules/**/docs
.vscode
.vscode
.nyc_output

View File

@@ -5,11 +5,17 @@ language: node_js
matrix:
include:
- node_js: "14"
script:
- ./node_modules/.bin/grunt && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
# - scripts/install-ui-test-dependencies.sh && grunt test-ui
before_script:
- npm install -g coveralls
- node_js: "12"
script:
- ./node_modules/.bin/grunt no-coverage
- node_js: "10"
script:
- ./node_modules/.bin/grunt && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
- scripts/install-ui-test-dependencies.sh && grunt test-ui
before_script:
- npm install -g istanbul coveralls
- ./node_modules/.bin/grunt no-coverage
- node_js: "8"
script:
- ./node_modules/.bin/grunt no-coverage

View File

@@ -1,3 +1,263 @@
### 1.2.8: Maintenance Release
Editor
- Ensure subflow help is picked up for palette tooltip Fixes #2834
- Improve Ru locale (#2826) @alexk111
- Fix scrollbars (#2825) @alexk111
Runtime
- Restrict project file access to inside the project directory
- Validate user-provided language parameter before passing to i18n
- Fix grunt release mkdir issue on Node.js 14 (#2827) @alexk111
- Prevent crash when coreNodesDir is empty (#2831) @hardillb
Nodes
- Batch node: Fixing minor typo in node's documentation (#2848) @matthiasradde
- Split node: Handle out of order messages as long as one of the messages has msg.parts.count set to the proper value (#2748) @s4ke
### 1.2.7: Maintenance Release
Editor
- Ensure subflow-scoped config nodes do not get moved on import Fixes #2789
- Allow TypedInput to be disabled (#2752) @bartbutenaers
- Allow userMenu to be explicitly enabled (#2805) @tfmf
- Improvements to DE translation (#2192) @ketzu
Runtime
- Handle `undefined` error passed to node.error (#2781) @johnwang71
- Disable nyc coverage reporting on older node versions
- Improve Editor API unit test coverage (#2777) @aaronmyatt
Nodes
- Trigger: ensure timestamp option sends .now() at point of sending
### 1.2.6: Maintenance Release
Editor
- Update Japanese translations for 1.2.5 (#2764) @kazuhitoyokoi
- Library: properly handle symlinked folders (#2768) @natcl
Runtime
- Support Windows paths when installing tarball by path name Fixes #2769
- Fix unsecure command usage in GH Action
Nodes
- Update MQTT to latest to fix Node 8 URL breakage
### 1.2.5: Maintenance Release
Editor
- Fix import of config nodes with unknown z property
Runtime
- Set ACTIONS_ALLOW_UNSECURE_COMMANDS in GH Action
### 1.2.4: Maintenance Release
Editor
- Support bigint types in Debug sidebar
- Clear retained status of deleted nodes
- Prevent needless retention of node status messages
- Update projects dialogs to use TypedInput-cred input
- Restore cursor position in TypedInput cred-mode
- Ensure config nodes with invalid z are imported somewhere
- Ensure user keyboard shortcuts override defaults Fixes #2753
Runtime
- Disable projects when flowFile passed into grunt dev
- Add Russian Locale (#2761) (#2531) (@alexk111)
- Add Japanese translation for http-in node (#2758) (@kazuhitoyokoi)
Nodes
- CSV: Fix CSV node repeating array output
### 1.2.3: Maintenance Release
Editor
- Disable 'use strict' checking in Function node Fixes #2743
- Add gray/grey alternate options for status
- Handle import errors on initial load and report to user
- Only apply recovery tab on initial load Fixes #2731
- Reinstate coveralls reporting to travis build
- Update Japanese message catalogue for 1.2.3 release #2747 (@HiroyasuNishiyama)
Runtime
- Modify default settings comment (#2739)
- Add mutex lock to saveSettings storage call Fixes #2736 (#2737)
- Migrate to nyc instead of istanbul for code coverage
- Move mosca to ui-test-dependencies
- Remove " from npm install prefix option
### 1.2.2: Maintenance Release
Editor
- Prevent node z property getting set to 0 or ""
- Only apply z-recovery logic to flow nodes
- Fix api call to reload flows Fixes #2726
- Remove bad z property from import config nodes
### 1.2.1: Maintenance Release
Runtime
- Fix race condition in .config file migration Fixes #2724
### 1.2.0: Milestone Release
Editor
- Fix selection of link node not existing within active workspace #2722 (@HiroyasuNishiyama)
- Fix import of merged flow
- Fix width of upload button in Safari #2718 (@HiroyasuNishiyama)
- Update Chinese translations #2719 (@JiyeYu)
- Update Japanese translations needed for 1.2 #2710 (@kazuhitoyokoi)
- Fix unexpected line break of sidebar tab name popover #2716 (@HiroyasuNishiyama)
- i18n module refresh tooltip #2717 (@HiroyasuNishiyama)
- Add better error message if context file gets corrupted
- Update info text of function node #2714 (@HiroyasuNishiyama)
- Use markdown editor if editText called with md mode
- Prevent group actions when in non-default mouse mode
### 1.2.0-beta.1: Beta Release
Editor
- Detect importing duplicate nodes and help user resolve #2698
- Allow sidebar tabs to be reordered #2655
- Add tgz upload button to palette manager #2682
- Add 'automatic' git workflow for projects #2035
- Allow project version string to be edited
- Sanitize unknown node type when displaying
- Handle nodes with invalid z property Closes #2170
- Outline: Ensure sf instance nodes update in outliner when import-replace sf
- Outline: Ensure recovered nodes tab is added to outliner properly
- Groups: Only recalculate group label offsets when needed
- Groups: Reuse first group name/style when merging elements Fixes #2680
- Groups: Fix copy/paste of node into active group Fixes #2686
- ACE: Update ACE to 1.4.12-src-min-noconflict Fixes #1988
- ACE: Add comment highlighting to JSONata and fix regex handling Closes #2701
- ACE: Ensure errors in ACE NRJavaScript mode are on valid lines
- Prevent Enter on search box from reloading page Fixes #2678
- Allow toggleButton icons to be optional
- Allow treeList to have a header component
- Disable selection of FA icons when dbl clicking node
Runtime
- Add RED.hooks API for pluggable routing #2665
- Add flows:* events and deprecate nodes-* events
- Split .config.json into separate files #2794
- Add support for file upload in /nodes api #2682
- Add 'done' metric log for message tracing #2685 (@k-toumura)
- Add mutex locking around /flow apis #2679
- Default flowFilePretty to true if projects enabled
- Replace Math.random with crypto.getBytes for session tokens
- Fix `this` context when calling multiple event listeners Fixes #2692. #2693 (@mgroenhoff)
- Add --userDir=/tmp/foo support to grunt dev
- Skip loading node html if disableEditor set #2684
- Update util.writeFile to write to tmp file before rename #2683
- Fix getModuleFiles function to include path property #2705 (@t-kawamorita)
- Update nodemon to latest so grunt dev task behaves
- Improve jsdoc of util.getObjectProperty to clarify thrown error See #2703
Nodes
- Trigger: allow msg.delay to be used to set delay/loop interval #2707
- Function: allow to send & log in its initialize code #2644 (@cinhcet)
- MQTT: Update to MQTT 4.2.1 Closes #2694
- Debug: Handle undefined value in Debug view of Array and Object Fixes #2696
- Switch: Clarify empty rules in switch node documentation #2649 (@natcl) #2669 (@kazuhitoyokoi)
- Updated core nodes to use Done callback #2653 (@k-toumura)
- yaml, xml, json, html, http, template, range, link, status, catch, complete, inject
### 1.1.3: Maintenance Release
Editor
- Fix vertical align of fa node icons Fixes #2670
- Allow lasso selection to be restricted to active group
- Make ctrl-click on nested group more intuitive
- Fix copy/paste of nested groups
- Add Set(iterable) polyfill for IE11
- Support select-all inside active group
- Improve performance of moving groups
- Add additional check for git auth failure response Fixes #2656
- german translation, wording (#2660) (#2666)
- Remove filtering of duplicate fa icons
- Show node help when switching node edit dialogs Fixes #2652
- Ensure group theme picks up theme defaults properly Fixes #2651
Nodes
- Clarify Switch node isEmpty help
- HTTP In: handle application/cbor as binary
Runtime
- Move runtime settings back to adminApi from editorApi Fixes #2662
- Update Chinese message for debug node
### 1.1.2: Maintenance Release
Editor
- Fix all the touch screen issues Fixes #2647
- Add RED.view.redrawStatus to avoid full redraw on update
- Ensure node/group xrefs are consistent on import
- Disable keyboard handler when dialogs are open
- Ensure unknown nodes removed from outliner when node registers Fixes #2646
Runtime
- Allow Comms websocket auth to be done via token header Fixes #2642
### 1.1.1: Maintenance Release
Editor
- Set apiRootUrl for debug pop-out to load locales properly Fixes #2629, #2630
- Update build-custom-theme to handle keyframes properly Fixes #2636
- Remove hardcoded css and allow group to default from theme Fixes #2633
- Add RED.view.DEBUG_SYNC_REDRAW to disable requestAnimationFrame References #2631
- Fix up subflow port wiring
- Ensure groups are removed when deleting subflows
- Get group order right in history events to ensure proper handling
- Prevent wiring to node with no corresponding port Fixes #2641
- Avoid copying duplicate nodes to internal clipboard
- Fix connecting wires to subflow status or io ports on touchscreen Fixes #2637
Runtime
- Authenticate websocket comms using user-provided token if present Fixes #2642
Nodes
- Delay: add words about independence of messages being delayed.
- Debug: fix debug status to not loop, make migration more seamless, detect status type objects #2638
- Debug: Update Japanese message for debug node #2645 (@kazuhitoyokoi)
### 1.1.0: Milestone Release
Editor

View File

@@ -20,10 +20,16 @@ var sass = require("node-sass");
module.exports = function(grunt) {
var nodemonArgs = ["-v"];
var nodemonArgs = ["-V"];
var flowFile = grunt.option('flowFile');
if (flowFile) {
nodemonArgs.push(flowFile);
process.env.NODE_RED_ENABLE_PROJECTS=false;
}
var userDir = grunt.option('userDir');
if (userDir) {
nodemonArgs.push("-u");
nodemonArgs.push(userDir);
}
var browserstack = grunt.option('browserstack');
@@ -47,8 +53,8 @@ module.exports = function(grunt) {
ui: 'bdd',
reporter: 'spec'
},
all: { src: ['test/**/*_spec.js'] },
core: { src: ["test/_spec.js","test/unit/**/*_spec.js"]},
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"]}
},
webdriver: {
@@ -56,19 +62,19 @@ module.exports = function(grunt) {
configFile: 'test/editor/wdio.conf.js'
}
},
mocha_istanbul: {
nyc: {
options: {
globals: ['expect'],
timeout: 3000,
ignoreLeaks: false,
ui: 'bdd',
reportFormats: ['lcov','html'],
print: 'both',
istanbulOptions: ['--no-default-excludes', '-i','**/packages/node_modules/**']
cwd: '.',
include: ['packages/node_modules/**'],
excludeNodeModules: false,
exclude: ['packages/node_modules/@node-red/editor-client/**'],
reporter: ['lcov', 'html','text-summary'],
reportDir: 'coverage',
all: true
},
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"]}
all: { cmd: false, args: ['grunt', 'simplemocha:all'] },
core: { options: { exclude:['packages/node_modules/@node-red/editor-client/**', 'packages/node_modules/@node-red/nodes/**']},cmd: false, args: ['grunt', 'simplemocha:core'] },
nodes: { cmd: false, args: ['grunt', 'simplemocha:nodes'] }
},
jshint: {
options: {
@@ -452,6 +458,7 @@ module.exports = function(grunt) {
'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/runtime/lib/hooks.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'
@@ -502,12 +509,10 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-chmod');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-mocha-istanbul');
if (fs.existsSync(path.join("node_modules", "grunt-webdriver"))) {
grunt.loadNpmTasks('grunt-webdriver');
}
@@ -515,6 +520,26 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-jsdoc-to-markdown');
grunt.loadNpmTasks('grunt-npm-command');
grunt.loadNpmTasks('grunt-mkdir');
grunt.loadNpmTasks('grunt-simple-nyc');
grunt.registerMultiTask('nodemon', 'Runs a nodemon monitor of your node.js server.', function () {
const nodemon = require('nodemon');
this.async();
const options = this.options();
options.script = this.data.script;
let callback;
if (options.callback) {
callback = options.callback;
delete options.callback;
} else {
callback = function(nodemonApp) {
nodemonApp.on('log', function (event) {
console.log(event.colour);
});
};
}
callback(nodemon(options));
});
grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src;
@@ -596,11 +621,16 @@ 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','verifyPackageDependencies','jshint:editor','nyc:all']);
grunt.registerTask('no-coverage',
'Builds editor content then runs code style checks and unit tests on all components without code coverage',
['build','verifyPackageDependencies','jshint:editor','simplemocha:all']);
grunt.registerTask('test-core',
'Runs code style check and unit tests on core runtime code',
['build','mocha_istanbul:core']);
['build','nyc:core']);
grunt.registerTask('test-editor',
'Runs code style check on editor code',
@@ -618,12 +648,12 @@ module.exports = function(grunt) {
grunt.registerTask('test-nodes',
'Runs unit tests on core nodes',
['build','mocha_istanbul:nodes']);
['build','nyc:nodes']);
grunt.registerTask('build',
'Builds editor content',
['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']);
grunt.registerTask('build-dev',
'Developer mode: build dev version',
['clean:build','concat:build','concat:vendor','copy:build','sass:build','setDevEnv']);
@@ -643,7 +673,7 @@ module.exports = function(grunt) {
grunt.registerTask('coverage',
'Run Istanbul code test coverage task',
['build','mocha_istanbul:all']);
['build','nyc:all']);
grunt.registerTask('docs',
'Generates API documentation',

View File

@@ -1,4 +1,4 @@
Copyright JS Foundation and other contributors, http://js.foundation
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
Apache License
Version 2.0, January 2004

View File

@@ -67,4 +67,4 @@ It was created by [IBM Emerging Technology](https://www.ibm.com/blogs/emerging-t
## Copyright and license
Copyright JS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
Copyright OpenJS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).

View File

@@ -1,13 +1,5 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.0.0 | :white_check_mark: |
| 0.20.x | :white_check_mark: |
## Reporting a Vulnerability
Please report any potential security issues to `team@nodered.org`. This will notify the core project team who will respond accordingly.

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "1.1.0",
"version": "1.2.8",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -26,7 +26,8 @@
}
],
"dependencies": {
"ajv": "6.12.2",
"ajv": "6.12.6",
"async-mutex": "0.2.6",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
@@ -37,7 +38,7 @@
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"cron": "1.7.2",
"denque": "1.4.1",
"denque": "1.5.0",
"express": "4.17.1",
"express-session": "1.17.1",
"fs-extra": "8.1.0",
@@ -45,24 +46,24 @@
"hash-sum": "2.0.0",
"https-proxy-agent": "5.0.0",
"i18next": "15.1.2",
"iconv-lite": "0.5.1",
"iconv-lite": "0.6.2",
"is-utf8": "0.2.1",
"js-yaml": "3.14.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.3",
"jsonata": "1.8.4",
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.2",
"mime": "2.4.6",
"moment-timezone": "^0.5.31",
"mqtt": "2.18.8",
"memorystore": "1.6.4",
"mime": "2.4.7",
"moment-timezone": "0.5.32",
"mqtt": "4.2.6",
"multer": "1.4.2",
"mustache": "4.0.1",
"mustache": "4.1.0",
"node-red-admin": "^0.2.6",
"node-red-node-rbe": "^0.2.9",
"node-red-node-sentiment": "^0.1.6",
"node-red-node-tail": "^0.1.0",
"nopt": "4.0.3",
"nopt": "5.0.0",
"oauth2orize": "1.11.0",
"on-headers": "1.0.2",
"passport": "0.4.1",
@@ -71,7 +72,8 @@
"raw-body": "2.4.1",
"request": "2.88.0",
"semver": "6.3.0",
"uglify-js": "3.9.4",
"tar": "6.0.5",
"uglify-js": "3.12.4",
"when": "3.7.8",
"ws": "6.2.1",
"xml2js": "0.4.23"
@@ -80,40 +82,38 @@
"bcrypt": "3.0.8"
},
"devDependencies": {
"marked": "0.8.2",
"dompurify": "2.0.11",
"grunt": "~1.0.4",
"dompurify": "2.2.6",
"grunt": "1.3.0",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.2",
"grunt-concurrent": "~2.3.1",
"grunt-concurrent": "3.0.0",
"grunt-contrib-clean": "~2.0.0",
"grunt-contrib-compress": "~1.5.0",
"grunt-contrib-compress": "1.6.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~2.1.0",
"grunt-contrib-uglify": "~4.0.1",
"grunt-contrib-watch": "~1.1.0",
"grunt-jsdoc": "^2.2.1",
"grunt-jsdoc-to-markdown": "^4.0.0",
"grunt-jsonlint": "~2.0.0",
"grunt-mkdir": "~1.0.0",
"grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon": "~0.4.2",
"grunt-jsdoc": "2.4.1",
"grunt-jsdoc-to-markdown": "5.0.0",
"grunt-jsonlint": "2.1.3",
"grunt-mkdir": "~1.1.0",
"grunt-npm-command": "~0.1.2",
"grunt-sass": "~3.1.0",
"grunt-simple-mocha": "~0.4.1",
"grunt-simple-nyc": "^3.0.1",
"http-proxy": "1.18.1",
"istanbul": "0.4.5",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "1.2.7",
"minami": "1.2.3",
"mocha": "^5.2.0",
"mosca": "^2.8.3",
"node-red-node-test-helper": "^0.2.5",
"node-red-node-test-helper": "^0.2.6",
"node-sass": "^4.14.1",
"should": "^8.4.0",
"nodemon": "2.0.6",
"should": "13.2.3",
"sinon": "1.17.7",
"stoppable": "^1.1.0",
"supertest": "3.4.2"
"supertest": "5.0.0"
},
"engines": {
"node": ">=8"

View File

@@ -21,15 +21,17 @@ var flows = require("./flows");
var flow = require("./flow");
var context = require("./context");
var auth = require("../auth");
var info = require("./settings");
var apiUtil = require("../util");
module.exports = {
init: function(runtimeAPI) {
init: function(settings,runtimeAPI) {
flows.init(runtimeAPI);
flow.init(runtimeAPI);
nodes.init(runtimeAPI);
context.init(runtimeAPI);
info.init(settings,runtimeAPI);
var needsPermission = auth.needsPermission;
@@ -47,7 +49,14 @@ module.exports = {
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) {
const multer = require('multer');
const upload = multer({ storage: multer.memoryStorage() });
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
} else {
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
}
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
@@ -67,6 +76,8 @@ module.exports = {
// adminApp.delete("/context/:scope(node|flow)/:id",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
adminApp.delete("/context/:scope(node|flow)/:id/*",needsPermission("context.write"),context.delete,apiUtil.errorHandler);
adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
return adminApp;
}
}

View File

@@ -45,8 +45,18 @@ module.exports = {
module: req.body.module,
version: req.body.version,
url: req.body.url,
tarball: undefined,
req: apiUtils.getRequestLogObject(req)
}
if (!runtimeAPI.settings.editorTheme || !runtimeAPI.settings.editorTheme.palette || runtimeAPI.settings.editorTheme.palette.upload !== false) {
if (req.file) {
opts.tarball = {
name: req.file.originalname,
size: req.file.size,
buffer: req.file.buffer
}
}
}
runtimeAPI.nodes.addModule(opts).then(function(info) {
res.json(info);
}).catch(function(err) {

View File

@@ -0,0 +1,72 @@
/**
* 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.
**/
var apiUtils = require("../util");
var runtimeAPI;
var settings;
var theme = require("../editor/theme");
var clone = require("clone");
var i18n = require("@node-red/util").i18n
function extend(target, source) {
var keys = Object.keys(source);
var i = keys.length;
while(i--) {
var value = source[keys[i]]
var type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
target[keys[i]] = value;
} else if (value === null) {
if (target.hasOwnProperty(keys[i])) {
delete target[keys[i]];
}
} else {
// Object
if (target.hasOwnProperty(keys[i])) {
target[keys[i]] = extend(target[keys[i]],value);
} else {
target[keys[i]] = value;
}
}
}
return target;
}
module.exports = {
init: function(_settings,_runtimeAPI) {
runtimeAPI = _runtimeAPI;
settings = _settings;
},
runtimeSettings: function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
if (!settings.disableEditor) {
result.editorTheme = result.editorTheme||{};
var themeSettings = theme.settings();
if (themeSettings) {
// result.editorTheme may already exist with the palette
// disabled. Need to merge that into the receive settings
result.editorTheme = extend(clone(themeSettings),result.editorTheme);
}
result.editorTheme.languages = i18n.availableLanguages("editor");
}
res.json(result);
});
},
}

View File

@@ -123,38 +123,57 @@ AnonymousStrategy.prototype.authenticate = function(req) {
});
}
function authenticateUserToken(req) {
return new Promise( (resolve,reject) => {
var token = null;
var tokenHeader = Users.tokenHeader();
if (Users.tokenHeader() === null) {
// No custom user token provided. Fail the request
reject();
return;
} else if (Users.tokenHeader() === 'authorization') {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
}
} else {
token = req.headers[Users.tokenHeader()];
}
if (token) {
Users.tokens(token).then(function(user) {
if (user) {
resolve(user);
} else {
reject();
}
});
} else {
reject();
}
});
}
function TokensStrategy() {
passport.Strategy.call(this);
this.name = 'tokens';
}
util.inherits(TokensStrategy, passport.Strategy);
TokensStrategy.prototype.authenticate = function(req) {
var self = this;
var token = null;
if (Users.tokenHeader() === 'authorization') {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
}
} else {
token = req.headers[Users.tokenHeader()];
}
if (token) {
Users.tokens(token).then(function(admin) {
if (admin) {
self.success(admin,{scope:admin.permissions});
} else {
self.fail(401);
}
});
} else {
self.fail(401);
}
authenticateUserToken(req).then(user => {
this.success(user,{scope:user.permissions});
}).catch(err => {
this.fail(401);
});
}
module.exports = {
bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange,
anonymousStrategy: new AnonymousStrategy(),
tokensStrategy: new TokensStrategy()
tokensStrategy: new TokensStrategy(),
authenticateUserToken: authenticateUserToken
}

View File

@@ -14,15 +14,7 @@
* limitations under the License.
**/
function generateToken(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = [];
for (var i=0;i<length;i++) {
token.push(c[Math.floor(Math.random()*c.length)]);
}
return token.join("");
}
const crypto = require("crypto");
var storage;
var sessionExpiryTime
@@ -115,7 +107,7 @@ module.exports = {
},
create: function(user,client,scope) {
return loadSessions().then(function() {
var accessToken = generateToken(128);
var accessToken = crypto.randomBytes(128).toString('base64');
var accessTokenExpiresAt = Date.now() + (sessionExpiryTime*1000);

View File

@@ -61,7 +61,7 @@ var api = {
authenticate: authenticate,
default: getDefaultUser,
tokens: getDefaultUser,
tokenHeader: "authorization"
tokenHeader: null
}
function init(config) {
@@ -111,6 +111,8 @@ function init(config) {
api.tokens = config.tokens;
if (config.tokenHeader && typeof config.tokenHeader === "string") {
api.tokenHeader = config.tokenHeader.toLowerCase();
} else {
api.tokenHeader = "authorization";
}
}
}

View File

@@ -16,11 +16,13 @@
var ws = require("ws");
var url = require("url");
const crypto = require("crypto");
var log = require("@node-red/util").log; // TODO: separate module
var Tokens;
var Users;
var Permissions;
var Strategies;
var server;
var settings;
@@ -31,8 +33,6 @@ var activeConnections = [];
var anonymousUser;
var retained = {};
var heartbeatTimer;
var lastSentTime;
@@ -44,6 +44,7 @@ function init(_server,_settings,_runtimeAPI) {
Tokens.onSessionExpiry(handleSessionExpiry);
Users = require("../auth/users");
Permissions = require("../auth/permissions");
Strategies = require("../auth/strategies");
}
function handleSessionExpiry(session) {
@@ -54,26 +55,19 @@ function handleSessionExpiry(session) {
}
})
}
function generateSession(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = [];
for (var i=0;i<length;i++) {
token.push(c[Math.floor(Math.random()*c.length)]);
}
return token.join("");
}
function CommsConnection(ws) {
this.session = generateSession(32);
function CommsConnection(ws, user) {
this.session = crypto.randomBytes(32).toString('base64');
this.ws = ws;
this.stack = [];
this.user = null;
this.user = user;
this.lastSentTime = 0;
var self = this;
log.audit({event: "comms.open"});
log.trace("comms.open "+self.session);
var pendingAuth = (settings.adminAuth != null);
var preAuthed = !!user;
var pendingAuth = !this.user && (settings.adminAuth != null);
if (!pendingAuth) {
addActiveConnection(self);
@@ -130,8 +124,16 @@ function CommsConnection(ws) {
}
});
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,null,false);
Users.tokens(msg.auth).then(function(user) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(user.permissions,msg.auth,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,null,false);
}
});
}
});
} else {
@@ -191,8 +193,8 @@ function start() {
var commsPath = settings.httpAdminRoot || "/";
commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({ noServer: true });
wsServer.on('connection',function(ws) {
var commsConnection = new CommsConnection(ws);
wsServer.on('connection',function(ws, request, user) {
var commsConnection = new CommsConnection(ws, user);
});
wsServer.on('error', function(err) {
log.warn(log._("comms.error-server",{message:err.toString()}));
@@ -201,8 +203,26 @@ function start() {
server.on('upgrade', function upgrade(request, socket, head) {
const pathname = url.parse(request.url).pathname;
if (pathname === commsPath) {
if (Users.tokenHeader() !== null && request.headers[Users.tokenHeader()]) {
// The user has provided custom token handling. For the websocket,
// the token could be provided in two ways:
// - as an http header (only possible with a reverse proxy setup)
// - passed over the connected websock in an auth packet
// If the header is present, verify the token. If not, use the auth
// packet over the connected socket
//
Strategies.authenticateUserToken(request).then(user => {
wsServer.handleUpgrade(request, socket, head, function done(ws) {
wsServer.emit('connection', ws, request, user);
});
}).catch(err => {
log.audit({event: "comms.auth.fail"});
socket.destroy();
})
return
}
wsServer.handleUpgrade(request, socket, head, function done(ws) {
wsServer.emit('connection', ws, request);
wsServer.emit('connection', ws, request, null);
});
}
// Don't destroy the socket as other listeners may want to handle the

View File

@@ -103,7 +103,7 @@ module.exports = {
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
// Settings
editorApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
// Main /settings route is an admin route - see lib/admin/settings.js
// User Settings
editorApp.get("/settings/user",needsPermission("settings.read"),info.userSettings,apiUtil.errorHandler);
// User Settings

View File

@@ -39,9 +39,12 @@ module.exports = {
},
get: function(req,res) {
var namespace = req.params[0];
var lngs = req.query.lng;
namespace = namespace.replace(/\.json$/,"");
var lang = req.query.lng || i18n.defaultLang; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
if (/[^a-z\-\*]/i.test(lang)) {
res.json({});
return;
}
var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){

View File

@@ -137,6 +137,7 @@ module.exports = {
req.body.hasOwnProperty('description') ||
req.body.hasOwnProperty('dependencies')||
req.body.hasOwnProperty('summary') ||
req.body.hasOwnProperty('version') ||
req.body.hasOwnProperty('files') ||
req.body.hasOwnProperty('git')) {
runtimeAPI.projects.updateProject(opts).then(function() {

View File

@@ -16,56 +16,12 @@
var apiUtils = require("../util");
var runtimeAPI;
var sshkeys = require("./sshkeys");
var theme = require("./theme");
var clone = require("clone");
var i18n = require("@node-red/util").i18n
function extend(target, source) {
var keys = Object.keys(source);
var i = keys.length;
while(i--) {
var value = source[keys[i]]
var type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
target[keys[i]] = value;
} else if (value === null) {
if (target.hasOwnProperty(keys[i])) {
delete target[keys[i]];
}
} else {
// Object
if (target.hasOwnProperty(keys[i])) {
target[keys[i]] = extend(target[keys[i]],value);
} else {
target[keys[i]] = value;
}
}
}
return target;
}
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
sshkeys.init(runtimeAPI);
},
runtimeSettings: function(req,res) {
var opts = {
user: req.user
}
runtimeAPI.settings.getRuntimeSettings(opts).then(function(result) {
result.editorTheme = result.editorTheme||{};
var themeSettings = theme.settings();
if (themeSettings) {
// result.editorTheme may already exist with the palette
// disabled. Need to merge that into the receive settings
result.editorTheme = extend(clone(themeSettings),result.editorTheme);
}
result.editorTheme.languages = i18n.availableLanguages("editor");
res.json(result);
});
},
userSettings: function(req, res) {
var opts = {
user: req.user

View File

@@ -99,7 +99,7 @@ function init(settings,_server,storage,runtimeAPI) {
adminApp.use(corsHandler);
}
var adminApiApp = require("./admin").init(runtimeAPI);
var adminApiApp = require("./admin").init(settings, runtimeAPI);
adminApp.use(adminApiApp);
} else {
adminApp = null;

View File

@@ -43,6 +43,10 @@ module.exports = {
rejectHandler: function(req,res,err) {
//TODO: why this when errorHandler also?!
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.message||err.toString()},req);
if (!err.code) {
// by definition, an unexpected_error to log
log.error(err);
}
var response = {
code: err.code||"unexpected_error",
message: err.message||err.toString()

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "1.1.0",
"version": "1.2.8",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,17 +16,18 @@
}
],
"dependencies": {
"@node-red/util": "1.1.0",
"@node-red/editor-client": "1.1.0",
"@node-red/util": "1.2.8",
"@node-red/editor-client": "1.2.8",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"clone": "2.1.2",
"cors": "2.8.5",
"express-session": "1.17.1",
"express": "4.17.1",
"memorystore": "1.6.2",
"mime": "2.4.6",
"mustache": "4.0.1",
"memorystore": "1.6.4",
"mime": "2.4.7",
"multer": "1.4.2",
"mustache": "4.1.0",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",

View File

@@ -32,7 +32,7 @@
"label" : {
"view" : {
"view" : "Ansicht",
"grid" : "Gitter",
"grid" : "Raster",
"showGrid" : "Raster anzeigen",
"snapGrid" : "Am Raster ausrichten",
"gridSize" : "Rastergröße",

View File

@@ -22,7 +22,8 @@
"color": "Color",
"position": "Position",
"enable": "Enable",
"disable": "Disable"
"disable": "Disable",
"upload": "Upload"
},
"type": {
"string": "string",
@@ -41,7 +42,8 @@
"loadNodeCatalogs": "Loading Node catalogs",
"loadNodes": "Loading Nodes __count__",
"loadFlows": "Loading Flows",
"importFlows": "Adding Flows to workspace"
"importFlows": "Adding Flows to workspace",
"importError": "<p>Error adding flows</p><p>__message__</p>"
},
"workspace": {
"defaultName": "Flow __number__",
@@ -197,6 +199,8 @@
"flow_plural": "__count__ flows",
"subflow": "__count__ subflow",
"subflow_plural": "__count__ subflows",
"replacedNodes": "__count__ node replaced",
"replacedNodes_plural": "__count__ nodes replaced",
"pasteNodes": "Paste flow json or",
"selectFile": "select a file to import",
"importNodes": "Import nodes",
@@ -204,6 +208,8 @@
"download": "Download",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"importDuplicate": "Imported duplicate node:",
"importDuplicate_plural": "Imported duplicate nodes:",
"nodesExported": "Nodes exported to clipboard",
"nodesImported": "Imported:",
"nodeCopied": "__count__ node copied",
@@ -212,6 +218,9 @@
"groupCopied_plural": "__count__ groups copied",
"groupStyleCopied": "Group style copied",
"invalidFlow": "Invalid flow: __message__",
"recoveredNodes": "Recovered Nodes",
"recoveredNodesInfo": "The nodes on this flow were missing a valid flow id when they were imported. They have been added to this flow so you can either restore or delete them.",
"recoveredNodesNotification": "<p>Imported nodes without a valid flow id</p><p>They have been added to a new flow called '__flowName__'.</p>",
"export": {
"selected":"selected nodes",
"current":"current flow",
@@ -226,13 +235,19 @@
},
"import": {
"import": "Import to",
"importSelected": "Import selected",
"importCopy": "Import copy",
"viewNodes": "View nodes...",
"newFlow": "new flow",
"replace": "replace",
"errors": {
"notArray": "Input not a JSON Array",
"itemNotObject": "Input not a valid flow - item __index__ not a node object",
"missingId": "Input not a valid flow - item __index__ missing 'id' property",
"missingType": "Input not a valid flow - item __index__ missing 'type' property"
}
},
"conflictNotification1": "Some of the nodes you are importing already exist in your workspace.",
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them."
},
"copyMessagePath": "Path copied",
"copyMessageValue": "Value copied",
@@ -532,6 +547,8 @@
"sortAZ": "a-z",
"sortRecent": "recent",
"more": "+ __count__ more",
"upload": "Upload module tgz file",
"refresh": "Refresh module list",
"errors": {
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
@@ -708,6 +725,12 @@
"committerTip": "Leave blank to use system default",
"userName": "Username",
"email": "Email",
"workflow": "Workflow",
"workfowTip": "Choose your preferred git workflow",
"workflowManual": "Manual",
"workflowManualTip": "All changes must be manually committed under the 'history' sidebar",
"workflowAuto": "Automatic",
"workflowAutoTip": "Changes are committed automatically with every deploy",
"sshKeys": "SSH Keys",
"sshKeysTip": "Allows you to create secure connections to remote git repositories.",
"add": "add key",
@@ -1066,6 +1089,7 @@
"en-US": "English",
"ja": "Japanese",
"ko": "Korean",
"ru": "Russian",
"zh-CN": "Chinese(Simplified)",
"zh-TW": "Chinese(Traditional)"
}

View File

@@ -22,7 +22,8 @@
"color": "色",
"position": "配置",
"enable": "有効",
"disable": "無効"
"disable": "無効",
"upload": "アップロード"
},
"type": {
"string": "文字列",
@@ -41,7 +42,8 @@
"loadNodeCatalogs": "ノードカタログを読み込み中",
"loadNodes": "ノードを読み込み中 __count__",
"loadFlows": "フローを読み込み中",
"importFlows": "ワークスペースにフローを追加中"
"importFlows": "ワークスペースにフローを追加中",
"importError": "<p>フロー追加エラー</p><p>__message__</p>"
},
"workspace": {
"defaultName": "フロー __number__",
@@ -197,13 +199,17 @@
"flow_plural": "__count__ 個のフロー",
"subflow": "__count__ 個のサブフロー",
"subflow_plural": "__count__ 個のサブフロー",
"pasteNodes": "JSON形式のフローデータを貼り付けてください",
"selectFile": "読み込むファイルを選択してください",
"importNodes": "フローをクリップボートから読み込み",
"replacedNodes": "置換された __count__ 個のノード",
"replacedNodes_plural": "置換された __count__ 個のノード",
"pasteNodes": "JSON形式のフローデータを貼り付け",
"selectFile": "読み込むファイルを選択",
"importNodes": "フローをクリップボードから読み込み",
"exportNodes": "フローをクリップボードへ書き出し",
"download": "ダウンロード",
"importUnrecognised": "認識できない型が読み込まれました:",
"importUnrecognised_plural": "認識できない型が読み込まれました:",
"importDuplicate": "重複したノードを読み込みました:",
"importDuplicate_plural": "重複したノードを読み込みました:",
"nodesExported": "クリップボードへフローを書き出しました",
"nodesImported": "読み込みました:",
"nodeCopied": "__count__ 個のノードをコピーしました",
@@ -212,6 +218,9 @@
"groupCopied_plural": "__count__ 個のグループをコピーしました",
"groupStyleCopied": "グループの形式をコピーしました",
"invalidFlow": "不正なフロー: __message__",
"recoveredNodes": "復旧したノード",
"recoveredNodesInfo": "このフロー内のードは読み込み時に、有効なフローIDがありませんでした。これらフローIDは、フローに追加されているため、復元または削除できます。",
"recoveredNodesNotification": "<p>有効なフローIDを持たないードが読み込まれました</p><p>これらノードは '__flowName__' という新しいフローへ追加されました。</p>",
"export": {
"selected": "選択したフロー",
"current": "現在のタブ",
@@ -226,13 +235,19 @@
},
"import": {
"import": "読み込み先",
"importSelected": "選択したノードを読み込み",
"importCopy": "コピーを読み込み",
"viewNodes": "ノードを参照...",
"newFlow": "新規のタブ",
"replace": "置換",
"errors": {
"notArray": "JSON形式の配列ではありません",
"itemNotObject": "不正なフロー - __index__ 番目の要素はノードオブジェクトではありません",
"missingId": "不正なフロー - __index__ 番目の要素に'id'プロパティがありません",
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
}
},
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
},
"copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました",
@@ -532,6 +547,8 @@
"sortAZ": "辞書順",
"sortRecent": "日付順",
"more": "+ さらに __count__ 個",
"upload": "モジュールのtgzファイルをアップロード",
"refresh": "モジュールリスト更新",
"errors": {
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
@@ -708,6 +725,12 @@
"committerTip": "システムのデフォルトを使用する場合、空白のままにしてください",
"userName": "ユーザ名",
"email": "メールアドレス",
"workflow": "ワークフロー",
"workfowTip": "望ましいgitワークフローを選択してください",
"workflowManual": "手動",
"workflowManualTip": "全ての変更は「履歴」サイドバー内で手動でコミットする必要があります",
"workflowAuto": "自動",
"workflowAutoTip": "変更はデプロイの度に自動的にコミットされます",
"sshKeys": "SSH キー",
"sshKeysTip": "gitリポジトリへのセキュアな接続を作成できます。",
"add": "キーを追加",
@@ -1066,6 +1089,7 @@
"en-US": "英語",
"ja": "日本語",
"ko": "韓国語",
"ru": "ロシア語",
"zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0" : "Вы можете удалить выбранные узлы или провода с {{core:delete-selection}}",
"tip1" : "Ищите узлы с {{core:search}}",
"tip2" : "{{core:toggle-sidebar}} показывает/скрывает эту боковою панель",
"tip3" : "Вы можете управлять палитрой узлов с помощью {{core:manage-palette}}",
"tip4" : "Узлы конфигурации потока перечисляются на боковой панели. Доступ к списку можно получить из меню или с помощью {{core:show-config-tab}}",
"tip5" : "Эти советы можно включить/выключить через настройки",
"tip6" : "Перемещайте выбранные узлы клавишами [влево] [вверх] [вниз] и [вправо]. Удерживайте [Shift], чтобы увеличить шаг",
"tip7" : "Перетаскивание узла на провод соединит его с обеих сторон",
"tip8" : "Экспортируйте выбранные узлы или текущую вкладку с {{core:show-export-dialog}}",
"tip9" : "Импортируйте поток, перетаскивая его JSON в редактор или с помощью {{core:show-import-dialog}}",
"tip10" : "Нажмите [Shift], [кликните] по порту узла и перетаскивайте подключенные провода на другой узел",
"tip11" : "Открывайте вкладку Информация с {{core:show-info-tab}} или вкладку Отладка с {{core:show-debug-tab}}",
"tip12" : "Нажмите [ctrl] и [кликните] в рабочей области, чтобы открыть диалог быстрого добавления",
"tip13" : "Нажмите [ctrl] и [кликните] по порту узла, чтобы начать быстрое подключение",
"tip14" : "Нажмите [Shift] и [кликните] по узлу, чтобы выбрать все соединенные узлы",
"tip15" : "Нажмите [ctrl] и [кликните] по узлу, чтобы добавить или убрать его из текущего выбора",
"tip16" : "Переключайте вкладки потока с помощью {{core:show-previous-tab}} и {{core:show-next-tab}}",
"tip17" : "Вы можете подтвердить изменения в редакторе узла с {{core:confirm-edit-tray}} или отменить их с {{core:cancel-edit-tray}}",
"tip18" : "Нажатие {{core:edit-selected-node}} откроет редактор первого узла в текущем выборе"
}
}

View File

@@ -0,0 +1,274 @@
{
"$string": {
"args": "arg[, prettify]",
"desc": "Преобразует параметр `arg` в строку, используя следующие правила приведения:\n\n - Строки возвращаются как есть\n - Функции преобразуются в пустую строку\n - Числовая бесконечность и NaN выдают ошибку, поскольку они не могут быть представлены числом в JSON\n - Все остальные значения преобразуются в строку JSON с помощью функции `JSON.stringify`. Если значение `prettify` равно true, тогда будет сгенерирован \"отформатированный\" JSON. То есть каждое поле будет в отдельной строке, а строки будут иметь отступ в зависимости от глубины поля."
},
"$length": {
"args": "str",
"desc": "Возвращает количество символов в строке `str`. Выдается ошибка, если `str` не является строкой."
},
"$substring": {
"args": "str, start[, length]",
"desc": "Возвращает строку, содержащую символы из первого параметра `str`, начиная с позиции `start` (отсчет с нуля). Если указан `length`, то подстрока будет содержать максимум `length` символов. Если `start` отрицателен, то это означает количество символов с конца `str`."
},
"$substringBefore": {
"args": "str, chars",
"desc": "Возвращает подстроку перед первым вхождением последовательности символов `chars` в строке `str`. Если `str` не содержит `chars`, то он возвращает `str`."
},
"$substringAfter": {
"args": "str, chars",
"desc": "Возвращает подстроку после первого вхождения последовательности символов `chars` в строке `str`. Если `str` не содержит `chars`, то он возвращает `str`."
},
"$uppercase": {
"args": "str",
"desc": "Возвращает строку со всеми символами `str`, преобразованными в верхний регистр."
},
"$lowercase": {
"args": "str",
"desc": "Возвращает строку со всеми символами `str`, преобразованными в нижний регистр."
},
"$trim": {
"args": "str",
"desc": "Нормализует и обрезает все пробельные символы в строке `str`, выполняя следующие шаги:\n\n - Все символы табуляции, возврата каретки и перевода строки заменяются пробелами.\n- Последовательности пробелов сокращаются до одного пробела.\n- Пробелы в начале и конце `str` удаляются.\n\n Если `str` не указан (то есть эта функция вызывается без аргументов), тогда значение контекста используется в качестве значения `str`. Выдается ошибка, если `str` не является строкой."
},
"$contains": {
"args": "str, pattern",
"desc": "Возвращает `true`, если строка `str` соответствует шаблону `pattern`, в противном случае возвращает `false`. Если `str` не указан (то есть эта функция вызывается с одним аргументом), то значение контекста используется как значение `str`. Параметр `pattern` может быть либо строкой, либо регулярным выражением."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "Разбивает строку `str` на массив подстрок. Выдает ошибку, если `str` не является строкой. Необязательный параметр `separator` (строка или регулярное выражение) указывает символы внутри строки `str`, относительно которых она должна быть разделена. Если `separator` не указан, то предполагается пустая строка, и `str` будет разбит на массив из отдельных символов. Выдает ошибку, если `separator` не является строкой. Необязательный параметр `limit` - это число, указывающее максимальное количество подстрок для включения в результирующий массив. Любые дополнительные подстроки отбрасываются. Если `limit` не указан, то весь `str` разделяется без ограничения размера результирующего массива. Выдает ошибку, если `limit` не является положительным числом."
},
"$join": {
"args": "array[, separator]",
"desc": "Объединяет массив подстрок в одну объединенную строку, в которой каждая подстрока отделена необязательным параметром `separator`. Выдает ошибку, если входной массив содержит элемент, который не является строкой. Если `separator` не указан, то предполагается, что это пустая строка, то есть нет `separator` между подстроками. Выдает ошибку, если `separator` не является строкой."
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "Применяет строку `str` к регулярному выражению `pattern` и возвращает массив объектов, каждый из которых содержит информацию о каждом совпадении внутри `str`."
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "Находит вхождения шаблона `pattern` в строке `str` и заменяет их на строку `replacement`.\n\nНеобязательный параметр `limit` - это максимальное количество замен."
},
"$now": {
"args":"",
"desc":"Создает отметку времени в формате, совместимом с ISO 8601, и возвращает ее как строку."
},
"$base64encode": {
"args":"string",
"desc":"Преобразует ASCII-строку в base-64 кодировку. Каждый символ в строке обрабатывается как байт двоичных данных. Для этого необходимо, чтобы все символы в строке находились в диапазоне от 0x00 до 0xFF, который включает все символы строк в URI-кодировке. Символы Юникода за пределами этого диапазона не поддерживаются."
},
"$base64decode": {
"args":"string",
"desc":"Преобразует байты в кодировке base-64 в строку, используя кодовую страницу Юникод UTF-8."
},
"$number": {
"args": "arg",
"desc": "Преобразует параметр `arg` в число с использованием следующих правил приведения:\n\n - Числа возвращаются как есть\n - Строки, которые содержат последовательность символов, представляющих допустимое в JSON число, преобразуются в это число\n - Все остальные значения вызывают ошибку."
},
"$abs": {
"args":"number",
"desc":"Возвращает абсолютное значение числа `number`."
},
"$floor": {
"args":"number",
"desc":"Возвращает значение числа `number`, округленное до ближайшего целого числа, которое меньше или равно `number`."
},
"$ceil": {
"args":"number",
"desc":"Возвращает значение числа `number`, округленное до ближайшего целого числа, которое больше или равно `number`."
},
"$round": {
"args":"number [, precision]",
"desc":"Возвращает значение числа `number`, округленное до количества десятичных знаков, указанных необязательным параметром `precision`."
},
"$power": {
"args":"base, exponent",
"desc":"Возвращает значение числа `base`, возведенное в степень `exponent`."
},
"$sqrt": {
"args":"number",
"desc":"Возвращает квадратный корень из значения числа `number`."
},
"$random": {
"args":"",
"desc":"Возвращает псевдослучайное число, которе больше или равно нулю и меньше единицы."
},
"$millis": {
"args":"",
"desc":"Возвращает число миллисекунд с начала Unix-эпохи (1 января 1970 года по Гринвичу) в виде числа. Все вызовы `$millis()` в пределах выполнения выражения будут возвращать одно и то же значение."
},
"$sum": {
"args": "array",
"desc": "Возвращает арифметическую сумму массива чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
},
"$max": {
"args": "array",
"desc": "Возвращает максимальное число в массиве чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
},
"$min": {
"args": "array",
"desc": "Возвращает минимальное число в массиве чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
},
"$average": {
"args": "array",
"desc": "Возвращает среднее значение массива чисел `array`. Вызывает ошибку, если входной массив `array` содержит элемент, который не является числом."
},
"$boolean": {
"args": "arg",
"desc": "Приводит аргумент к логическому значению, используя следующие правила: \n\n - Логические значения возвращаются как есть\n - пустая строка: `false`\n - непустая строка: `true`\n - число равное `0`: `false`\n - ненулевое число: `true`\n - `null` : `false`\n - пустой массив: `false`\n - массив, который содержит хотя бы один элемент, приводимый к `true`: `true`\n - массив, все элементы которого приводятся к `false`: `false`\n - пустой объект: `false`\n - непустой объект: `true`\n - функция: `false`"
},
"$not": {
"args": "arg",
"desc": "Возвращает логическое НЕ для аргумента. `arg` сначала приводится к логическому значению"
},
"$exists": {
"args": "arg",
"desc": "Возвращает логическое `true`, если выполнение выражения `arg` возвращает значение, или `false`, если выражение ничему не соответствует (например, путь к несуществующему полю)."
},
"$count": {
"args": "array",
"desc": "Возвращает количество элементов в массиве"
},
"$append": {
"args": "array, array",
"desc": "Присоединяет один массив к другому"
},
"$sort": {
"args":"array [, function]",
"desc":"Возвращает массив, содержащий все значения параметра `array`, но отсортированные по порядку.\n\nЕсли указан компаратор `function`, то это должна быть функция, которая принимает два параметра:\n\n`function(val1, val2)`\n\nЭту функцию вызывает алгоритм сортировки для сравнения двух значений: val1 и val2. Если значение val1 следует поместить после значения val2 в желаемом порядке сортировки, то функция должна возвращать логическое значение `true`, чтобы обозначить замену. В противном случае она должна вернуть `false`."
},
"$reverse": {
"args":"array",
"desc":"Возвращает массив, содержащий все значения из параметра `array`, но в обратном порядке."
},
"$shuffle": {
"args":"array",
"desc":"Возвращает массив, содержащий все значения из параметра `array`, но перемешанный в случайном порядке."
},
"$zip": {
"args":"array, ...",
"desc":"Возвращает свернутый (сжатый) массив, содержащий сгруппированные массивы значений из аргументов `array1` … `arrayN` по индексам 0, 1, 2...."
},
"$keys": {
"args": "object",
"desc": "Возвращает массив, содержащий ключи объекта. Если аргумент является массивом объектов, то возвращаемый массив содержит недублированный список всех ключей из всех объектов."
},
"$lookup": {
"args": "object, key",
"desc": "Возвращает значение, связанное с ключом в объекте. Если первый аргумент является массивом объектов, то просходит поиск по всем объектам в массиве, и возвращаются значения, связанные со всеми вхождениями ключа."
},
"$spread": {
"args": "object",
"desc": "Разбивает объект, содержащий пары ключ / значение, на массив объектов, каждый из которых имеет одну пару ключ / значение из входного объекта. Если параметр является массивом объектов, то результирующий массив содержит объект для каждой пары ключ / значение из каждого объекта предоставленного массива."
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "Объединяет массив объектов в один объект, содержащий все пары ключ / значение каждого из объектов входного массива. Если какой-либо из входных объектов содержит один и тот же ключ, возвращаемый объект будет содержать значение последнего в массиве. Вызывает ошибку, если входной массив содержит элемент, который не является объектом."
},
"$sift": {
"args":"object, function",
"desc":"Возвращает объект, который содержит только пары ключ / значение из параметра `object`, которые удовлетворяют предикату `function`, переданному в качестве второго параметра.\n\n`function`, которая передается в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, key [, object]])`"
},
"$each": {
"args":"object, function",
"desc":"Возвращает массив, который содержит значения, возвращаемые функцией `function` при применении к каждой паре ключ/значение из объекта `object`."
},
"$map": {
"args":"array, function",
"desc":"Возвращает массив, содержащий результаты применения функции `function` к каждому значению массива `array`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args":"array, function",
"desc":"Возвращает массив, содержащий только те значения из массива `array`, которые удовлетворяют предикату `function`.\n\nФункция `function`, указанная в качестве второго параметра, должна иметь следующую сигнатуру:\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args":"array, function [, init]",
"desc":"Возвращает агрегированное значение, полученное в результате последовательного применения функции `function` к каждому значению в массиве в сочетании с результатом от предыдущего применения функции.\n\nФункция должна принимать два аргумента и вести себя как инфиксный оператор между каждым значением в массиве `array`. Сигнатура `function` должна иметь форму: `myfunc($accumulator, $value[, $index[, $array]])`\n\nНеобязательный параметр `init` используется в качестве начального значения в агрегации."
},
"$flowContext": {
"args": "string[, string]",
"desc": "Извлекает свойство контекста потока.\n\nЭто функция от Node-RED."
},
"$globalContext": {
"args": "string[, string]",
"desc": "Извлекает свойство глобального контекста.\n\nЭто функция от Node-RED."
},
"$pad": {
"args": "string, width [, char]",
"desc": "Возвращает копию строки `string` с дополнительным заполнением, если необходимо, чтобы общее количество символов как минимум соответствовало абсолютному значению параметра `width`.\n\nЕсли `width` является положительным числом, то строка дополняется справа; если отрицательным, то дополняется слева.\n\nНеобязательный аргумент `char` указывает символ(ы) для заполнения. Если не указано, по умолчанию используется пробел."
},
"$fromMillis": {
"args": "number",
"desc": "Преобразует число, представляющее миллисекунды с начала Unix-эпохи (1 января 1970 года по Гринвичу), в строку отметки времени в формате ISO 8601."
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "Преобразует число `number` в строку и форматирует ее в десятичное представление, как указано в строке `picture`.\n\nПоведение этой функции соответствует XPath/XQuery-функции fn:format-number, как определено в спецификация XPath F&O 3.1. Строка `picture` определяет, как форматируется число и имеет тот же синтаксис, что и fn:format-number.\n\nНеобязательный третий аргумент `options` используется для переопределения символов форматирования, специфичных для локали по умолчанию, таких как десятичный разделитель. Если аргумент указан, то это должен быть объект, содержащий пары имя/значение, указанные в разделе десятичного формата спецификации XPath F&O 3.1."
},
"$formatBase": {
"args": "number [, radix]",
"desc": "Преобразует число `number` в строку и форматирует ее в целое число, представленное в системе счисления, указанной аргументом `radix`. Если `radix` не указан, то по умолчанию используется десятичная. Значение 'radix` может быть от 2 до 36, в противном случае выдается ошибка."
},
"$toMillis": {
"args": "timestamp",
"desc": "Преобразует строку `timestamp` в формате ISO 8601 в число миллисекунд с начала Unix-эпохи (1 января 1970 года по Гринвичу). Вызывает ошибку, если строка в неправильном формате."
},
"$env": {
"args": "arg",
"desc": "Возвращает значение переменной среды.\n\nЭто функция от Node-RED."
},
"$eval": {
"args": "expr [, context]",
"desc": "Анализирует и исполняет строку `expr`, которая содержит JSON или выражение JSONata, используя текущий контекст в качестве контекста для исполнения."
},
"$formatInteger": {
"args": "number, picture",
"desc": "Преобразует число `number` в строку и форматирует ее в целочисленное представление, как указано в строке `picture`. Строка `picture` определяет, как форматируется число и имеет тот же синтаксис, что и `fn:format-integer` из спецификации XPath F&O 3.1."
},
"$parseInteger": {
"args": "string, picture",
"desc": "Разбирает содержимое строки `string` в целое число (как число JSON), используя формат, указанный в строке `picture`. Строковый параметр `picture` имеет тот же формат, что и `$formatInteger`."
},
"$error": {
"args": "[str]",
"desc": "Вызывает ошибку с сообщением. Необязательная строка `str` заменяет сообщение по умолчанию $error() function evaluated`"
},
"$assert": {
"args": "arg, str",
"desc": "Если значение `arg` равно true, функция возвращает значение undefined. Если значение `arg` равно false, генерируется исключение с `str` в качестве сообщения об исключении."
},
"$single": {
"args": "array, function",
"desc": "Возвращает одно-единственное значение из массива `array`, которое удовлетворяет предикату `function` (то есть когда `function` возвращает логическое `true` при передаче значения). Выдает исключение, если число подходящих значений не одно.\n\nФункция должна соответствовать следующей сигнатуре: `function(value [, index [, array]])` где value - элемент массива, index - позиция этого значения, а весь массив передается в качестве третьего аргумента"
},
"$encodeUrl": {
"args": "str",
"desc": "Кодирует компонент Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "Кодирует Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrlComponent.\n\nПример: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrl. \n\nПример: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "Возвращает массив содержащий все элементы из массива `array`, с удаленными дупликатами"
},
"$type": {
"args": "value",
"desc": "Возвращает тип значения `value` в виде строки. Если `value` не определено, то будет возвращено `undefined`"
},
"$moment": {
"args": "[str]",
"desc": "Получает date объект, используя библиотеку Moment."
}
}

View File

@@ -22,7 +22,8 @@
"color": "颜色",
"position": "位置",
"enable": "启用",
"disable": "禁用"
"disable": "禁用",
"upload": "上传"
},
"type": {
"string": "字符串",
@@ -197,6 +198,8 @@
"flow_plural": "__count__ 个流程",
"subflow": "__count__ 个子流程",
"subflow_plural": "__count__ 子流程",
"replacedNodes": "__count__ 个节点被置换",
"replacedNodes_plural": "__count__ 个节点被置换",
"pasteNodes": "在这里粘贴节点",
"selectFile": "选择要导入的文件",
"importNodes": "导入节点",
@@ -212,6 +215,9 @@
"groupCopied_plural": "已复制 __count__ 个groups",
"groupStyleCopied": "已复制组风格",
"invalidFlow": "无效的流程: __message__",
"recoveredNodes": "复原的节点",
"recoveredNodesInfo": "导入节点时此流上的节点缺少有效的流ID。 它们已被添加到此流中,您可以复原或删除它们。",
"recoveredNodesNotification": "<p>导入的节点缺少有效的流ID</p><p>已将它们添加到名为 '__flowName__'的新流中。</p>",
"export": {
"selected": "已选择的节点",
"current": "现在的节点",
@@ -226,13 +232,19 @@
},
"import": {
"import": "导入到",
"importSelected": "导入所选项",
"importCopy": "导入副本",
"viewNodes": "查看节点",
"newFlow": "新流程",
"replace": "置换",
"errors": {
"notArray": "输入的不是JSON数组",
"itemNotObject": "输入的流无效 - 项目 __index__ 不是节点对象",
"missingId": "输入的流无效-项 __index__ 缺少'id'属性",
"missingType": "输入的流程无效-项 __index__ 缺少'类型'属性"
}
},
"conflictNotification1": "您要导入的某些节点已经存在于工作空间中。",
"conflictNotification2": "选择要导入的节点,并确认要替换现有的节点还是导入它们的副本"
},
"copyMessagePath": "已复制路径",
"copyMessageValue": "已复制数值",
@@ -533,6 +545,7 @@
"sortAZ": "a-z顺序",
"sortRecent": "日期顺序",
"more": "增加 __count__ 个",
"upload": "上传模块tgz文件",
"errors": {
"catalogLoadFailed": "无法加载节点目录。<br>查看浏览器控制台了解更多信息",
"installFailed": "无法安装: __module__<br>__message__<br>查看日志了解更多信息",
@@ -709,6 +722,12 @@
"committerTip": "保留空白以使用系统默认值",
"userName": "用户名",
"email": "电子邮件",
"workflow": "工作流",
"workfowTip": "选择您偏好的工作流",
"workflowManual": "手动",
"workflowManualTip": "所有更改都必须在“历史记录”侧边栏中手动提交",
"workflowAuto": "自动",
"workflowAutoTip": "每次部署后都会自动提交更改",
"sshKeys": "SSH密钥",
"sshKeysTip": "允许您创建到远程git存储库的安全连接。",
"add": "添加密钥",

View File

@@ -22,7 +22,8 @@
"color": "顏色",
"position": "位置",
"enable": "啟用",
"disable": "禁用"
"disable": "禁用",
"upload": "上傳"
},
"type": {
"string": "字符串",
@@ -197,6 +198,8 @@
"flow_plural": "__count__ 多流程",
"subflow": "__count__ 子流程",
"subflow_plural": "__count__ 多子流程",
"replacedNodes": "__count__ 個節點被置換",
"replacedNodes_plural": "__count__ 個節點被置換",
"pasteNodes": "在這裡粘貼節點",
"selectFile": "匯入所選檔案",
"importNodes": "匯入節點",
@@ -212,6 +215,9 @@
"groupCopied_plural": "已複製 __count__ 個groups",
"groupStyleCopied": "已複製組風格",
"invalidFlow": "無效的流程: __message__",
"recoveredNodes": "復原的節點",
"recoveredNodesInfo": "導入節點時此流上的節點缺少有效的流ID。它們已被添加到此流中您可以復原或刪除它們。",
"recoveredNodesNotification": "<p>導入的節點缺少有效的流ID</p><p>已將它們添加到名為 '__flowName__'的新流中。</p>",
"export": {
"selected": "已選擇的節點",
"current": "現在的節點",
@@ -226,13 +232,19 @@
},
"import": {
"import": "匯入到",
"importSelected": "導入所選項",
"importCopy": "導入副本",
"viewNodes": "查看節點",
"newFlow": "新流程",
"replace": "置換",
"errors": {
"notArray": "輸入的不是JSON數組",
"itemNotObject": "輸入的流程無效-項目 __index__ 不是節點對象",
"missingId": "輸入的流程無效-項 __index__ 缺少“ id”屬性",
"missingType": "輸入的流程無效-項 __index__ 缺少“類型”屬性"
}
},
"conflictNotification1": "您要導入的某些節點已經存在於工作空間中。",
"conflictNotification2": "選擇要導入的節點,並確認要替換現有的節點還是導入它們的副本"
},
"copyMessagePath": "已複製路徑",
"copyMessageValue": "已複製數值",
@@ -533,6 +545,7 @@
"sortAZ": "a-z順序",
"sortRecent": "日期順序",
"more": "增加 __count__ 個",
"upload": "上傳模塊tgz文件",
"errors": {
"catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制臺瞭解更多資訊",
"installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊",
@@ -709,6 +722,12 @@
"committerTip": "保留空白以使用系統默認值",
"userName": "用戶名",
"email": "電子郵件",
"workflow": "工作流",
"workfowTip": "選擇您偏好的工作流",
"workflowManual": "手動",
"workflowManualTip": "所有更改都必須在“歷史記錄”側邊欄中手動提交",
"workflowAuto": "自動",
"workflowAutoTip": "每次部署後都會自動提交更改",
"sshKeys": "SSH密鑰",
"sshKeysTip": "允許您創建到遠程git存儲庫的安全連接。",
"add": "添加密鑰",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "1.1.0",
"version": "1.2.8",
"license": "Apache-2.0",
"repository": {
"type": "git",

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,8 @@
ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\n /**\n * ${1:description}\n *\n */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n# Node-RED Specific Funcs\nsnippet nodes\n node.send(${1:msg})\nsnippet clone\n RED.util.cloneMessage(${1:msg})\nsnippet nodel\n node.log($1)\nsnippet nodew\n node.warn($1)\nsnippet nodee\n node.error($1)\nsnippet noded\n node.debug($1)\nsnippet done\n node.done($1)\nsnippet flowg\n flow.get($1)\nsnippet flows\n flow.set($1, $2)\nsnippet globalg\n global.get($1)\nsnippet globals\n global.set($1, $2)\n',t.scope="nrjavascript"});
(function() {
; (function() {
ace.require(["ace/snippets/nrjavascript"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

View File

@@ -76,13 +76,12 @@ oop.inherits(NRJavaScriptWorker, Mirror);
(function() {
this.setOptions = function(options) {
this.options = options || {
this.options = {
// undef: true,
// unused: true,
esnext: true,
moz: true,
esversion: 9,
devel: true,
browser: true,
browser: false,
node: true,
laxcomma: true,
laxbreak: true,
@@ -92,8 +91,17 @@ oop.inherits(NRJavaScriptWorker, Mirror);
maxerr: 100,
expr: true,
multistr: true,
globalstrict: true
strict: false,
sub: true,
asi: true
};
if (options) {
for (var opt in options) {
if (options.hasOwnProperty(opt)) {
this.options[opt] = options.opt;
}
}
}
this.doc.getValue() && this.deferredUpdate.schedule(100);
};
@@ -119,6 +127,8 @@ oop.inherits(NRJavaScriptWorker, Mirror);
if (!value)
return this.sender.emit("annotate", []);
var originalValue = value;
// [Node-RED] wrap the code in a function
value = "async function __nodered__(msg) {\n"+value+"\n}";
@@ -138,6 +148,7 @@ oop.inherits(NRJavaScriptWorker, Mirror);
continue;
var raw = error.raw;
var type = "warning";
var line = error.line - 2;
if (raw == "Missing semicolon.") {
var str = error.evidence.substr(error.character);
@@ -166,9 +177,62 @@ oop.inherits(NRJavaScriptWorker, Mirror);
type = "info";
}
if (raw === "Unmatched '{a}'." && line === -1) {
// This is an unmatched { error. It has incorrectly matched it
// against the { in the added line. Need to find the next valid {
// This code scans through the original code looking for the first '{'
// that is not in a comment or string.
// It will incorrectly find a '{' if it is inside a regex... but
// at least the error will be shown somwhere. There are only
// so many hours in the day to fix every tiny edge case of an
// edge case.
var inSingleComment = false;
var inMultiComment = false;
var inString = false;
var stringQ;
var lineNumber = 0;
for (var pos = 0;pos<originalValue.length;pos++) {
var c = originalValue[pos];
if (c === "\\") {
pos++;
} else if (inSingleComment) {
if (c === "\n") {
lineNumber++;
inSingleComment = false;
}
} else if (inMultiComment) {
if (c === "*" && originalValue[pos+1] === "/") {
pos++;
inMultiComment = false;
} else if (c === "\n") {
lineNumber++;
}
} else if (inString) {
if (c === stringQ) {
inString = false;
}
} else if (c === "'" || c === "\"") {
inString = true;
stringQ = c;
} else if (c === "/") {
if (originalValue[pos+1] === "/") {
inSingleComment = true;
} else if (originalValue[pos+1] === "*") {
inMultiComment = true;
}
} else if (c === "\n") {
lineNumber++;
} else if (c === "{") {
// found it!
break;
}
}
line = lineNumber;
}
errors.push({
// [Node-RED] offset the row for the added line
row: error.line-2,
row: Math.max(0,line),
column: error.character-1,
text: error.reason,
type: type,

View File

@@ -808,17 +808,7 @@ RED.nodes.fontAwesome = (function() {
"fa-youtube": "\uf167",
};
var iconList = [];
var isUsed = {};
Object.keys(iconMap).forEach(function(icon) {
var unicode = iconMap[icon];
// skip icons with a same unicode
if (isUsed[unicode] !== true) {
iconList.push(icon);
isUsed[unicode] = true;
}
});
isUsed = undefined;
var iconList = Object.keys(iconMap);
return {
getIconUnicode: function(name) {

View File

@@ -37,22 +37,38 @@ RED.history = (function() {
inverseEv.events.push(r);
}
} else if (ev.t == 'replace') {
inverseEv = {
t: 'replace',
config: RED.nodes.createCompleteNodeSet(),
changed: {},
rev: RED.nodes.version()
};
RED.nodes.clear();
var imported = RED.nodes.import(ev.config);
imported[0].forEach(function(n) {
if (ev.changed[n.id]) {
n.changed = true;
inverseEv.changed[n.id] = true;
}
})
if (ev.complete) {
// This is a replace of everything. We can short-cut
// the logic by clearing everyting first, then importing
// the ev.config.
// Used by RED.diff.mergeDiff
inverseEv = {
t: 'replace',
config: RED.nodes.createCompleteNodeSet(),
changed: {},
rev: RED.nodes.version()
};
RED.nodes.clear();
var imported = RED.nodes.import(ev.config);
imported.nodes.forEach(function(n) {
if (ev.changed[n.id]) {
n.changed = true;
inverseEv.changed[n.id] = true;
}
})
RED.nodes.version(ev.rev);
RED.nodes.version(ev.rev);
} else {
var importMap = {};
ev.config.forEach(function(n) {
importMap[n.id] = "replace";
})
var importedResult = RED.nodes.import(ev.config,{importMap: importMap})
inverseEv = {
t: 'replace',
config: importedResult.removedNodes
}
}
} else if (ev.t == 'add') {
inverseEv = {
t: "delete",
@@ -85,7 +101,7 @@ RED.history = (function() {
}
if (ev.groups) {
inverseEv.groups = [];
for (i=0;i<ev.groups.length;i++) {
for (i = ev.groups.length - 1;i>=0;i--) {
group = ev.groups[i];
modifiedTabs[group.z] = true;
// The order of groups is important
@@ -214,7 +230,7 @@ RED.history = (function() {
inverseEv.groups = [];
var groupsToAdd = {};
ev.groups.forEach(function(g) { groupsToAdd[g.id] = g; });
for (i=0;i<ev.groups.length;i++) {
for (i = ev.groups.length - 1;i>=0;i--) {
RED.nodes.addGroup(ev.groups[i])
modifiedTabs[ev.groups[i].z] = true;
// The order of groups is important

View File

@@ -16,7 +16,7 @@
RED.nodes = (function() {
var node_defs = {};
var nodes = [];
var nodes = {};
var nodeTabMap = {};
var configNodes = {};
@@ -189,6 +189,7 @@ RED.nodes = (function() {
})();
function getID() {
// return Math.floor(Math.random()*15728640 + 1048576).toString(16)
return (1+Math.random()*4294967295).toString(16);
}
@@ -216,7 +217,7 @@ RED.nodes = (function() {
});
n.i = nextId+1;
}
nodes.push(n);
nodes[n.id] = n;
if (nodeTabMap[n.z]) {
nodeTabMap[n.z][n.id] = n;
} else {
@@ -233,12 +234,8 @@ RED.nodes = (function() {
function getNode(id) {
if (id in configNodes) {
return configNodes[id];
} else {
for (var n in nodes) {
if (nodes[n].id == id) {
return nodes[n];
}
}
} else if (id in nodes) {
return nodes[id];
}
return null;
}
@@ -252,58 +249,56 @@ RED.nodes = (function() {
delete configNodes[id];
RED.events.emit('nodes:remove',node);
RED.workspaces.refresh();
} else {
node = getNode(id);
if (node) {
nodes.splice(nodes.indexOf(node),1);
if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id];
}
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
removedLinks.forEach(removeLink);
var updatedConfigNode = false;
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
var property = node._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
if (type && type.category == "config") {
var configNode = configNodes[node[d]];
if (configNode) {
updatedConfigNode = true;
if (configNode._def.exclusive) {
removeNode(node[d]);
removedNodes.push(configNode);
} else {
var users = configNode.users;
users.splice(users.indexOf(node),1);
}
} else if (id in nodes) {
node = nodes[id];
delete nodes[id]
if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id];
}
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
removedLinks.forEach(removeLink);
var updatedConfigNode = false;
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
var property = node._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
if (type && type.category == "config") {
var configNode = configNodes[node[d]];
if (configNode) {
updatedConfigNode = true;
if (configNode._def.exclusive) {
removeNode(node[d]);
removedNodes.push(configNode);
} else {
var users = configNode.users;
users.splice(users.indexOf(node),1);
}
}
}
}
}
if (node.type.indexOf("subflow:") === 0) {
var subflowId = node.type.substring(8);
var sf = RED.nodes.subflow(subflowId);
if (sf) {
sf.instances.splice(sf.instances.indexOf(node),1);
}
}
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.type.indexOf("subflow:") === 0) {
var subflowId = node.type.substring(8);
var sf = RED.nodes.subflow(subflowId);
if (sf) {
sf.instances.splice(sf.instances.indexOf(node),1);
}
}
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);
}
@@ -377,10 +372,13 @@ RED.nodes = (function() {
workspacesOrder.splice(workspacesOrder.indexOf(id),1);
var i;
var node;
for (i=0;i<nodes.length;i++) {
node = nodes[i];
if (node.z == id) {
removedNodes.push(node);
// TODO: this should use nodeTabMap
for (i in nodes) {
if (nodes.hasOwnProperty(i)) {
node = nodes[i];
if (node.z == id) {
removedNodes.push(node);
}
}
}
for(i in configNodes) {
@@ -487,17 +485,19 @@ RED.nodes = (function() {
}
function subflowContains(sfid,nodeid) {
for (var i=0;i<nodes.length;i++) {
var node = nodes[i];
if (node.z === sfid) {
var m = /^subflow:(.+)$/.exec(node.type);
if (m) {
if (m[1] === nodeid) {
return true;
} else {
var result = subflowContains(m[1],nodeid);
if (result) {
for (var i in nodes) {
if (nodes.hasOwnProperty(i)) {
var node = nodes[i];
if (node.z === sfid) {
var m = /^subflow:(.+)$/.exec(node.type);
if (m) {
if (m[1] === nodeid) {
return true;
} else {
var result = subflowContains(m[1],nodeid);
if (result) {
return true;
}
}
}
}
@@ -553,6 +553,9 @@ RED.nodes = (function() {
node.id = n.id;
node.type = n.type;
node.z = n.z;
if (node.z === 0 || node.z === "") {
delete node.z;
}
if (n.d === true) {
node.d = true;
}
@@ -611,7 +614,9 @@ RED.nodes = (function() {
node.y = n.y;
node.w = n.w;
node.h = n.h;
node.nodes = node.nodes.map(function(n) { return n.id });
// In 1.1.0, we have seen an instance of this array containing `undefined`
// Until we know how that can happen, add a filter here to remove them
node.nodes = node.nodes.filter(function(n) { return !!n }).map(function(n) { return n.id });
}
if (n._def.category != "config") {
node.x = n.x;
@@ -735,6 +740,16 @@ RED.nodes = (function() {
return node;
}
function createExportableSubflow(id) {
var sf = getSubflow(id);
var nodeSet = [sf];
var sfNodeIds = Object.keys(nodeTabMap[sf.id]||{});
for (var i=0, l=sfNodeIds.length; i<l; i++) {
nodeSet.push(nodeTabMap[sf.id][sfNodeIds[i]]);
}
return createExportableNodeSet(nodeSet);
}
/**
* Converts the current node selection to an exportable JSON Object
**/
@@ -824,9 +839,10 @@ RED.nodes = (function() {
nns.push(convertNode(configNodes[i], exportCredentials));
}
}
for (i=0;i<nodes.length;i++) {
var node = nodes[i];
nns.push(convertNode(node, exportCredentials));
for (i in nodes) {
if (nodes.hasOwnProperty(i)) {
nns.push(convertNode(nodes[i], exportCredentials));
}
}
return nns;
}
@@ -896,11 +912,164 @@ RED.nodes = (function() {
return true;
}
function importNodes(newNodesObj,createNewIds,createMissingWorkspace) {
function identifyImportConflicts(importedNodes) {
var imported = {
tabs: {},
subflows: {},
groups: {},
configs: {},
nodes: {},
all: [],
conflicted: {},
zMap: {},
}
importedNodes.forEach(function(n) {
imported.all.push(n);
if (n.type === "tab") {
imported.tabs[n.id] = n;
} else if (n.type === "subflow") {
imported.subflows[n.id] = n;
} else if (n.type === "group") {
imported.groups[n.id] = n;
} else if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) {
imported.nodes[n.id] = n;
} else {
imported.configs[n.id] = n;
}
var nodeZ = n.z || "__global__";
imported.zMap[nodeZ] = imported.zMap[nodeZ] || [];
imported.zMap[nodeZ].push(n)
if (nodes[n.id] || configNodes[n.id] || workspaces[n.id] || subflows[n.id] || groups[n.id]) {
imported.conflicted[n.id] = n;
}
})
return imported;
}
/**
* Replace the provided nodes.
* This must contain complete Subflow defs or complete Flow Tabs.
* It does not replace an individual node in the middle of a flow.
*/
function replaceNodes(newNodes) {
var zMap = {};
var newSubflows = {};
var newConfigNodes = {};
var removedNodes = [];
// Figure out what we're being asked to replace - subflows/configNodes
// TODO: config nodes
newNodes.forEach(function(n) {
if (n.type === "subflow") {
newSubflows[n.id] = n;
} else if (!n.hasOwnProperty('x') && !n.hasOwnProperty('y')) {
newConfigNodes[n.id] = n;
}
if (n.z) {
zMap[n.z] = zMap[n.z] || [];
zMap[n.z].push(n);
}
})
// Filter out config nodes inside a subflow def that is being replaced
var configNodeIds = Object.keys(newConfigNodes);
configNodeIds.forEach(function(id) {
var n = newConfigNodes[id];
if (newSubflows[n.z]) {
// This config node is in a subflow to be replaced.
// - remove from the list as it'll get handled with the subflow
delete newConfigNodes[id];
}
});
// Rebuild the list of ids
configNodeIds = Object.keys(newConfigNodes);
// ------------------------------
// Replace subflow definitions
//
// For each of the subflows to be replaced:
var newSubflowIds = Object.keys(newSubflows);
newSubflowIds.forEach(function(id) {
var n = newSubflows[id];
// Get a snapshot of the existing subflow definition
removedNodes = removedNodes.concat(createExportableSubflow(id));
// Remove the old subflow definition - but leave the instances in place
var removalResult = RED.subflow.removeSubflow(n.id, true);
// Create the list of nodes for the new subflow def
var subflowNodes = [n].concat(zMap[n.id]);
// Import the new subflow - no clashes should occur as we've removed
// the old version
var result = importNodes(subflowNodes);
newSubflows[id] = getSubflow(id);
})
// Having replaced the subflow definitions, now need to update the
// instance nodes.
RED.nodes.eachNode(function(n) {
if (/^subflow:/.test(n.type)) {
var sfId = n.type.substring(8);
if (newSubflows[sfId]) {
// This is an instance of one of the replaced subflows
// - update the new def's instances array to include this one
newSubflows[sfId].instances.push(n);
// - update the instance's _def to point to the new def
n._def = RED.nodes.getType(n.type);
// - set all the flags so the view refreshes properly
n.dirty = true;
n.changed = true;
n._colorChanged = true;
}
}
})
newSubflowIds.forEach(function(id) {
var n = newSubflows[id];
RED.events.emit("subflows:change",n);
})
// Just in case the imported subflow changed color.
RED.utils.clearNodeColorCache();
// ------------------------------
// Replace config nodes
//
configNodeIds.forEach(function(id) {
removedNodes = removedNodes.concat(convertNode(getNode(id)));
removeNode(id);
importNodes([newConfigNodes[id]])
});
return {
removedNodes: removedNodes
}
}
/**
* Options:
* - generateIds - whether to replace all node ids
* - addFlow - whether to import nodes to a new tab
* - importToCurrent
* - importMap - how to resolve any conflicts.
* - id:import - import as-is
* - id:copy - import with new id
* - id:replace - import over the top of existing
*/
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
options = options || {
generateIds: false,
addFlow: false,
}
options.importMap = options.importMap || {};
var createNewIds = options.generateIds;
var createMissingWorkspace = options.addFlow;
var i;
var n;
var newNodes;
var nodeZmap = {};
var recoveryWorkspace;
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
@@ -925,14 +1094,59 @@ RED.nodes = (function() {
// copies of the flow would get loaded at the same time.
// If the user hit deploy they would have saved those duplicates.
var seenIds = {};
var existingNodes = [];
var nodesToReplace = [];
newNodes = newNodes.filter(function(n) {
var id = n.id;
if (seenIds[n.id]) {
return false;
}
seenIds[n.id] = true;
if (!options.generateIds) {
if (!options.importMap[id]) {
// No conflict resolution for this node
var existing = nodes[id] || configNodes[id] || workspaces[id] || subflows[id] || groups[id];
if (existing) {
existingNodes.push({existing:existing, imported:n});
}
} else if (options.importMap[id] === "replace") {
nodesToReplace.push(n);
return false;
}
}
return true;
})
if (existingNodes.length > 0) {
var errorMessage = RED._("clipboard.importDuplicate",{count:existingNodes.length});
var nodeList = $("<ul>");
var existingNodesCount = Math.min(5,existingNodes.length);
for (var i=0;i<existingNodesCount;i++) {
var conflict = existingNodes[i];
$("<li>").text(
conflict.existing.id+
" [ "+conflict.existing.type+ ((conflict.imported.type !== conflict.existing.type)?" | "+conflict.imported.type:"")+" ]").appendTo(nodeList)
}
if (existingNodesCount !== existingNodes.length) {
$("<li>").text(RED._("deploy.confirm.plusNMore",{count:existingNodes.length-existingNodesCount})).appendTo(nodeList)
}
var wrapper = $("<p>").append(nodeList);
var existingNodesError = new Error(errorMessage+wrapper.html());
existingNodesError.code = "import_conflict";
existingNodesError.importConfig = identifyImportConflicts(newNodes);
throw existingNodesError;
}
var removedNodes;
if (nodesToReplace.length > 0) {
var replaceResult = replaceNodes(nodesToReplace);
removedNodes = replaceResult.removedNodes;
}
var isInitialLoad = false;
if (!initialLoad) {
isInitialLoad = true;
@@ -941,6 +1155,7 @@ RED.nodes = (function() {
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
var id = n.id;
// TODO: remove workspace in next release+1
if (n.type != "workspace" &&
n.type != "tab" &&
@@ -954,11 +1169,32 @@ RED.nodes = (function() {
if (n.z) {
nodeZmap[n.z] = nodeZmap[n.z] || [];
nodeZmap[n.z].push(n);
} else if (isInitialLoad && n.hasOwnProperty('x') && n.hasOwnProperty('y') && !n.z) {
// Hit the rare issue where node z values get set to 0.
// Repair the flow - but we really need to track that down.
if (!recoveryWorkspace) {
recoveryWorkspace = {
id: RED.nodes.id(),
type: "tab",
disabled: false,
label: RED._("clipboard.recoveredNodes"),
info: RED._("clipboard.recoveredNodesInfo")
}
addWorkspace(recoveryWorkspace);
RED.workspaces.add(recoveryWorkspace);
nodeZmap[recoveryWorkspace.id] = [];
}
n.z = recoveryWorkspace.id;
nodeZmap[recoveryWorkspace.id].push(n);
}
}
if (!isInitialLoad && unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
var typeList = $("<ul>");
unknownTypes.forEach(function(t) {
$("<li>").text(t).appendTo(typeList);
})
typeList = typeList[0].outerHTML;
RED.notify("<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,"error",false,10000);
}
@@ -991,17 +1227,22 @@ RED.nodes = (function() {
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var subflow_blacklist = {};
var subflow_denylist = {};
var node_map = {};
var new_nodes = [];
var new_links = [];
var new_groups = [];
var new_group_set = new Set();
var nid;
var def;
var configNode;
var missingWorkspace = null;
var d;
if (recoveryWorkspace) {
new_workspaces.push(recoveryWorkspace);
}
// Find all tabs and subflow templates
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
@@ -1013,7 +1254,10 @@ RED.nodes = (function() {
if (defaultWorkspace == null) {
defaultWorkspace = n;
}
if (createNewIds) {
if (activeWorkspace === 0) {
activeWorkspace = n.id;
}
if (createNewIds || options.importMap[n.id] === "copy") {
nid = getID();
workspace_map[n.id] = nid;
n.id = nid;
@@ -1022,12 +1266,15 @@ RED.nodes = (function() {
RED.workspaces.add(n);
new_workspaces.push(n);
} else if (n.type === "subflow") {
var matchingSubflow = checkForMatchingSubflow(n,nodeZmap[n.id]);
var matchingSubflow;
if (!options.importMap[n.id]) {
matchingSubflow = checkForMatchingSubflow(n,nodeZmap[n.id]);
}
if (matchingSubflow) {
subflow_blacklist[n.id] = matchingSubflow;
subflow_denylist[n.id] = matchingSubflow;
} else {
subflow_map[n.id] = n;
if (createNewIds) {
if (createNewIds || options.importMap[n.id] === "copy") {
nid = getID();
n.id = nid;
}
@@ -1053,7 +1300,7 @@ RED.nodes = (function() {
n.status.id = getID();
}
new_subflows.push(n);
addSubflow(n,createNewIds);
addSubflow(n,createNewIds || options.importMap[n.id] === "copy");
}
}
}
@@ -1073,9 +1320,9 @@ RED.nodes = (function() {
def = registry.getNodeType(n.type);
if (def && def.category == "config") {
var existingConfigNode = null;
if (createNewIds) {
if (createNewIds || options.importMap[n.id] === "copy") {
if (n.z) {
if (subflow_blacklist[n.z]) {
if (subflow_denylist[n.z]) {
continue;
} else if (subflow_map[n.z]) {
n.z = subflow_map[n.z].id;
@@ -1094,23 +1341,28 @@ RED.nodes = (function() {
}
}
}
existingConfigNode = RED.nodes.node(n.id);
if (existingConfigNode) {
if (n.z && existingConfigNode.z !== n.z) {
existingConfigNode = null;
// Check the config nodes on n.z
for (var cn in configNodes) {
if (configNodes.hasOwnProperty(cn)) {
if (configNodes[cn].z === n.z && compareNodes(configNodes[cn],n,false)) {
existingConfigNode = configNodes[cn];
node_map[n.id] = configNodes[cn];
break;
if (options.importMap[n.id] !== "copy") {
existingConfigNode = RED.nodes.node(n.id);
if (existingConfigNode) {
if (n.z && existingConfigNode.z !== n.z) {
existingConfigNode = null;
// Check the config nodes on n.z
for (var cn in configNodes) {
if (configNodes.hasOwnProperty(cn)) {
if (configNodes[cn].z === n.z && compareNodes(configNodes[cn],n,false)) {
existingConfigNode = configNodes[cn];
node_map[n.id] = configNodes[cn];
break;
}
}
}
}
}
}
} else {
if (n.z && !workspaces[n.z] && !subflow_map[n.z]) {
n.z = activeWorkspace;
}
}
if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
@@ -1122,6 +1374,9 @@ RED.nodes = (function() {
users:[],
_config:{}
};
if (!n.z) {
delete configNode.z;
}
if (n.hasOwnProperty('d')) {
configNode.d = n.d;
}
@@ -1141,7 +1396,7 @@ RED.nodes = (function() {
}
configNode.label = def.label;
configNode._def = def;
if (createNewIds) {
if (createNewIds || options.importMap[n.id] === "copy") {
configNode.id = getID();
}
node_map[n.id] = configNode;
@@ -1181,8 +1436,8 @@ RED.nodes = (function() {
if (n.hasOwnProperty('g')) {
node.g = n.g;
}
if (createNewIds) {
if (subflow_blacklist[n.z]) {
if (createNewIds || options.importMap[n.id] === "copy") {
if (subflow_denylist[n.z]) {
continue;
} else if (subflow_map[node.z]) {
node.z = subflow_map[node.z].id;
@@ -1229,8 +1484,8 @@ RED.nodes = (function() {
node._config.y = node.y;
} else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
@@ -1264,6 +1519,9 @@ RED.nodes = (function() {
delete node.wires;
delete node.inputLabels;
delete node.outputLabels;
if (!n.z) {
delete node.z;
}
}
var orig = {};
for (var p in n) {
@@ -1324,6 +1582,7 @@ RED.nodes = (function() {
new_nodes.push(node);
} else if (node.type === "group") {
new_groups.push(node);
new_group_set.add(node.id);
}
}
}
@@ -1427,18 +1686,53 @@ RED.nodes = (function() {
delete n.status.wires;
}
}
// Order the groups to ensure they are outer-most to inner-most
var groupDepthMap = {};
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
if (n.g && !new_group_set.has(n.g)) {
delete n.g;
}
n.nodes = n.nodes.map(function(id) {
return node_map[id];
})
// Just in case the group references a node that doesn't exist for some reason
n.nodes = n.nodes.filter(function(v) {
if (v) {
// Repair any nodes that have forgotten they are in this group
if (v.g !== n.id) {
v.g = n.id;
}
}
return !!v
});
if (!n.g) {
groupDepthMap[n.id] = 0;
}
}
var changedDepth;
do {
changedDepth = false;
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
if (n.g) {
if (groupDepthMap[n.id] !== groupDepthMap[n.g] + 1) {
groupDepthMap[n.id] = groupDepthMap[n.g] + 1;
changedDepth = true;
}
}
}
} while(changedDepth);
new_groups.sort(function(A,B) {
return groupDepthMap[A.id] - groupDepthMap[B.id];
});
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
addGroup(n);
}
// Now the nodes have been fully updated, add them.
for (i=0;i<new_nodes.length;i++) {
var node = new_nodes[i];
@@ -1453,24 +1747,47 @@ RED.nodes = (function() {
}
RED.workspaces.refresh();
return [new_nodes,new_links,new_groups,new_workspaces,new_subflows,missingWorkspace];
if (recoveryWorkspace) {
var notification = RED.notify(RED._("clipboard.recoveredNodesNotification",{flowName:RED._("clipboard.recoveredNodes")}),{
type:"warning",
fixed:true,
buttons: [
{text: RED._("common.label.close"), click: function() { notification.close() }}
]
});
}
return {
nodes:new_nodes,
links:new_links,
groups:new_groups,
workspaces:new_workspaces,
subflows:new_subflows,
missingWorkspace: missingWorkspace,
removedNodes: removedNodes
}
}
// TODO: supports filter.z|type
function filterNodes(filter) {
var result = [];
var searchSet = nodes;
var searchSet = null;
var doZFilter = false;
if (filter.hasOwnProperty("z")) {
if (Object.hasOwnProperty("values") && nodeTabMap.hasOwnProperty(filter.z) ) {
searchSet = Object.values(nodeTabMap[filter.z]);
if (nodeTabMap.hasOwnProperty(filter.z)) {
searchSet = Object.keys(nodeTabMap[filter.z]);
} else {
doZFilter = true;
}
}
if (searchSet === null) {
searchSet = Object.keys(nodes);
}
for (var n=0;n<searchSet.length;n++) {
var node = searchSet[n];
var node = nodes[searchSet[n]];
if (filter.hasOwnProperty("type") && node.type !== filter.type) {
continue;
}
@@ -1539,7 +1856,7 @@ RED.nodes = (function() {
}
function clear() {
nodes = [];
nodes = {};
links = [];
nodeTabMap = {};
configNodes = {};
@@ -1557,6 +1874,8 @@ RED.nodes = (function() {
});
defaultWorkspace = null;
initialLoad = null;
workspaces = {};
RED.nodes.dirty(false);
RED.view.redraw(true, true);
RED.palette.refresh();
@@ -1567,7 +1886,7 @@ RED.nodes = (function() {
RED.events.emit("workspace:clear");
// var node_defs = {};
// var nodes = [];
// var nodes = {};
// var configNodes = {};
// var links = [];
// var defaultWorkspace;
@@ -1627,12 +1946,13 @@ RED.nodes = (function() {
if (configNodes.hasOwnProperty(n.id)) {
delete configNodes[n.id];
} else {
nodes.splice(nodes.indexOf(n),1);
delete nodes[n.id];
if (nodeTabMap[n.z]) {
delete nodeTabMap[n.z][n.id];
}
}
reimportList.push(convertNode(n));
RED.events.emit('nodes:remove',n);
});
// Remove any links between nodes that are going to be reimported.
@@ -1648,9 +1968,9 @@ RED.nodes = (function() {
// Force the redraw to be synchronous so the view updates
// *now* and removes the unknown node
RED.view.redraw(true, true);
var result = importNodes(reimportList,false);
var result = importNodes(reimportList,{generateIds:false});
var newNodeMap = {};
result[0].forEach(function(n) {
result.nodes.forEach(function(n) {
newNodeMap[n.id] = n;
});
RED.nodes.eachLink(function(l) {
@@ -1707,9 +2027,11 @@ RED.nodes = (function() {
groups: function(z) { return groupsByZ[z]||[] },
eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) {
if (cb(nodes[n]) === false) {
break;
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
if (cb(nodes[id]) === false) {
break;
}
}
}
},
@@ -1762,6 +2084,8 @@ RED.nodes = (function() {
import: importNodes,
identifyImportConflicts: identifyImportConflicts,
getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet,

View File

@@ -37,5 +37,21 @@
}
return result;
}
if (new Set([0]).size === 0) {
// IE does not support passing an iterable to Set constructor
var _Set = Set;
/*global Set:true */
Set = function Set(iterable) {
var set = new _Set();
if (iterable) {
iterable.forEach(set.add, set);
}
return set;
};
Set.prototype = _Set.prototype;
Set.prototype.constructor = Set;
}
}
})();

View File

@@ -178,11 +178,21 @@ var RED = (function() {
var currentHash = window.location.hash;
RED.nodes.version(nodes.rev);
loader.reportProgress(RED._("event.importFlows"),90 )
RED.nodes.import(nodes.flows);
RED.nodes.dirty(false);
RED.view.redraw(true);
if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6));
try {
RED.nodes.import(nodes.flows);
RED.nodes.dirty(false);
RED.view.redraw(true);
if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6));
}
} catch(err) {
RED.notify(
RED._("event.importError", {message: err.message}),
{
fixed: true,
type: 'error'
}
);
}
}
done();
@@ -372,7 +382,7 @@ var RED = (function() {
node.status = msg;
node.dirtyStatus = true;
node.dirty = true;
RED.view.redraw();
RED.view.redrawStatus(node);
}
});
RED.comms.subscribe("notification/node/#",function(topic,msg) {

View File

@@ -21,7 +21,18 @@ RED.actions = (function() {
var result = [];
Object.keys(actions).forEach(function(action) {
var shortcut = RED.keyboard.getShortcut(action);
result.push({id:action,scope:shortcut?shortcut.scope:undefined,key:shortcut?shortcut.key:undefined,user:shortcut?shortcut.user:undefined})
var isUser = false;
if (shortcut) {
isUser = shortcut.user;
} else {
isUser = !!RED.keyboard.getUserShortcut(action);
}
result.push({
id:action,
scope:shortcut?shortcut.scope:undefined,
key:shortcut?shortcut.key:undefined,
user:isUser
})
})
return result;
}

View File

@@ -28,6 +28,8 @@ RED.clipboard = (function() {
var libraryBrowser;
var examplesBrowser;
var pendingImportConfig;
function setupDialogs() {
dialog = $('<div id="red-ui-clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("#red-ui-editor")
@@ -42,14 +44,14 @@ RED.clipboard = (function() {
"ui-widget-overlay": "red-ui-editor-dialog"
},
buttons: [
{
{ // red-ui-clipboard-dialog-cancel
id: "red-ui-clipboard-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
{ // red-ui-clipboard-dialog-download
id: "red-ui-clipboard-dialog-download",
class: "primary",
text: RED._("clipboard.download"),
@@ -64,7 +66,7 @@ RED.clipboard = (function() {
$( this ).dialog( "close" );
}
},
{
{ // red-ui-clipboard-dialog-export
id: "red-ui-clipboard-dialog-export",
class: "primary",
text: RED._("clipboard.export.copy"),
@@ -134,14 +136,14 @@ RED.clipboard = (function() {
}
}
},
{
{ // red-ui-clipboard-dialog-ok
id: "red-ui-clipboard-dialog-ok",
class: "primary",
text: RED._("common.label.import"),
click: function() {
var addNewFlow = ($("#red-ui-clipboard-dialog-import-opt > a.selected").attr('id') === 'red-ui-clipboard-dialog-import-opt-new');
if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") {
RED.view.importNodes($("#red-ui-clipboard-dialog-import-text").val(),addNewFlow);
importNodes($("#red-ui-clipboard-dialog-import-text").val(),addNewFlow);
} else {
var selectedPath;
if (activeTab === "red-ui-clipboard-dialog-import-tab-library") {
@@ -151,15 +153,51 @@ RED.clipboard = (function() {
}
if (selectedPath.path) {
$.get('library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path, function(data) {
RED.view.importNodes(data,addNewFlow);
importNodes(data,addNewFlow);
});
}
}
$( this ).dialog( "close" );
}
},
{ // red-ui-clipboard-dialog-import-conflict
id: "red-ui-clipboard-dialog-import-conflict",
class: "primary",
text: RED._("clipboard.import.importSelected"),
click: function() {
var importMap = {};
$('#red-ui-clipboard-dialog-import-conflicts-list input[type="checkbox"]').each(function() {
importMap[$(this).attr("data-node-id")] = this.checked?"import":"skip";
})
$('.red-ui-clipboard-dialog-import-conflicts-controls input[type="checkbox"]').each(function() {
if (!$(this).attr("disabled")) {
importMap[$(this).attr("data-node-id")] = this.checked?"replace":"copy"
}
})
// skip - don't import
// import - import as-is
// copy - import with new id
// replace - import over the top of existing
pendingImportConfig.importOptions.importMap = importMap;
var newNodes = pendingImportConfig.importNodes.filter(function(n) {
if (!importMap[n.id] || importMap[n.z]) {
importMap[n.id] = importMap[n.z];
}
return importMap[n.id] !== "skip"
})
// console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}}))
RED.view.importNodes(newNodes, pendingImportConfig.importOptions);
$( this ).dialog( "close" );
}
}
],
open: function( event, ui ) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
if (popover) {
popover.close(true);
currentPopoverError = null;
@@ -232,6 +270,14 @@ RED.clipboard = (function() {
'</span>'+
'</div>';
importConflictsDialog =
'<div class="form-row">'+
'<div class="form-row"><p data-i18n="clipboard.import.conflictNotification1"></p><p data-i18n="clipboard.import.conflictNotification2"></p></div>'+
'<div class="red-ui-clipboard-dialog-import-conflicts-list-container">'+
'<div id="red-ui-clipboard-dialog-import-conflicts-list"></div>'+
'</div>'+
'</div>';
}
var validateExportFilenameTimeout
@@ -355,7 +401,7 @@ RED.clipboard = (function() {
}
}
function importNodes(mode) {
function showImportNodes(mode) {
if (disabled) {
return;
}
@@ -441,6 +487,8 @@ RED.clipboard = (function() {
$("#red-ui-clipboard-dialog-cancel").show();
$("#red-ui-clipboard-dialog-export").hide();
$("#red-ui-clipboard-dialog-download").hide();
$("#red-ui-clipboard-dialog-import-conflict").hide();
$("#red-ui-clipboard-dialog-ok").button("disable");
$("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
$("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
@@ -481,7 +529,9 @@ RED.clipboard = (function() {
}
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
dialog.dialog("option","title",RED._("clipboard.importNodes"))
.dialog("option","width",700)
.dialog("open");
popover = RED.popover.create({
target: $("#red-ui-clipboard-dialog-import-text"),
trigger: "manual",
@@ -490,7 +540,7 @@ RED.clipboard = (function() {
});
}
function exportNodes(mode) {
function showExportNodes(mode) {
if (disabled) {
return;
}
@@ -627,6 +677,8 @@ RED.clipboard = (function() {
$("#red-ui-clipboard-dialog-ok").hide();
$("#red-ui-clipboard-dialog-cancel").hide();
$("#red-ui-clipboard-dialog-export").hide();
$("#red-ui-clipboard-dialog-import-conflict").hide();
var selection = RED.workspaces.selection();
if (selection.length > 0) {
$("#red-ui-clipboard-dialog-export-rng-selected").trigger("click");
@@ -653,12 +705,15 @@ RED.clipboard = (function() {
}
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
dialog.dialog("option","title",RED._("clipboard.exportNodes"))
.dialog("option","width",700)
.dialog("open");
$("#red-ui-clipboard-dialog-export-text").trigger("focus");
$("#red-ui-clipboard-dialog-cancel").show();
$("#red-ui-clipboard-dialog-export").show();
$("#red-ui-clipboard-dialog-download").show();
$("#red-ui-clipboard-dialog-import-conflict").hide();
}
@@ -718,6 +773,12 @@ RED.clipboard = (function() {
// representation or null
return null;
}
if (value.type === 'bigint') {
return value.data.toString();
}
if (value.type === 'undefined') {
return undefined;
}
}
}
return value;
@@ -742,19 +803,315 @@ RED.clipboard = (function() {
}
return result;
}
function importNodes(nodesStr,addFlow) {
var newNodes = nodesStr;
if (typeof nodesStr === 'string') {
try {
nodesStr = nodesStr.trim();
if (nodesStr.length === 0) {
return;
}
newNodes = JSON.parse(nodesStr);
} catch(err) {
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
e.code = "NODE_RED";
throw e;
}
}
var importOptions = {generateIds: false, addFlow: addFlow};
try {
RED.view.importNodes(newNodes, importOptions);
} catch(error) {
// Thrown for import_conflict
confirmImport(error.importConfig, newNodes, importOptions);
}
}
function confirmImport(importConfig,importNodes,importOptions) {
var notification = RED.notify("<p>"+RED._("clipboard.import.conflictNotification1")+"</p>",{
type: "info",
fixed: true,
buttons: [
{text: RED._("common.label.cancel"), click: function() { notification.close(); }},
{text: RED._("clipboard.import.viewNodes"), click: function() {
notification.close();
showImportConflicts(importConfig,importNodes,importOptions);
}},
{text: RED._("clipboard.import.importCopy"), click: function() {
notification.close();
// generateIds=true to avoid conflicts
// and default to the 'old' behaviour around matching
// config nodes and subflows
importOptions.generateIds = true;
RED.view.importNodes(importNodes, importOptions);
}}
]
})
}
function showImportConflicts(importConfig,importNodes,importOptions) {
pendingImportConfig = {
importConfig: importConfig,
importNodes: importNodes,
importOptions: importOptions
}
var id,node;
var treeData = [];
var container;
var addedHeader = false;
for (id in importConfig.subflows) {
if (importConfig.subflows.hasOwnProperty(id)) {
if (!addedHeader) {
treeData.push({gutter:$('<span data-i18n="menu.label.subflows"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
addedHeader = true;
}
node = importConfig.subflows[id];
var isConflicted = importConfig.conflicted[node.id];
var isSelected = !isConflicted;
var elements = getNodeElement(node, isConflicted, isSelected );
container = {
id: node.id,
gutter: elements.gutter.element,
element: elements.element,
class: isSelected?"":"disabled",
deferBuild: true,
children: []
}
treeData.push(container);
if (importConfig.zMap[id]) {
importConfig.zMap[id].forEach(function(node) {
var childElements = getNodeElement(node, importConfig.conflicted[node.id], isSelected, elements.gutter.cb);
container.children.push({
id: node.id,
gutter: childElements.gutter.element,
element: childElements.element,
class: isSelected?"":"disabled"
})
});
}
}
}
addedHeader = false;
for (id in importConfig.tabs) {
if (importConfig.tabs.hasOwnProperty(id)) {
if (!addedHeader) {
treeData.push({gutter:$('<span data-i18n="menu.label.flows"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
addedHeader = true;
}
node = importConfig.tabs[id];
var isConflicted = importConfig.conflicted[node.id];
var isSelected = true;
var elements = getNodeElement(node, isConflicted, isSelected);
container = {
id: node.id,
gutter: elements.gutter.element,
element: elements.element,
icon: "red-ui-icons red-ui-icons-flow",
deferBuild: true,
class: isSelected?"":"disabled",
children: []
}
treeData.push(container);
if (importConfig.zMap[id]) {
importConfig.zMap[id].forEach(function(node) {
var childElements = getNodeElement(node, importConfig.conflicted[node.id], isSelected, elements.gutter.cb);
container.children.push({
id: node.id,
gutter: childElements.gutter.element,
element: childElements.element,
class: isSelected?"":"disabled"
})
// console.log(" ["+(importConfig.conflicted[node.id]?"*":" ")+"] "+node.type+" "+node.id);
});
}
}
}
addedHeader = false;
var extraNodes = [];
importConfig.all.forEach(function(node) {
if (node.type !== "tab" && node.type !== "subflow" && !importConfig.tabs[node.z] && !importConfig.subflows[node.z]) {
var isConflicted = importConfig.conflicted[node.id];
var isSelected = !isConflicted || !importConfig.configs[node.id];
var elements = getNodeElement(node, isConflicted, isSelected);
var item = {
id: node.id,
gutter: elements.gutter.element,
element: elements.element,
class: isSelected?"":"disabled"
}
if (importConfig.configs[node.id]) {
extraNodes.push(item);
} else {
if (!addedHeader) {
treeData.push({gutter:$('<span data-i18n="menu.label.nodes"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
addedHeader = true;
}
treeData.push(item);
}
// console.log("["+(importConfig.conflicted[node.id]?"*":" ")+"] "+node.type+" "+node.id);
}
})
if (extraNodes.length > 0) {
treeData.push({gutter:$('<span data-i18n="menu.label.displayConfig"></span>'), label: '', class:"red-ui-clipboard-dialog-import-conflicts-item-header"})
addedHeader = true;
treeData = treeData.concat(extraNodes);
}
dialogContainer.empty();
dialogContainer.append($(importConflictsDialog));
var nodeList = $("#red-ui-clipboard-dialog-import-conflicts-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
data: treeData
})
dialogContainer.i18n();
var dialogHeight = 400;
var winHeight = $(window).height();
if (winHeight < 600) {
dialogHeight = 400 - (600 - winHeight);
}
$(".red-ui-clipboard-dialog-box").height(dialogHeight);
$("#red-ui-clipboard-dialog-ok").hide();
$("#red-ui-clipboard-dialog-cancel").show();
$("#red-ui-clipboard-dialog-export").hide();
$("#red-ui-clipboard-dialog-download").hide();
$("#red-ui-clipboard-dialog-import-conflict").show();
dialog.dialog("option","title",RED._("clipboard.importNodes"))
.dialog("option","width",500)
.dialog( "open" );
}
function getNodeElement(n, isConflicted, isSelected, parent) {
var element;
if (n.type === "tab") {
element = getFlowLabel(n, isSelected);
} else {
element = getNodeLabel(n, isConflicted, isSelected);
}
var controls = $('<div>',{class:"red-ui-clipboard-dialog-import-conflicts-controls"}).appendTo(element);
controls.on("click", function(evt) { evt.stopPropagation(); });
if (isConflicted && !parent) {
var cb = $('<label><input '+(isSelected?'':'disabled ')+'type="checkbox" data-node-id="'+n.id+'"> <span data-i18n="clipboard.import.replace"></span></label>').appendTo(controls);
if (n.type === "tab" || (n.type !== "subflow" && n.hasOwnProperty("x") && n.hasOwnProperty("y"))) {
cb.hide();
}
}
return {
element: element,
gutter: getGutter(n, isSelected, parent)
}
}
function getGutter(n, isSelected, parent) {
var span = $("<label>",{class:"red-ui-clipboard-dialog-import-conflicts-gutter"});
var cb = $('<input data-node-id="'+n.id+'" type="checkbox" '+(isSelected?"checked":"")+'>').appendTo(span);
if (parent) {
cb.attr("disabled",true);
parent.addChild(cb);
}
span.on("click", function(evt) {
evt.stopPropagation();
})
cb.on("change", function(evt) {
var state = this.checked;
span.parent().toggleClass("disabled",!!!state);
span.parent().find('.red-ui-clipboard-dialog-import-conflicts-controls input[type="checkbox"]').attr("disabled",!!!state);
childItems.forEach(function(c) {
c.attr("checked",state);
c.trigger("change");
});
})
var childItems = [];
var checkbox = {
addChild: function(c) {
childItems.push(c);
}
}
return {
cb: checkbox,
element: span
}
}
function getNodeLabelText(n) {
var label = n.name || n.type+": "+n.id;
if (n._def.label) {
try {
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
} catch(err) {
console.log("Definition error: "+n.type+".label",err);
}
}
var newlineIndex = label.indexOf("\\n");
if (newlineIndex > -1) {
label = label.substring(0,newlineIndex)+"...";
}
return label;
}
function getFlowLabel(n) {
n = JSON.parse(JSON.stringify(n));
n._def = RED.nodes.getType(n.type) || {};
if (n._def) {
n._ = n._def._;
}
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
var label = (typeof n === "string")? n : n.label;
var newlineIndex = label.indexOf("\\n");
if (newlineIndex > -1) {
label = label.substring(0,newlineIndex)+"...";
}
contentDiv.text(label);
// A conflicted flow should not be imported by default.
return div;
}
function getNodeLabel(n, isConflicted) {
n = JSON.parse(JSON.stringify(n));
n._def = RED.nodes.getType(n.type) || {};
if (n._def) {
n._ = n._def._;
}
var div = $('<div>',{class:"red-ui-info-outline-item"});
RED.utils.createNodeIcon(n).appendTo(div);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var labelText = getNodeLabelText(n);
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
if (labelText) {
label.text(labelText)
} else {
label.html(n.type)
}
return div;
}
return {
init: function() {
setupDialogs();
$('<input type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
RED.actions.add("core:show-export-dialog",exportNodes);
RED.actions.add("core:show-import-dialog",importNodes);
RED.actions.add("core:show-export-dialog",showExportNodes);
RED.actions.add("core:show-import-dialog",showImportNodes);
RED.actions.add("core:show-library-export-dialog",function() { exportNodes('library') });
RED.actions.add("core:show-library-import-dialog",function() { importNodes('library') });
RED.actions.add("core:show-library-export-dialog",function() { showExportNodes('library') });
RED.actions.add("core:show-library-import-dialog",function() { showImportNodes('library') });
RED.actions.add("core:show-examples-import-dialog",function() { importNodes('examples') });
RED.actions.add("core:show-examples-import-dialog",function() { showImportNodes('examples') });
RED.events.on("editor:open",function() { disabled = true; });
RED.events.on("editor:close",function() { disabled = false; });
@@ -788,7 +1145,7 @@ RED.clipboard = (function() {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
var data = event.originalEvent.dataTransfer.getData("text/plain");
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
RED.view.importNodes(data);
importNodes(data);
} else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
var files = event.originalEvent.dataTransfer.files;
if (files.length === 1) {
@@ -796,7 +1153,7 @@ RED.clipboard = (function() {
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
RED.view.importNodes(e.target.result);
importNodes(e.target.result);
};
})(file);
reader.readAsText(file);
@@ -807,8 +1164,8 @@ RED.clipboard = (function() {
});
},
import: importNodes,
export: exportNodes,
import: showImportNodes,
export: showExportNodes,
copyText: copyText
}
})();

View File

@@ -27,26 +27,26 @@
this.partialFlag = false;
this.stateValue = 0;
var initialState = this.element.prop('checked');
this.options = [
this.states = [
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-square-o"></i></span>').appendTo(this.uiElement),
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-check-square-o"></i></span>').appendTo(this.uiElement),
$('<span class="red-ui-checkboxSet-option hide"><i class="fa fa-minus-square-o"></i></span>').appendTo(this.uiElement)
];
if (initialState) {
this.options[1].show();
this.states[1].show();
} else {
this.options[0].show();
this.states[0].show();
}
this.element.on("change", function() {
if (this.checked) {
that.options[0].hide();
that.options[1].show();
that.options[2].hide();
that.states[0].hide();
that.states[1].show();
that.states[2].hide();
} else {
that.options[1].hide();
that.options[0].show();
that.options[2].hide();
that.states[1].hide();
that.states[0].show();
that.states[2].hide();
}
var isChecked = this.checked;
that.children.forEach(function(child) {
@@ -106,17 +106,17 @@
var trueState = this.partialFlag||state;
this.element.prop('checked',trueState);
if (state === true) {
this.options[0].hide();
this.options[1].show();
this.options[2].hide();
this.states[0].hide();
this.states[1].show();
this.states[2].hide();
} else if (state === false) {
this.options[2].hide();
this.options[1].hide();
this.options[0].show();
this.states[2].hide();
this.states[1].hide();
this.states[0].show();
} else if (state === null) {
this.options[0].hide();
this.options[1].hide();
this.options[2].show();
this.states[0].hide();
this.states[1].hide();
this.states[2].show();
}
if (!suppressEvent) {
this.element.trigger('change',null);

View File

@@ -91,6 +91,9 @@
if (v!=="auto" && v!=="") {
that.topContainer.css(s,v);
that.uiContainer.css(s,"0");
if (s === "top" && that.options.header) {
that.uiContainer.css(s,"20px")
}
that.element.css(s,'auto');
}
})

View File

@@ -84,6 +84,7 @@ RED.popover = (function() {
var targetHeight = target.outerHeight();
var divHeight = div.height();
var divWidth = div.width();
var paddingRight = 10;
var viewportTop = $(window).scrollTop();
var viewportLeft = $(window).scrollLeft();
@@ -105,7 +106,7 @@ RED.popover = (function() {
d = "right";
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left+targetWidth+deltaSizes[size].leftRight;
} else if (left+divWidth > viewportRight) {
} else if (left+divWidth+paddingRight > viewportRight) {
d = "left";
top = targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top;
left = targetPos.left-deltaSizes[size].leftLeft-divWidth;

View File

@@ -119,6 +119,9 @@
if (evt.keyCode === 27) {
that.element.val("");
}
if (evt.keyCode === 13) {
evt.preventDefault();
}
})
this.element.on("keyup",function(evt) {
that._change($(this).val());

View File

@@ -29,7 +29,7 @@ RED.tabs = (function() {
var currentTabWidth;
var currentActiveTabWidth = 0;
var collapsibleMenu;
var preferredOrder = options.order;
var ul = options.element || $("#"+options.id);
var wrapper = ul.wrap( "<div>" ).parent();
var scrollContainer = ul.wrap( "<div>" ).parent();
@@ -132,11 +132,11 @@ RED.tabs = (function() {
activateTab(id);
}
};
if (tabs[id].pinned) {
pinnedOptions.push(opt);
} else {
// if (tabs[id].pinned) {
// pinnedOptions.push(opt);
// } else {
options.push(opt);
}
// }
});
options = pinnedOptions.concat(options);
collapsibleMenu = RED.menu.init({options: options});
@@ -363,23 +363,39 @@ RED.tabs = (function() {
var tabWidth;
if (options.collapsible) {
var availableCount = collapsedButtonsRow.children().length;
var visibleCount = collapsedButtonsRow.children(":visible").length;
tabWidth = width - collapsedButtonsRow.width()-10;
if (tabWidth < 198) {
var delta = 198 - tabWidth;
var maxTabWidth = 198;
var minTabWidth = 80;
if (tabWidth <= minTabWidth || (tabWidth < maxTabWidth && visibleCount > 5)) {
// The tab is too small. Hide the next button to make room
// Start at the end of the button row, -1 for the menu button
var b = collapsedButtonsRow.find("a:last").prev();
var index = collapsedButtonsRow.children().length - 2;
// Work backwards to find the first visible button
while (b.is(":not(:visible)")) {
b = b.prev();
index--;
}
if (!b.hasClass("red-ui-tab-link-button-pinned")) {
// If it isn't a pinned button, hide it to get the room
if (tabWidth <= minTabWidth || visibleCount>6) {//}!b.hasClass("red-ui-tab-link-button-pinned")) {
b.hide();
}
tabWidth = width - collapsedButtonsRow.width()-10;
tabWidth = Math.max(minTabWidth,width - collapsedButtonsRow.width()-10);
} else {
var space = width - 198 - collapsedButtonsRow.width();
if (visibleCount !== availableCount) {
if (visibleCount < 6) {
tabWidth = minTabWidth;
} else {
tabWidth = maxTabWidth;
}
}
var space = width - tabWidth - collapsedButtonsRow.width();
if (space > 40) {
collapsedButtonsRow.find("a:not(:visible):first").show();
tabWidth = width - collapsedButtonsRow.width()-10;
}
tabWidth = width - collapsedButtonsRow.width()-10;
}
tabs.css({width:tabWidth});
@@ -469,7 +485,7 @@ RED.tabs = (function() {
}
}
return {
var tabAPI = {
addTab: function(tab,targetIndex) {
if (options.onselect) {
var selection = ul.find("li.red-ui-tab.selected");
@@ -531,11 +547,93 @@ RED.tabs = (function() {
evt.preventDefault();
activateTab(tab.id);
});
pinnedLink.data("tabId",tab.id)
if (tab.pinned) {
pinnedLink.addClass("red-ui-tab-link-button-pinned");
pinnedTabsCount++;
}
RED.popover.tooltip($(pinnedLink), tab.name, tab.action);
if (options.onreorder) {
var pinnedLinkIndex;
var pinnedLinks = [];
var startPinnedIndex;
pinnedLink.draggable({
distance: 10,
axis:"x",
containment: ".red-ui-tab-link-buttons",
start: function(event,ui) {
dragActive = true;
$(".red-ui-tab-link-buttons").width($(".red-ui-tab-link-buttons").width());
if (dblClickArmed) { dblClickArmed = false; return false }
collapsedButtonsRow.children().each(function(i) {
pinnedLinks[i] = {
el:$(this),
text: $(this).text(),
left: $(this).position().left,
width: $(this).width(),
menu: $(this).hasClass("red-ui-tab-link-button-menu")
};
if ($(this).is(pinnedLink)) {
pinnedLinkIndex = i;
startPinnedIndex = i;
}
});
collapsedButtonsRow.children().each(function(i) {
if (i!==pinnedLinkIndex) {
$(this).css({
position: 'absolute',
left: pinnedLinks[i].left+"px",
width: pinnedLinks[i].width+2,
transition: "left 0.3s"
});
}
})
if (!pinnedLink.hasClass('active')) {
pinnedLink.css({'zIndex':1});
}
},
drag: function(event,ui) {
ui.position.left += pinnedLinks[pinnedLinkIndex].left;
var tabCenter = ui.position.left + pinnedLinks[pinnedLinkIndex].width/2;
for (var i=0;i<pinnedLinks.length;i++) {
if (i === pinnedLinkIndex || pinnedLinks[i].menu || pinnedLinks[i].el.is(":not(:visible)")) {
continue;
}
if (tabCenter > pinnedLinks[i].left && tabCenter < pinnedLinks[i].left+pinnedLinks[i].width) {
if (i < pinnedLinkIndex) {
pinnedLinks[i].left += pinnedLinks[pinnedLinkIndex].width+8;
pinnedLinks[pinnedLinkIndex].el.detach().insertBefore(pinnedLinks[i].el);
} else {
pinnedLinks[i].left -= pinnedLinks[pinnedLinkIndex].width+8;
pinnedLinks[pinnedLinkIndex].el.detach().insertAfter(pinnedLinks[i].el);
}
pinnedLinks[i].el.css({left:pinnedLinks[i].left+"px"});
pinnedLinks.splice(i, 0, pinnedLinks.splice(pinnedLinkIndex, 1)[0]);
pinnedLinkIndex = i;
break;
}
}
},
stop: function(event,ui) {
dragActive = false;
collapsedButtonsRow.children().css({position:"relative",left:"",transition:""});
$(".red-ui-tab-link-buttons").width('auto');
pinnedLink.css({zIndex:""});
updateTabWidths();
if (startPinnedIndex !== pinnedLinkIndex) {
if (collapsibleMenu) {
collapsibleMenu.remove();
collapsibleMenu = null;
}
var newOrder = $.makeArray(collapsedButtonsRow.children().map(function() { return $(this).data('tabId');}));
tabAPI.order(newOrder);
options.onreorder(newOrder);
}
}
});
}
}
link.on("mouseup",onTabClick);
@@ -565,7 +663,7 @@ RED.tabs = (function() {
if (ul.find("li.red-ui-tab").length == 1) {
activateTab(link);
}
if (options.onreorder) {
if (options.onreorder && !options.collapsible) {
var originalTabOrder;
var tabDragIndex;
var tabElements = [];
@@ -652,6 +750,9 @@ RED.tabs = (function() {
collapsibleMenu.remove();
collapsibleMenu = null;
}
if (preferredOrder) {
tabAPI.order(preferredOrder);
}
},
removeTab: removeTab,
activateTab: activateTab,
@@ -673,10 +774,8 @@ RED.tabs = (function() {
},
selection: getSelection,
order: function(order) {
preferredOrder = 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++) {
@@ -692,12 +791,41 @@ RED.tabs = (function() {
var existingTabs = ul.children().detach().each(function() {
existingTabMap[$(this).data("tabId")] = $(this);
});
var pinnedButtons = {};
if (options.collapsible) {
collapsedButtonsRow.children().detach().each(function() {
var id = $(this).data("tabId");
if (!id) {
id = "__menu__"
}
pinnedButtons[id] = $(this);
});
}
for (i=0;i<order.length;i++) {
existingTabMap[order[i]].appendTo(ul);
if (existingTabMap[order[i]]) {
existingTabMap[order[i]].appendTo(ul);
if (options.collapsible) {
pinnedButtons[order[i]].appendTo(collapsedButtonsRow);
}
delete existingTabMap[order[i]];
}
}
// Add any tabs that aren't known in the order
for (i in existingTabMap) {
if (existingTabMap.hasOwnProperty(i)) {
existingTabMap[i].appendTo(ul);
if (options.collapsible) {
pinnedButtons[i].appendTo(collapsedButtonsRow);
}
}
}
if (options.collapsible) {
pinnedButtons["__menu__"].appendTo(collapsedButtonsRow);
updateTabWidths();
}
}
}
return tabAPI;
}
return {

View File

@@ -37,8 +37,8 @@
invertState = this.options.invertState;
}
var baseClass = this.options.baseClass || "red-ui-button";
var enabledIcon = this.options.enabledIcon || "fa-check-square-o";
var disabledIcon = this.options.disabledIcon || "fa-square-o";
var enabledIcon = this.options.hasOwnProperty('enabledIcon')?this.options.enabledIcon : "fa-check-square-o";
var disabledIcon = this.options.hasOwnProperty('disabledIcon')?this.options.disabledIcon : "fa-square-o";
var enabledLabel = this.options.hasOwnProperty('enabledLabel') ? this.options.enabledLabel : RED._("editor:workspace.enabled");
var disabledLabel = this.options.hasOwnProperty('disabledLabel') ? this.options.disabledLabel : RED._("editor:workspace.disabled");
@@ -46,25 +46,41 @@
this.element.on("focus", function() {
that.button.focus();
});
this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"><i class="fa"></i> <span></span></button>');
this.button = $('<button type="button" class="red-ui-toggleButton '+baseClass+' toggle single"></button>');
if (enabledLabel || disabledLabel) {
this.buttonLabel = $("<span>").appendTo(this.button);
}
if (this.options.class) {
this.button.addClass(this.options.class)
}
this.element.after(this.button);
this.buttonIcon = this.button.find("i");
this.buttonLabel = this.button.find("span");
if (enabledIcon && disabledIcon) {
this.buttonIcon = $('<i class="fa"></i>').prependTo(this.button);
}
// Quick hack to find the maximum width of the button
this.button.addClass("selected");
this.buttonIcon.addClass(enabledIcon);
this.buttonLabel.text(enabledLabel);
if (this.buttonIcon) {
this.buttonIcon.addClass(enabledIcon);
}
if (this.buttonLabel) {
this.buttonLabel.text(enabledLabel);
}
var width = this.button.width();
this.button.removeClass("selected");
this.buttonIcon.removeClass(enabledIcon);
that.buttonIcon.addClass(disabledIcon);
that.buttonLabel.text(disabledLabel);
if (this.buttonIcon) {
this.buttonIcon.removeClass(enabledIcon);
that.buttonIcon.addClass(disabledIcon);
}
if (this.buttonLabel) {
that.buttonLabel.text(disabledLabel);
}
width = Math.max(width,this.button.width());
this.buttonIcon.removeClass(disabledIcon);
if (this.buttonIcon) {
this.buttonIcon.removeClass(disabledIcon);
}
// Fix the width of the button so it doesn't jump around when toggled
if (width > 0) {
@@ -73,7 +89,7 @@
this.button.on("click",function(e) {
e.stopPropagation();
if (that.buttonIcon.hasClass(disabledIcon)) {
if (!that.state) {
that.element.prop("checked",!invertState);
} else {
that.element.prop("checked",invertState);
@@ -84,14 +100,24 @@
this.element.on("change", function(e) {
if ($(this).prop("checked") !== invertState) {
that.button.addClass("selected");
that.buttonIcon.addClass(enabledIcon);
that.buttonIcon.removeClass(disabledIcon);
that.buttonLabel.text(enabledLabel);
that.state = true;
if (that.buttonIcon) {
that.buttonIcon.addClass(enabledIcon);
that.buttonIcon.removeClass(disabledIcon);
}
if (that.buttonLabel) {
that.buttonLabel.text(enabledLabel);
}
} else {
that.button.removeClass("selected");
that.buttonIcon.addClass(disabledIcon);
that.buttonIcon.removeClass(enabledIcon);
that.buttonLabel.text(disabledLabel);
that.state = false;
if (that.buttonIcon) {
that.buttonIcon.addClass(disabledIcon);
that.buttonIcon.removeClass(enabledIcon);
}
if (that.buttonLabel) {
that.buttonLabel.text(disabledLabel);
}
}
})
this.element.trigger("change");

View File

@@ -167,7 +167,7 @@
this._selected = new Set();
this._topList = $('<ol class="red-ui-treeList-list">').css({
position:'absolute',
top: 0,
top:0,
left:0,
right:0,
bottom:0
@@ -181,6 +181,9 @@
that._addSubtree(that._topList,container,item,0);
}
};
if (this.options.header) {
topListOptions.header = this.options.header;
}
if (this.options.rootSortable !== false && !!this.options.sortable) {
topListOptions.sortable = this.options.sortable;
topListOptions.connectWith = '.red-ui-treeList-sortable';

View File

@@ -173,6 +173,7 @@
valueLabel: function(container,value) {
var that = this;
container.css("pointer-events","none");
container.css("flex-grow",0);
this.elementDiv.hide();
var buttons = $('<div>').css({
position: "absolute",
@@ -184,22 +185,25 @@
width:"20px"
}).appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
var cursorPosition = that.input[0].selectionStart;
var currentType = that.input.attr("type");
if (currentType === "text") {
that.input.attr("type","password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
setTimeout(function() {
that.input.focus();
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
},50);
} else {
that.input.attr("type","text");
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
setTimeout(function() {
that.input.focus();
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
},50);
}
}).hide();
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-1px").appendTo(eyeButton);
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-2px").appendTo(eyeButton);
if (value === "__PWRD__") {
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
@@ -284,7 +288,7 @@
this.input.css('width','100%');
this.uiSelect.width(m[1]);
this.uiWidth = null;
} else {
} else if (this.uiWidth !== 0){
this.uiSelect.width(this.uiWidth);
}
["Right","Left"].forEach(function(d) {
@@ -304,7 +308,11 @@
this.element.attr('type','hidden');
this.options.types = this.options.types||Object.keys(allOptions);
if (!this.options.types && this.options.type) {
this.options.types = [this.options.type]
} else {
this.options.types = this.options.types||Object.keys(allOptions);
}
this.selectTrigger = $('<button class="red-ui-typedInput-type-select" tabindex="0"></button>').prependTo(this.uiSelect);
$('<i class="red-ui-typedInput-icon fa fa-caret-down"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
@@ -872,6 +880,9 @@
this.elementDiv.hide();
this.valueLabelContainer.hide();
} else if (opt.valueLabel) {
// Reset any CSS the custom label may have set
this.valueLabelContainer.css("pointer-events","");
this.valueLabelContainer.css("flex-grow",1);
this.valueLabelContainer.show();
this.valueLabelContainer.empty();
this.elementDiv.hide();
@@ -954,6 +965,18 @@
},
hide: function() {
this.uiSelect.hide();
},
disable: function(val) {
if(val === true) {
this.uiSelect.attr("disabled", "disabled");
} else if (val === false) {
this.uiSelect.attr("disabled", null); //remove attr
} else {
this.uiSelect.attr("disabled", val); //user value
}
},
disabled: function() {
return this.uiSelect.attr("disabled");
}
});
})(jQuery);

View File

@@ -1411,7 +1411,7 @@ RED.diff = (function() {
// Restore the original flow so subsequent merge resolutions can properly
// identify new-vs-old
RED.nodes.originalFlow(originalFlow);
imported[0].forEach(function(n) {
imported.nodes.forEach(function(n) {
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
n.changed = true;
}

View File

@@ -1630,6 +1630,7 @@ RED.editor = (function() {
show: function() {
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
RED.sidebar.help.show(editing_node.type, false);
}
}
}
@@ -1836,6 +1837,7 @@ RED.editor = (function() {
show: function() {
if (editing_config_node) {
RED.sidebar.info.refresh(editing_config_node);
RED.sidebar.help.show(type, false);
}
}
}
@@ -2722,7 +2724,7 @@ RED.editor = (function() {
if (options.globals) {
setTimeout(function() {
if (!!session.$worker) {
session.$worker.send("setOptions", [{globals: options.globals, esversion:6, sub:true, asi:true, maxerr:1000}]);
session.$worker.send("setOptions", [{globals: options.globals, maxerr:1000}]);
}
},100);
}
@@ -2786,7 +2788,13 @@ RED.editor = (function() {
editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) },
editMarkdown: function(options) { showTypeEditor("_markdown", options) },
editText: function(options) { showTypeEditor("_text", options) },
editText: function(options) {
if (options.mode == "markdown") {
showTypeEditor("_markdown", options)
} else {
showTypeEditor("_text", options)
}
},
editBuffer: function(options) { showTypeEditor("_buffer", options) },
buildEditForm: buildEditForm,
validateNode: validateNode,

View File

@@ -83,7 +83,8 @@ RED.group = (function() {
}
var defaultGroupStyle = {
label: true
label: true,
"label-position": "nw"
};
var groupDef = {
@@ -97,25 +98,25 @@ RED.group = (function() {
var style = this.style || {};
RED.colorPicker.create({
id:"node-input-style-stroke",
value: style.stroke || "#a4a4a4",
value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['stroke-opacity'] || 1.0
opacity: style.hasOwnProperty('stroke-opacity')?style['stroke-opacity']:(defaultGroupStyle.hasOwnProperty('stroke-opacity')?defaultGroupStyle['stroke-opacity']:1.0)
}).appendTo("#node-input-row-style-stroke");
RED.colorPicker.create({
id:"node-input-style-fill",
value: style.fill || "none",
value: style.fill || defaultGroupStyle.fill ||"none",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['fill-opacity'] || 1.0
opacity: style.hasOwnProperty('fill-opacity')?style['fill-opacity']:(defaultGroupStyle.hasOwnProperty('fill-opacity')?defaultGroupStyle['fill-opacity']:1.0)
}).appendTo("#node-input-row-style-fill");
createLayoutPicker({
@@ -125,7 +126,7 @@ RED.group = (function() {
RED.colorPicker.create({
id:"node-input-style-color",
value: style.color || "#a4a4a4",
value: style.color || defaultGroupStyle.color ||"#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
@@ -161,12 +162,13 @@ RED.group = (function() {
delete this.style.color;
}
if (this.style["stroke-opacity"] === "1") {
delete this.style["stroke-opacity"]
}
if (this.style["fill-opacity"] === "1") {
delete this.style["fill-opacity"]
}
var node = this;
['stroke','fill','stroke-opacity','fill-opacity','color','label-position'].forEach(function(prop) {
if (node.style[prop] === defaultGroupStyle[prop]) {
delete node.style[prop]
}
})
this.resize = true;
},
set:{
@@ -219,9 +221,17 @@ RED.group = (function() {
"stroke-opacity": groupStyle.strokeOpacity,
fill: convertColorToHex(groupStyle.fill),
"fill-opacity": groupStyle.fillOpacity,
label: true
label: true,
"label-position": "nw"
}
groupStyleDiv.remove();
groupStyleDiv = $("<div>",{
class:"red-ui-flow-group-label",
style: "position: absolute; top: -1000px;"
}).appendTo(document.body);
groupStyle = getComputedStyle(groupStyleDiv[0]);
defaultGroupStyle.color = convertColorToHex(groupStyle.fill);
groupStyleDiv.remove();
}
function convertColorToHex(c) {
@@ -237,6 +247,7 @@ RED.group = (function() {
var groupStyleClipboard;
function copyGroupStyle() {
if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection();
if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].type === 'group') {
groupStyleClipboard = JSON.parse(JSON.stringify(selection.nodes[0].style));
@@ -244,6 +255,7 @@ RED.group = (function() {
}
}
function pasteGroupStyle() {
if (RED.view.state() !== RED.state.DEFAULT) { return }
if (groupStyleClipboard) {
var selection = RED.view.selection();
if (selection.nodes) {
@@ -277,6 +289,7 @@ RED.group = (function() {
}
function groupSelection() {
if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection();
if (selection.nodes) {
var group = createGroup(selection.nodes);
@@ -293,6 +306,7 @@ RED.group = (function() {
}
}
function ungroupSelection() {
if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection();
if (selection.nodes) {
var newSelection = [];
@@ -341,8 +355,7 @@ RED.group = (function() {
}
function mergeSelection() {
// TODO: this currently creates an entirely new group. Need to merge properties
// of any existing group
if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
@@ -370,11 +383,16 @@ RED.group = (function() {
return;
}
}
var existingGroup;
// Second pass, ungroup any groups in the selection and add their contents
// to the selection
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (n.type === "group") {
if (!existingGroup) {
existingGroup = n;
}
ungroupHistoryEvent.groups.push(n);
nodes = nodes.concat(ungroup(n));
} else {
@@ -388,6 +406,10 @@ RED.group = (function() {
// Finally, create the new group
var group = createGroup(nodes);
if (group) {
if (existingGroup) {
group.style = existingGroup.style;
group.name = existingGroup.name;
}
RED.view.select({nodes:[group]})
}
historyEvent.events.push({
@@ -401,6 +423,7 @@ RED.group = (function() {
}
function removeSelection() {
if (RED.view.state() !== RED.state.DEFAULT) { return }
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];

View File

@@ -17,6 +17,8 @@ RED.keyboard = (function() {
var isMac = /Mac/i.test(window.navigator.platform);
var handlersActive = true;
var handlers = {};
var partialState;
@@ -68,6 +70,11 @@ RED.keyboard = (function() {
}
}
}
function getUserKey(action) {
var currentEditorSettings = RED.settings.get('editor') || {};
var userKeymap = currentEditorSettings.keymap || {};
return userKeymap[action];
}
function init() {
// Migrate from pre-0.18
migrateOldKeymap();
@@ -225,6 +232,9 @@ RED.keyboard = (function() {
}
}
d3.select(window).on("keydown",function() {
if (!handlersActive) {
return;
}
if (metaKeyCodes[d3.event.keyCode]) {
return;
}
@@ -250,6 +260,19 @@ RED.keyboard = (function() {
var i=0;
if (typeof key === 'string') {
if (typeof cbdown === 'string') {
if (!ondown && !defaultKeyMap.hasOwnProperty(cbdown)) {
defaultKeyMap[cbdown] = {
scope:scope,
key:key,
user:false
}
}
if (!ondown) {
var userAction = getUserKey(cbdown);
if (userAction) {
return;
}
}
actionToKeyMap[cbdown] = {scope:scope,key:key};
if (typeof ondown === 'boolean') {
actionToKeyMap[cbdown].user = ondown;
@@ -412,11 +435,9 @@ RED.keyboard = (function() {
});
revertButton.on("click", function(e) {
e.stopPropagation();
RED.keyboard.revertToDefault(object.id);
container.empty();
container.removeClass('keyboard-shortcut-entry-expanded');
var shortcut = RED.keyboard.getShortcut(object.id);
var userKeymap = RED.settings.get('keymap') || {};
// var userKeymap = RED.settings.get('keymap') || {};
var currentEditorSettings = RED.settings.get('editor') || {};
var userKeymap = currentEditorSettings.keymap || {};
@@ -424,6 +445,9 @@ RED.keyboard = (function() {
currentEditorSettings.keymap = userKeymap;
RED.settings.set('editor',currentEditorSettings);
RED.keyboard.revertToDefault(object.id);
var shortcut = RED.keyboard.getShortcut(object.id);
var obj = {
id:object.id,
scope:shortcut?shortcut.scope:undefined,
@@ -570,6 +594,13 @@ RED.keyboard = (function() {
return pane;
}
function enable() {
handlersActive = true;
}
function disable() {
handlersActive = false;
}
return {
init: init,
add: addHandler,
@@ -577,9 +608,12 @@ RED.keyboard = (function() {
getShortcut: function(actionName) {
return actionToKeyMap[actionName];
},
getUserShortcut: getUserKey,
revertToDefault: revertToDefault,
formatKey: formatKey,
validateKey: validateKey
validateKey: validateKey,
disable: disable,
enable: enable
}
})();

View File

@@ -472,6 +472,8 @@ RED.library = (function() {
autoOpen: false,
width: 800,
resizable: false,
open: function( event, ui ) { RED.keyboard.disable() },
close: function( event, ui ) { RED.keyboard.enable() },
classes: {
"ui-dialog": "red-ui-editor-dialog",
"ui-dialog-titlebar-close": "hide",
@@ -556,9 +558,11 @@ RED.library = (function() {
}
],
open: function(e) {
RED.keyboard.disable();
$(this).parent().find(".ui-dialog-titlebar-close").hide();
},
close: function(e) {
RED.keyboard.enable();
if (libraryEditor) {
libraryEditor.destroy();
libraryEditor = null;

View File

@@ -542,8 +542,6 @@ RED.palette.editor = (function() {
return settingsPane;
}
function createSettingsPane() {
settingsPane = $('<div id="red-ui-settings-tab-palette"></div>');
var content = $('<div id="red-ui-palette-editor">'+
@@ -574,7 +572,11 @@ RED.palette.editor = (function() {
minimumActiveTabWidth: 110
});
createNodeTab(content);
createInstallTab(content);
}
function createNodeTab(content) {
var modulesTab = $('<div>',{class:"red-ui-palette-editor-tab"}).appendTo(content);
editorTabs.addTab({
@@ -726,9 +728,9 @@ RED.palette.editor = (function() {
}
}
});
}
function createInstallTab(content) {
var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content);
editorTabs.addTab({
@@ -761,7 +763,6 @@ RED.palette.editor = (function() {
}
});
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar);
var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar);
var sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
@@ -795,6 +796,7 @@ RED.palette.editor = (function() {
loadedIndex = {};
initInstallTab();
})
RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
addButton: false,
@@ -878,8 +880,87 @@ RED.palette.editor = (function() {
}
});
if (RED.settings.theme('palette.upload') !== false) {
var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
var uploadInput = uploadButton.find('input[type="file"]');
uploadInput.on("change", function(evt) {
if (this.files.length > 0) {
uploadFilenameLabel.text(this.files[0].name)
uploadToolbar.slideDown(200);
}
})
var uploadToolbar = $('<div class="red-ui-palette-editor-upload"></div>').appendTo(installTab);
var uploadForm = $('<div>').appendTo(uploadToolbar);
var uploadFilename = $('<div class="placeholder-input"><i class="fa fa-upload"></i> </div>').appendTo(uploadForm);
var uploadFilenameLabel = $('<span></span>').appendTo(uploadFilename);
var uploadButtons = $('<div class="red-ui-palette-editor-upload-buttons"></div>').appendTo(uploadForm);
$('<button class="editor-button"></button>').text(RED._("common.label.cancel")).appendTo(uploadButtons).on("click", function(evt) {
evt.preventDefault();
uploadToolbar.slideUp(200);
uploadInput.val("");
});
$('<button class="editor-button primary"></button>').text(RED._("common.label.upload")).appendTo(uploadButtons).on("click", function(evt) {
evt.preventDefault();
var spinner = RED.utils.addSpinnerOverlay(uploadToolbar, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="red-ui-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).on("click", function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+uploadInput[0].files[0].name);
var data = new FormData();
data.append("tarball",uploadInput[0].files[0]);
var filename = uploadInput[0].files[0].name;
$.ajax({
url: 'nodes',
data: data,
cache: false,
contentType: false,
processData: false,
method: 'POST',
}).always(function(data,textStatus,xhr) {
spinner.remove();
uploadInput.val("");
uploadToolbar.slideUp(200);
}).fail(function(xhr,textStatus,err) {
var message = textStatus;
if (xhr.responseJSON) {
message = xhr.responseJSON.message;
}
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: filename,message:message}),{
type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
});
uploadInput.val("");
uploadToolbar.slideUp(200);
})
})
RED.popover.tooltip(uploadButton,RED._("palette.editor.upload"));
}
$('<div id="red-ui-palette-module-install-shade" class="red-ui-palette-module-shade hide"><div class="red-ui-palette-module-shade-status"></div><img src="red/images/spin.svg" class="red-ui-palette-spinner"/></div>').appendTo(installTab);
}
function update(entry,version,url,container,done) {
if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable'));

View File

@@ -417,7 +417,8 @@ RED.palette = (function() {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
nodeInfo = RED.utils.renderMarkdown(def.info||"");
var subflow = RED.nodes.subflow(nt.substring(8));
nodeInfo = RED.utils.renderMarkdown(subflow.info||"");
}
setLabel(nt,d,label,nodeInfo);

View File

@@ -166,34 +166,42 @@ RED.projects.settings = (function() {
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
}
function editSummary(activeProject, summary, container) {
function editSummary(activeProject, summary, container, version, versionContainer) {
var editButton = container.prev();
editButton.hide();
container.empty();
versionContainer.empty();
var bg = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').appendTo(container);
var input = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(summary||"").appendTo(container);
var versionInput = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(version||"").appendTo(versionContainer);
$('<button class="red-ui-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(bg)
.on("click", function(evt) {
evt.preventDefault();
updateProjectSummary(activeProject.summary, container);
updateProjectVersion(activeProject.version, versionContainer);
editButton.show();
});
$('<button class="red-ui-button">' + RED._("common.label.save") + '</button>')
.appendTo(bg)
.on("click", function(evt) {
evt.preventDefault();
var v = input.val();
updateProjectSummary(v, container);
var spinner = utils.addSpinnerOverlay(container);
var newSummary = input.val();
var newVersion = versionInput.val();
updateProjectSummary(newSummary, container);
updateProjectVersion(newVersion, versionContainer);
var spinner = utils.addSpinnerOverlay(container).addClass('red-ui-component-spinner-contain');
var done = function(err,res) {
if (err) {
spinner.remove();
return editSummary(activeProject, summary, container);
return editSummary(activeProject, summary, container, version, versionContainer);
}
activeProject.summary = v;
activeProject.summary = newSummary;
activeProject.version = newVersion;
spinner.remove();
updateProjectSummary(activeProject.summary, container);
updateProjectVersion(activeProject.version, versionContainer);
editButton.show();
}
utils.sendRequest({
@@ -214,31 +222,39 @@ RED.projects.settings = (function() {
}
},
}
},{summary:v});
},{summary:newSummary, version: newVersion});
});
}
function updateProjectSummary(summary, container) {
container.empty();
if (summary) {
container.text(summary).removeClass('node-info-node');
container.text(summary).removeClass('red-ui-help-info-none');
} else {
container.text(RED._("sidebar.project.noSummaryAvailable")).addClass('red-ui-help-info-none');
}
}
function updateProjectVersion(version, container) {
container.empty();
if (version) {
container.text(version);
}
}
function createMainPane(activeProject) {
var pane = $('<div id="red-ui-project-settings-tab-main" class="red-ui-project-settings-tab-pane red-ui-help"></div>');
$('<h1>').text(activeProject.name).appendTo(pane);
var summary = $('<div style="position: relative">').appendTo(pane);
var summaryContent = $('<div></div>').appendTo(summary);
var versionContent = $('<div></div>').addClass('red-ui-help-info-none').appendTo(summary);
updateProjectSummary(activeProject.summary, summaryContent);
updateProjectVersion(activeProject.version, versionContent);
if (RED.user.hasPermission("projects.write")) {
$('<button class="red-ui-button red-ui-button-small" style="float: right;">' + RED._('sidebar.project.editDescription') + '</button>')
.prependTo(summary)
.on("click", function(evt) {
evt.preventDefault();
editSummary(activeProject, activeProject.summary, summaryContent);
editSummary(activeProject, activeProject.summary, summaryContent, activeProject.version, versionContent);
});
}
$('<hr>').appendTo(pane);
@@ -1017,7 +1033,7 @@ RED.projects.settings = (function() {
var credentialSecretExistingRow = $('<div class="red-ui-settings-row red-ui-settings-row-credentials"></div>').appendTo(credentialFormRows);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.currentKey")).appendTo(credentialSecretExistingRow);
var credentialSecretExistingInput = $('<input type="password">').appendTo(credentialSecretExistingRow)
var credentialSecretExistingInput = $('<input type="text">').appendTo(credentialSecretExistingRow).typedInput({type:"cred"})
.on("change keyup paste",function() {
if (popover) {
popover.close();
@@ -1030,7 +1046,7 @@ RED.projects.settings = (function() {
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.newKey")).appendTo(credentialSecretNewRow);
var credentialSecretNewInput = $('<input type="password">').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles);
var credentialSecretNewInput = $('<input type="text">').appendTo(credentialSecretNewRow).typedInput({type:"cred"}).on("change keyup paste",checkFiles);
var credentialResetWarning = $('<div class="form-tips form-warning" style="margin: 10px;"><i class="fa fa-warning"></i>' + RED._("sidebar.project.projectSettings.credentialsAlert") + '</div>').hide().appendTo(credentialFormRows);
@@ -1573,8 +1589,6 @@ RED.projects.settings = (function() {
updateForm();
}
function createSettingsPane(activeProject) {
var pane = $('<div id="red-ui-project-settings-tab-settings" class="red-ui-project-settings-tab-pane red-ui-help"></div>');
createFilesSection(activeProject,pane);

View File

@@ -38,13 +38,34 @@ RED.projects.userSettings = (function() {
$('<label for="user-settings-gitconfig-email"></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text" id="user-settings-gitconfig-email">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||"");
}
function createWorkflowSection(pane) {
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.workflow = currentGitSettings.workflow || {};
currentGitSettings.workflow.mode = currentGitSettings.workflow.mode || "manual";
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.workflow")).appendTo(pane);
var workflowContainer = $('<div class="red-ui-settings-section"></div>').appendTo(pane);
$('<div class="red-ui-settings-section-description"></div>').appendTo(workflowContainer).text(RED._("editor:sidebar.project.userSettings.workfowTip"));
var row = $('<div class="red-ui-settings-row"></div>').appendTo(workflowContainer);
$('<label><input type="radio" name="user-setting-gitworkflow" value="manual"> <div style="margin-left: 3px; display: inline-block"><div data-i18n="editor:sidebar.project.userSettings.workflowManual"></div><div style="color:#aaa;" data-i18n="editor:sidebar.project.userSettings.workflowManualTip"></div></div></label>').appendTo(row);
row = $('<div class="red-ui-settings-row"></div>').appendTo(workflowContainer);
$('<label><input type="radio" name="user-setting-gitworkflow" value="auto"> <div style="margin-left: 3px; display: inline-block"><div data-i18n="editor:sidebar.project.userSettings.workflowAuto"></div><div style="color:#aaa;" data-i18n="editor:sidebar.project.userSettings.workflowAutoTip"></div></div></label>').appendTo(row);
workflowContainer.find('[name="user-setting-gitworkflow"][type="radio"][value="'+currentGitSettings.workflow.mode+'"]').prop('checked',true)
}
function createSSHKeySection(pane) {
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(pane);
var container = $('<div class="red-ui-settings-section"></div>').appendTo(pane);
var popover;
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(container);
var subtitle = $('<div class="red-ui-settings-section-description"></div>').appendTo(container).text(RED._("editor:sidebar.project.userSettings.sshKeysTip"));
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="red-ui-button red-ui-button-small" style="float: right; margin-right: 10px;">'+RED._("editor:sidebar.project.userSettings.add")+'</button>')
@@ -391,6 +412,7 @@ RED.projects.userSettings = (function() {
function createSettingsPane(activeProject) {
var pane = $('<div id="red-ui-settings-tab-gitconfig" class="project-settings-tab-pane red-ui-help"></div>');
createGitUserSection(pane);
createWorkflowSection(pane);
createSSHKeySection(pane);
return pane;
}
@@ -407,6 +429,9 @@ RED.projects.userSettings = (function() {
currentGitSettings.user = currentGitSettings.user || {};
currentGitSettings.user.name = gitUsernameInput.val();
currentGitSettings.user.email = gitEmailInput.val();
currentGitSettings.workflow = currentGitSettings.workflow || {};
currentGitSettings.workflow.mode = $('[name="user-setting-gitworkflow"][type="radio"]:checked').val()
RED.settings.set('git', currentGitSettings);
}
});

View File

@@ -81,8 +81,8 @@ RED.projects = (function() {
$('<p>').text(RED._("projects.welcome.desc2")).appendTo(body);
var row = $('<div style="text-align: center"></div>').appendTo(body);
var createAsEmpty = $('<button data-type="empty" class="red-ui-button red-ui-projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.welcome.create")+'</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.welcome.clone")+'</button>').appendTo(row);
var createAsEmpty = $('<button data-type="empty" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.welcome.create")+'</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.welcome.clone")+'</button>').appendTo(row);
createAsEmpty.on("click", function(e) {
e.preventDefault();
@@ -511,7 +511,8 @@ RED.projects = (function() {
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="red-ui-projects-dialog-screen-create-project-repo-pass">'+RED._("projects.clone-project.passwd")+'</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="red-ui-projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
projectRepoPasswordInput = $('<input style="width:100%" id="red-ui-projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
projectRepoPasswordInput.typedInput({type:"cred"});
// -----------------------------------------------------
// Repo credentials - key/passphrase -------------------
@@ -539,12 +540,12 @@ RED.projects = (function() {
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="red-ui-projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.clone-project.passphrase")+'</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="red-ui-projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
projectRepoPassphrase.typedInput({type:"cred"});
subrow = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="red-ui-projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.clone-project.ssh-key-desc")+'</div>').appendTo(sshwarningRow);
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
$('<button class="red-ui-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).on("click", function(e) {
$('<button class="red-ui-button red-ui-projects-dialog-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).on("click", function(e) {
e.preventDefault();
dialog.dialog( "close" );
RED.userSettings.show('gitconfig');
@@ -558,8 +559,8 @@ RED.projects = (function() {
// Secret - clone
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label>'+RED._("projects.clone-project.credential-key")+'</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row);
projectSecretInput = $('<input style="width: 100%" type="password"></input>').appendTo(row);
projectSecretInput.typedInput({type:"cred"});
return container;
@@ -894,6 +895,7 @@ RED.projects = (function() {
$('<label class="red-ui-projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="custom" name="projects-encryption-key"> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.use-custom")+'</span></label>').appendTo(row);
row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
emptyProjectCredentialInput = $('<input disabled type="password" style="margin-left: 25px; width: calc(100% - 30px);"></input>').appendTo(row);
emptyProjectCredentialInput.typedInput({type:"cred"});
emptyProjectCredentialInput.on("change keyup paste", validateForm);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
@@ -1169,11 +1171,11 @@ RED.projects = (function() {
row = $('<div class="form-row button-group"></div>').appendTo(container);
var openProject = $('<button data-type="open" class="red-ui-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>'+RED._("projects.create.open")+'</button>').appendTo(row);
var createAsEmpty = $('<button data-type="empty" class="red-ui-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.create.create")+'</button>').appendTo(row);
// var createAsCopy = $('<button data-type="copy" class="red-ui-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.create.clone")+'</button>').appendTo(row);
// var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row);
var openProject = $('<button data-type="open" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>'+RED._("projects.create.open")+'</button>').appendTo(row);
var createAsEmpty = $('<button data-type="empty" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.create.create")+'</button>').appendTo(row);
// var createAsCopy = $('<button data-type="copy" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.create.clone")+'</button>').appendTo(row);
// var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row);
row.find(".red-ui-projects-dialog-screen-create-type").on("click", function(evt) {
evt.preventDefault();
container.find(".red-ui-projects-dialog-screen-create-type").removeClass('selected');
@@ -1298,6 +1300,7 @@ RED.projects = (function() {
$('<label class="red-ui-projects-edit-form-inline-label">'+RED._("projects.create.encryption-key")+'</label>').appendTo(row);
// row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
emptyProjectCredentialInput = $('<input type="password"></input>').appendTo(row);
emptyProjectCredentialInput.typedInput({type:"cred"});
emptyProjectCredentialInput.on("change keyup paste", validateForm);
$('<label class="red-ui-projects-edit-form-sublabel"><small>'+RED._("projects.create.desc0")+'</small></label>').appendTo(row);
@@ -1356,7 +1359,8 @@ RED.projects = (function() {
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="red-ui-projects-dialog-screen-create-project-repo-pass">'+RED._("projects.create.password")+'</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="red-ui-projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
projectRepoPasswordInput = $('<input style="width:100%" id="red-ui-projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
projectRepoPasswordInput.typedInput({type:"cred"});
// -----------------------------------------------------
// Repo credentials - key/passphrase -------------------
@@ -1384,12 +1388,13 @@ RED.projects = (function() {
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="red-ui-projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.create.passphrase")+'</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="red-ui-projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
projectRepoPassphrase.typedInput({type:"cred"});
subrow = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="red-ui-projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.create.desc2")+'</div>').appendTo(sshwarningRow);
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
$('<button class="red-ui-button">'+RED._("projects.create.add-ssh-key")+'</button>').appendTo(subrow).on("click", function(e) {
$('<button class="red-ui-button red-ui-projects-dialog-button">'+RED._("projects.create.add-ssh-key")+'</button>').appendTo(subrow).on("click", function(e) {
e.preventDefault();
$('#red-ui-projects-dialog-cancel').trigger("click");
RED.userSettings.show('gitconfig');
@@ -1403,8 +1408,8 @@ RED.projects = (function() {
// Secret - clone
row = $('<div class="hide form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>'+RED._("projects.create.credentials-encryption-key")+'</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row);
projectSecretInput = $('<input style="width:100%" type="password"></input>').appendTo(row);
projectSecretInput.typedInput({type:"cred"});
switch(options.screen||"empty") {
case "empty": createAsEmpty.trigger("click"); break;
@@ -1617,14 +1622,14 @@ RED.projects = (function() {
function deleteProject(row,name,done) {
var cover = $('<div class="red-ui-projects-dialog-project-list-entry-delete-confirm"></div>').on("click", function(evt) { evt.stopPropagation(); }).appendTo(row);
$('<span>').text(RED._("projects.delete.confirm")).appendTo(cover);
$('<button class="red-ui-button">'+RED._("common.label.cancel")+'</button>')
$('<button class="red-ui-button red-ui-projects-dialog-button">'+RED._("common.label.cancel")+'</button>')
.appendTo(cover)
.on("click", function(e) {
e.stopPropagation();
cover.remove();
done(true);
});
$('<button class="red-ui-button primary">'+RED._("common.label.delete")+'</button>')
$('<button class="red-ui-button red-ui-projects-dialog-button primary">'+RED._("common.label.delete")+'</button>')
.appendTo(cover)
.on("click", function(e) {
e.stopPropagation();
@@ -1808,7 +1813,7 @@ RED.projects = (function() {
header.addClass("selectable");
var tools = $('<div class="red-ui-projects-dialog-project-list-entry-tools"></div>').appendTo(header);
$('<button class="red-ui-button red-ui-button-small" style="float: right;"><i class="fa fa-trash"></i></button>')
$('<button class="red-ui-button red-ui-projects-dialog-button red-ui-button-small" style="float: right;"><i class="fa fa-trash"></i></button>')
.appendTo(tools)
.on("click", function(e) {
e.stopPropagation();
@@ -1962,7 +1967,8 @@ RED.projects = (function() {
var isSSH = false;
if (/^https?:\/\//.test(url)) {
$('<div class="form-row"><label for="projects-user-auth-username">'+RED._("projects.send-req.username")+'</label><input id="projects-user-auth-username" type="text"></input></div>'+
'<div class="form-row"><label for=projects-user-auth-password">'+RED._("projects.send-req.password")+'</label><input id="projects-user-auth-password" type="password"></input></div>').appendTo(message);
'<div class="form-row"><label for="projects-user-auth-password">'+RED._("projects.send-req.password")+'</label><input id="projects-user-auth-password" type="password"></input></div>').appendTo(message);
message.find("#projects-user-auth-password").typedInput({type:"cred"})
} else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) {
isSSH = true;
var row = $('<div class="form-row"></div>').appendTo(message);
@@ -1980,7 +1986,7 @@ RED.projects = (function() {
});
row = $('<div class="form-row"></div>').appendTo(message);
$('<label for="projects-user-auth-passphrase">'+RED._("projects.send-req.passphrase")+'</label>').appendTo(row);
$('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row);
$('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row).typedInput({type:"cred"});
}
var notification = RED.notify(message,{
@@ -2263,6 +2269,12 @@ RED.projects = (function() {
autoOpen: false,
width: 600,
resizable: false,
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
},
classes: {
"ui-dialog": "red-ui-editor-dialog",
"ui-dialog-titlebar-close": "hide",

View File

@@ -293,14 +293,20 @@ RED.sidebar.versionControl = (function() {
if (activeProject) {
// TODO: this is a full refresh of the files - should be able to
// just do an incremental refresh
allChanges = {};
unstagedChangesList.editableList('empty');
stagedChangesList.editableList('empty');
unmergedChangesList.editableList('empty');
$.getJSON("projects/"+activeProject.name+"/status",function(result) {
refreshFiles(result);
});
var workflowMode = ((RED.settings.get('git') || {}).workflow || {}).mode || "manual";
if (workflowMode === 'auto') {
refresh(true);
} else {
allChanges = {};
unstagedChangesList.editableList('empty');
stagedChangesList.editableList('empty');
unmergedChangesList.editableList('empty');
$.getJSON("projects/"+activeProject.name+"/status",function(result) {
refreshFiles(result);
});
}
}
});
RED.events.on("login",function() {

View File

@@ -232,7 +232,11 @@ RED.sidebar = (function() {
}
},
// minimumActiveTabWidth: 70,
collapsible: true
collapsible: true,
onreorder: function(order) {
RED.settings.set("editor.sidebar.order",order);
},
order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])
// scrollable: true
});

View File

@@ -453,14 +453,16 @@ RED.subflow = (function() {
$("#red-ui-workspace-chart").css({"margin-top": "0"});
}
function removeSubflow(id) {
function removeSubflow(id, keepInstanceNodes) {
// TODO: A lot of this logic is common with RED.nodes.removeWorkspace
var removedNodes = [];
var removedLinks = [];
var removedGroups = [];
var activeSubflow = RED.nodes.subflow(id);
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+id) {
if (!keepInstanceNodes && n.type == "subflow:"+id) {
removedNodes.push(n);
}
if (n.z == id) {
@@ -472,7 +474,9 @@ RED.subflow = (function() {
removedNodes.push(n);
}
});
RED.nodes.groups(id).forEach(function(n) {
removedGroups.push(n);
})
var removedConfigNodes = [];
for (var i=0;i<removedNodes.length;i++) {
var removedEntities = RED.nodes.remove(removedNodes[i].id);
@@ -482,6 +486,18 @@ RED.subflow = (function() {
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
removedGroups = RED.nodes.groups(id).filter(function(g) { return !g.g; });
for (i=0;i<removedGroups.length;i++) {
removedGroups[i].nodes.forEach(function(n) {
if (n.type === "group") {
removedGroups.push(n);
}
});
}
// Now remove them in the reverse order
for (i=removedGroups.length-1; i>=0; i--) {
RED.nodes.removeGroup(removedGroups[i]);
}
RED.nodes.removeSubflow(activeSubflow);
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
@@ -490,6 +506,7 @@ RED.subflow = (function() {
return {
nodes:removedNodes,
links:removedLinks,
groups: removedGroups,
subflows: [activeSubflow]
}
}

View File

@@ -261,10 +261,12 @@ RED.sidebar.help = (function() {
}
function show(type) {
RED.sidebar.show("help");
function show(type, bringToFront) {
if (bringToFront !== false) {
RED.sidebar.show("help");
}
if (type) {
hideTOC();
// hideTOC();
showHelp(type);
}
resizeStack();

View File

@@ -79,7 +79,7 @@ RED.sidebar.info.outliner = (function() {
try {
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
} catch(err) {
console.log("Definition error: "+type+".label",err);
console.log("Definition error: "+n.type+".label",err);
}
}
var newlineIndex = label.indexOf("\\n");

View File

@@ -112,14 +112,14 @@ RED.touch.radialMenu = (function() {
if (!opt.disabled) {
if (p[0]>opt.x-30 && p[0]<opt.x+30 && p[1]>opt.y-30 && p[1]<opt.y+30) {
if (opt !== activeOption) {
opt.el.style("background","#999");
opt.el.classed("selected",true);
activeOption = opt;
}
} else if (opt === activeOption) {
opt.el.style("background","#fff");
activeOption = null;
} else {
opt.el.style("background","#fff");
if (opt === activeOption) {
activeOption = null;
}
opt.el.classed("selected",false);
}
}
}

View File

@@ -52,13 +52,15 @@ RED.utils = (function() {
} else if (value === null) {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-null">null</span>');
} else if (typeof value === 'object') {
if (value.hasOwnProperty('type') && value.type === 'Buffer' && value.hasOwnProperty('data')) {
if (value.hasOwnProperty('type') && value.type === 'undefined') {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-null">undefined</span>');
} else if (value.hasOwnProperty('type') && value.type === 'Buffer' && value.hasOwnProperty('data')) {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('buffer['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('array['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'function') {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('function');
} else if (value.hasOwnProperty('type') && value.type === 'number') {
} else if (value.hasOwnProperty('type') && (value.type === 'number' || value.type === 'bigint')) {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-number"></span>').text(value.data);
} else {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta">object</span>');
@@ -348,7 +350,9 @@ RED.utils = (function() {
}
if (obj === null || obj === undefined) {
$('<span class="red-ui-debug-msg-type-null">'+obj+'</span>').appendTo(entryObj);
} else if (obj.__enc__ && obj.type === 'number') {
} else if (obj.__enc__ && obj.type === 'undefined') {
$('<span class="red-ui-debug-msg-type-null">undefined</span>').appendTo(entryObj);
} else if (obj.__enc__ && (obj.type === 'number' || obj.type === 'bigint')) {
e = $('<span class="red-ui-debug-msg-type-number red-ui-debug-msg-object-header"></span>').text(obj.data).appendTo(entryObj);
} else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) {
e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("function").appendTo(entryObj);

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,11 @@ RED.user = (function() {
closeOnEscape: !!opts.cancelable,
width: 600,
resizable: false,
draggable: false
draggable: false,
close: function( event, ui ) {
$("#node-dialog-login").dialog('destroy').remove();
RED.keyboard.enable()
}
});
$("#node-dialog-login-fields").empty();
@@ -98,10 +102,10 @@ RED.user = (function() {
data: body
}).done(function(data,textStatus,xhr) {
RED.settings.set("auth-tokens",data);
$("#node-dialog-login").dialog('destroy').remove();
if (opts.updateMenu) {
updateUserMenu();
}
$("#node-dialog-login").dialog("close");
done();
}).fail(function(jqXHR,textStatus,errorThrown) {
RED.settings.remove("auth-tokens");
@@ -143,7 +147,8 @@ RED.user = (function() {
}
if (opts.cancelable) {
$("#node-dialog-login-cancel").button().on("click", function( event ) {
$("#node-dialog-login").dialog('destroy').remove();
$("#node-dialog-login").dialog('close');
});
}
@@ -152,8 +157,7 @@ RED.user = (function() {
$("#node-dialog-login-image").load(function() {
dialog.dialog("open");
}).attr("src",loginImageSrc);
RED.keyboard.disable();
}
});
}
@@ -215,7 +219,7 @@ RED.user = (function() {
function init() {
if (RED.settings.user) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu") || RED.settings.editorTheme.userMenu) {
var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
.prependTo(".red-ui-header-toolbar");

View File

@@ -15,6 +15,9 @@
**/
body {
overflow: hidden;
}
.red-ui-editor {
font-size: $primary-font-size;

View File

@@ -289,3 +289,4 @@ $group-default-fill: none;
$group-default-fill-opacity: 1;
$group-default-stroke: #999;
$group-default-stroke-opacity: 1;
$group-default-label-color: #a4a4a4;

View File

@@ -597,6 +597,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
padding: 4px;
color: $secondary-text-color;
font-size: 0.9em;
line-height: 24px;
}
button {
float: right;

View File

@@ -106,6 +106,11 @@
pointer-events: stroke;
stroke-opacity: 0;
stroke-width: 3;
&.red-ui-flow-group-outline-select-background {
stroke: $view-background;
stroke-width: 6;
}
}
.red-ui-flow-group-body {
pointer-events: none;
@@ -117,6 +122,7 @@
}
.red-ui-flow-group-label {
@include disable-selection;
fill: $group-default-label-color;
}
@@ -134,6 +140,7 @@
}
.red-ui-flow-node-icon-group {
.fa-lg {
@include disable-selection;
stroke: none;
fill: $node-icon-color;
text-anchor: middle;
@@ -244,7 +251,7 @@ g.red-ui-flow-node-selected {
stroke-dasharray: none;
}
}
@each $current-color in red green yellow blue grey {
@each $current-color in red green yellow blue grey gray {
.red-ui-flow-node-status-dot-#{$current-color} {
fill: map-get($node-status-colors,$current-color);
stroke: map-get($node-status-colors,$current-color);
@@ -336,7 +343,10 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line {
stroke-dasharray: 10, 4;
}
@keyframes red-ui-flow-port-tooltip-fadeIn { from { opacity:0; } to { opacity:1; } }
// @keyframes *must* be on multiple lines so build-custom-theme can filter them out
@keyframes red-ui-flow-port-tooltip-fadeIn {
from { opacity:0; } to { opacity:1; }
}
.red-ui-flow-port-tooltip {
opacity:0;

View File

@@ -153,3 +153,61 @@
border-top: none;
}
}
.red-ui-clipboard-dialog-import-conflicts-list-container {
min-height: 300px;
position: relative;
li:not(:first-child) .red-ui-clipboard-dialog-import-conflicts-item-header {
// border-top: 1px solid $secondary-border-color;
}
}
.red-ui-clipboard-dialog-import-conflicts-item-header {
background: $tertiary-background;
& > span:first-child {
color: $header-text-color;
padding-left: 4px;
font-size: 12px;
}
}
.red-ui-clipboard-dialog-import-conflicts-controls {
position: absolute;
top:0;
bottom: 0;
right: 0px;
text-align: center;
color: $form-text-color;
.form-row & label {
padding: 2px 0;
line-height: 23px;
margin-bottom: 0;
width: 80px;
display: inline-block;
position: relative;
height: 100%;
width: 80px;
text-align: center;
border-left: 1px solid $secondary-border-color;
}
input[type="checkbox"] {
display: inline-block;
width: auto;
margin: 0;
}
}
#red-ui-clipboard-dialog-import-conflicts-list .disabled .red-ui-info-outline-item {
opacity: 0.4;
}
.form-row label.red-ui-clipboard-dialog-import-conflicts-gutter {
box-sizing: border-box;
width: 22px;
text-align: center;
.red-ui-editor-dialog & input[type="checkbox"] {
width: auto;
padding: 0;
margin: 0;
}
}

View File

@@ -71,38 +71,9 @@
}
.red-ui-notification-shake-horizontal {
-webkit-animation: red-ui-notification-shake-horizontal 0.3s steps(2, end) both;
animation: red-ui-notification-shake-horizontal 0.3s steps(2, end) both;
}
@-webkit-keyframes red-ui-notification-shake-horizontal {
0%,
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
10%,
30%,
50%,
70% {
-webkit-transform: translateX(-1px);
transform: translateX(-1px);
}
20%,
40%,
60% {
-webkit-transform: translateX(1px);
transform: translateX(1px);
}
// 80% {
// -webkit-transform: translateX(1px);
// transform: translateX(1px);
// }
// 90% {
// -webkit-transform: translateX(-1px);
// transform: translateX(-1px);
// }
animation: red-ui-notification-shake-horizontal 0.3s steps(2, end) both;
}
// @keyframes *must* be on multiple lines so build-custom-theme can filter them out
@keyframes red-ui-notification-shake-horizontal {
0%,
100% {
@@ -122,12 +93,4 @@
-webkit-transform: translateX(1px);
transform: translateX(1px);
}
// 80% {
// -webkit-transform: translateX(1px);
// transform: translateX(1px);
// }
// 90% {
// -webkit-transform: translateX(-1px);
// transform: translateX(-1px);
// }
}

View File

@@ -237,3 +237,45 @@ ul.red-ui-palette-module-error-list {
#red-ui-palette-module-install-shade {
padding-top: 80px;
}
button.red-ui-palette-editor-upload-button {
padding: 0;
height: 25px;
margin-top: -1px;
input[type="file"] {
opacity: 0;
margin: 0;
height: 0;
width: 0;
}
.red-ui-settings-tabs-content & label {
margin: 0;
min-width: 0;
padding: 2px 8px;
}
form {
width: 0;
}
}
.red-ui-palette-editor-upload {
display: none;
position: absolute;
left: 0;
right: 0;
top: 44px;
padding: 20px;
background: $secondary-background;
border-bottom: 1px $secondary-border-color solid;
box-shadow: 1px 1px 4px $shadow;
.placeholder-input {
width: calc(100% - 180px);
margin: 0;
}
}
.red-ui-palette-editor-upload-buttons {
float: right;
button {
margin-left: 10px;
}
}

View File

@@ -204,7 +204,7 @@
.red-ui-palette-icon-fa {
color: white;
position: absolute;
top: 7px;
top: calc(50% - 7px);
left: 3px;
}
.red-ui-palette-node-small {

View File

@@ -90,7 +90,7 @@
font-size: 1.2em;
}
}
button.red-ui-button {
button.red-ui-button.red-ui-projects-dialog-button {
width: calc(50% - 80px);
margin: 20px;
height: auto;

View File

@@ -42,7 +42,11 @@
background: $secondary-background;
border: 2px solid $primary-border-color;
text-align: center;
line-height:50px
line-height:50px;
&.selected {
background: $workspace-button-background-hover;
}
}
.red-ui-editor-radial-menu-opt-disabled {

View File

@@ -39,6 +39,13 @@
vertical-align: top;
}
button.red-ui-toggleButton.toggle {
text-align: center;
i {
min-width: 10px;
}
}
}
.red-ui-sidebar-context-property {

View File

@@ -321,15 +321,12 @@ div.red-ui-info-table {
border: none;
border-radius: 0;
}
.red-ui-treeList-label {
font-size: 13px;
padding: 2px 0;
overflow: hidden;
}
.red-ui-info-outline-project {
border-bottom: 1px solid $secondary-border-color;
}
}
.red-ui-info-outline,.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list {
.red-ui-info-outline-item {
display: inline-block;
padding: 0;
@@ -366,6 +363,12 @@ div.red-ui-info-table {
}
}
.red-ui-treeList-label {
font-size: 13px;
padding: 2px 0;
overflow: hidden;
}
.red-ui-search-result-node {
width: 24px;
height: 20px;
@@ -387,6 +390,7 @@ div.red-ui-info-table {
color: $secondary-text-color;
}
}
.red-ui-info-outline-item-control-spacer {
display: inline-block;
width: 23px;

View File

@@ -19,7 +19,7 @@
color: $secondary-text-color;
cursor: pointer;
input {
display:none;
display:none !important;
}
&.disabled {

View File

@@ -18,6 +18,7 @@
border: 1px solid $form-input-border-color;
border-radius: 4px;
height: 34px;
line-height: 14px;
display: inline-flex;
padding: 0;
margin: 0;
@@ -25,6 +26,14 @@
box-sizing: border-box;
overflow:visible;
position: relative;
&[disabled] {
input, button {
background: $secondary-background-inactive;
pointer-events: none;
cursor: not-allowed;
}
}
.red-ui-typedInput-input-wrap {
flex-grow: 1;
}

View File

@@ -79,7 +79,7 @@
display: table;
clear: both;
}
.uneditable-input, input, textarea {
.uneditable-input, input[type="text"],input[type="password"], textarea {
width: calc(100% - 150px);
}
textarea {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
ace.define("ace/mode/sql_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|when|then|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|foreign|not|references|default|null|inner|cross|natural|database|drop|grant",t="true|false",n="avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|coalesce|ifnull|isnull|nvl",r="int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|money|real|number|integer",i=this.createKeywordMapper({"support.function":n,keyword:e,"constant.language":t,"storage.type":r},"identifier",!0);this.$rules={start:[{token:"comment",regex:"--.*$"},{token:"comment",start:"/\\*",end:"\\*/"},{token:"string",regex:'".*?"'},{token:"string",regex:"'.*?'"},{token:"string",regex:"`.*?`"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:i,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\(]"},{token:"paren.rparen",regex:"[\\)]"},{token:"text",regex:"\\s+"}]},this.normalizeRules()};r.inherits(s,i),t.SqlHighlightRules=s}),ace.define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./sql_highlight_rules").SqlHighlightRules,o=function(){this.HighlightRules=s,this.$behaviour=this.$defaultBehaviour};r.inherits(o,i),function(){this.lineCommentStart="--",this.$id="ace/mode/sql"}.call(o.prototype),t.Mode=o}); (function() {
ace.define("ace/mode/sql_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|when|then|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|foreign|not|references|default|null|inner|cross|natural|database|drop|grant",t="true|false",n="avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|coalesce|ifnull|isnull|nvl",r="int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|money|real|number|integer",i=this.createKeywordMapper({"support.function":n,keyword:e,"constant.language":t,"storage.type":r},"identifier",!0);this.$rules={start:[{token:"comment",regex:"--.*$"},{token:"comment",start:"/\\*",end:"\\*/"},{token:"string",regex:'".*?"'},{token:"string",regex:"'.*?'"},{token:"string",regex:"`.*?`"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:i,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\(]"},{token:"paren.rparen",regex:"[\\)]"},{token:"text",regex:"\\s+"}]},this.normalizeRules()};r.inherits(s,i),t.SqlHighlightRules=s}),ace.define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./sql_highlight_rules").SqlHighlightRules,o=function(){this.HighlightRules=s,this.$behaviour=this.$defaultBehaviour};r.inherits(o,i),function(){this.lineCommentStart="--",this.$id="ace/mode/sql",this.snippetFileId="ace/snippets/sql"}.call(o.prototype),t.Mode=o}); (function() {
ace.require(["ace/mode/sql"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(:(?=\s|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*:(?=\s|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:/[|>][-+\d\s]*$/,onMatch:function(e,t,n,r){var i=/^\s*/.exec(r)[0];return n.length<1?n.push(this.next):n[0]="mlString",n.length<2?n.push(i.length):n[1]=i.length,this.token},next:"mlString"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:/[^\s,:\[\]\{\}]+/}],mlString:[{token:"indent",regex:/^\s*$/},{token:"indent",regex:/^\s*/,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.splice(0)):this.next="mlString",this.token},next:"mlString"},{token:"string",regex:".+"}]},this.normalizeRules()};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart=["#"],this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a}); (function() {
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(:(?=\s|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*:(?=\s|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:/[|>][-+\d]*(?:$|\s+(?:$|#))/,onMatch:function(e,t,n,r){r=r.replace(/ #.*/,"");var i=/^ *((:\s*)?-(\s*[^|>])?)?/.exec(r)[0].replace(/\S\s*$/,"").length,s=parseInt(/\d+[\s+-]*$/.exec(r));return s?(i+=s-1,this.next="mlString"):this.next="mlStringPre",n.length?(n[0]=this.next,n[1]=i):(n.push(this.next),n.push(i)),this.token},next:"mlString"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:/[^\s,:\[\]\{\}]+/}],mlStringPre:[{token:"indent",regex:/^ *$/},{token:"indent",regex:/^ */,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.shift(),n.shift()):(n[1]=e.length-1,this.next=n[0]="mlString"),this.token},next:"mlString"},{defaultToken:"string"}],mlString:[{token:"indent",regex:/^ *$/},{token:"indent",regex:/^ */,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.splice(0)):this.next="mlString",this.token},next:"mlString"},{token:"string",regex:".+"}]},this.normalizeRules()};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart=["#"],this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a}); (function() {
ace.require(["ace/mode/yaml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;

View File

@@ -1,4 +1,4 @@
ace.define("ace/snippets/handlebars",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="handlebars"}); (function() {
; (function() {
ace.require(["ace/snippets/handlebars"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;

Some files were not shown because too many files have changed in this diff Show More