Compare commits

...

401 Commits

Author SHA1 Message Date
Nick O'Leary
08fccc4e77 Update for 0.19.4 2018-09-17 11:26:06 +01:00
Nick O'Leary
c1d50e82e1 Fix race condition in non-cache lfs context
Fixes #1888
2018-09-17 10:31:00 +01:00
Nick O'Leary
9777af7cb5 LocalFileSystem Context: Remove extra flush code 2018-09-16 22:04:09 +01:00
Hiroki Uchikawa
fd86035865 Prevent race condition (#1889)
* Make pending Flag to be deleted after write process complete.

* Prevent executing write process until the previous process is completed

* Fix to prevent file write race condition when closing file context

* Make flushing rerun if pendingWrites was added
2018-09-16 21:15:23 +01:00
Nick O'Leary
a8ec032553 Allow context store name to be provided in the key
For nodes that get/set context, when multiple stores are configured
they will not know to parse the store name from the key. So they
will pass the store name in the key, such as #:(store)::key.

Currently that will cause that full string to be used as the key
and the default context store used - which is wrong.

The code now parses out the store name from the key if it is set -
athough if the call to get/set does include the store argument, it
will take precedence.

This only applies when the key is a string - it doesn't apply when
an array of keys is provided.
2018-09-14 23:21:05 +01:00
Nick O'Leary
66ee27c5fa Switch node: only use promises when absolutely necessary
Fixes a significant performance regression introduced when the
node was made async-only with the persistent context work.
2018-09-14 14:03:36 +01:00
Nick O'Leary
17a737ca88 Fix dbl-click handling on webkit-based browsers
d3.event.buttons is not as widely supported as I thought. Can
change this one instance as it is inside a click handler so
d3.event.button will be defined instead
2018-09-14 11:09:56 +01:00
Nick O'Leary
75e7c0e50d Ensure context.flow/global cannot be deleted or enumerated 2018-09-10 22:30:51 +01:00
Nick O'Leary
fc0cf1ff51 Handle context.get with multiple levels of unknown key
Fixes #1883
2018-09-09 23:47:31 +01:00
Nick O'Leary
0f4d46671f Fix global.get("foo.bar") for functionGlobalContext set values 2018-09-09 11:07:44 +01:00
Kazuhito Yokoi
048f9c0294 Fix node color bug (#1877)
* Fix node color bug

* Add color property into sample node

* Revert view.js

* Add color handling into getNodeColor()
2018-09-08 22:41:38 +01:00
Nick O'Leary
ca77842b5b Merge pull request #1857 from cclauss/patch-1
Define raw_input() in Python 3 & fix time.sleep()
2018-09-06 22:59:48 +01:00
Hiroyasu Nishiyama
6fa8b7f5f1 fix persistable context handling of sort node & existing error in testcases 2018-09-05 16:04:12 +01:00
Nick O'Leary
a2d03c14ae Update CHANGELOG for 0.19.3 2018-09-05 09:49:09 +01:00
Dave Conway-Jones
c667a0e74c debug node - show ring at start until first msg 2018-09-05 09:45:34 +01:00
Dave Conway-Jones
8123828113 improve split node accumulation test to include early complete 2018-09-05 08:36:56 +01:00
Dave Conway-Jones
72b8dbb45b Split node - fix complete to send msg for k/v object
and update info to try to clarify.
2018-09-04 22:54:28 +01:00
Dave Conway-Jones
7703875740 tidy split node merged object key typed input 2018-09-04 17:41:14 +01:00
Nick O'Leary
6442bb8a13 Set the JavaScript editor to full-screen 2018-09-04 13:30:06 +01:00
Nick O'Leary
f29d7c9252 Fixup localfilesystem registry test 2018-09-04 11:37:04 +01:00
Nick O'Leary
2f7f53ed96 Filter global modules installed locally
If a module is found both locally and globally installed, the local
copy will take precedence. This will allow a user to upgrade a
node module that they may not otherwise be able to touch
2018-09-04 11:26:05 +01:00
Nick O'Leary
94031a52a5 Add svg to permitted icon extension list 2018-08-31 21:02:09 +01:00
Dave Conway-Jones
d67f91e7ed debug node - indicate status all the time if selected to do so 2018-08-31 16:31:08 +01:00
Nick O'Leary
f37697c4fb Merge pull request #1870 from natcl/json-schema
JSON node: fix schema validation for obj -> obj or str -> str
2018-08-31 11:25:31 +01:00
Dave Conway-Jones
69448c7329 pi nodes - increase test coverage slightly 2018-08-30 20:54:03 +01:00
Dave Conway-Jones
8e9815fb91 TCP-request node - only write payload
to close #1869
2018-08-30 20:47:39 +01:00
Nathanaël Lécaudé
4cdd7978cf JSON schema: remove unused function 2018-08-29 13:40:37 -04:00
Nathanaël Lécaudé
40d81358f4 JSON schema: perform validation when obj -> obj or str -> str 2018-08-29 13:36:28 -04:00
Nathanaël Lécaudé
c7b62aed91 JSON schema: add draft-06 support (via $schema keyword) 2018-08-29 12:20:04 -04:00
Stefan Machmeier
c0e7d6d826 Mqtt proxy configuration for websocket connection, #1651. 2018-08-29 09:53:07 +01:00
Nick O'Leary
f809377de8 Merge pull request #1854 from kazuhitoyokoi/master-fixtypointestcase4functionnode
Fix typo in test case
2018-08-28 21:19:48 +01:00
Nick O'Leary
9767bd9697 Merge pull request #1860 from node-red-hitachi/uitest-refactoring
Refactored UI testing code following a design note
2018-08-28 21:06:36 +01:00
Nick O'Leary
3a55528552 Merge pull request #1861 from SPIRIT-21/master
Allows MQTT Shared Subscriptions for MQTT-In core node
2018-08-28 21:04:53 +01:00
Nick O'Leary
56197ffe3a Merge pull request #1851 from t4skforce/patch-1
fixed some linting issues
2018-08-28 20:58:51 +01:00
Nick O'Leary
0f0d0c046c Merge pull request #1853 from node-red-hitachi/fix-icon-spec-for-typedInput
Fix use of HTML tag or CSS class specification as icon of TypedInput
2018-08-28 20:44:06 +01:00
nakanishi
ecc4973645 Fixed the problems that were caused by timing issue 2018-08-27 17:34:04 +09:00
Nick O'Leary
3169f93cc2 Bump for 0.19.2 2018-08-24 13:25:02 +01:00
Nick O'Leary
c1a1a73599 Ensure node default color is used if palette.theme has no match 2018-08-24 13:08:49 +01:00
Christopher Hiller
db1b0ccb79 fix lost messages / properties in TCPRequest Node; closes #1863 (#1864)
- Added some more checks around this.
- We're choosing to only use the latest message when sending, which is
  effectively what was happening before the queue implementation.
2018-08-23 08:50:51 +01:00
Lars Oldiges
df161ce672 Allows MQTT Shared Subscriptions for MQTT-In core node 2018-08-22 13:20:49 +02:00
nakanishi
72fe30892e Refactor UI testing code following a design note 2018-08-22 14:36:30 +09:00
Nick O'Leary
d373105b32 Fix typo in template.html 2018-08-21 13:42:51 +01:00
Nick O'Leary
28b311b7ed Improve error reporting from context plugin loading 2018-08-16 14:36:11 +01:00
Nick O'Leary
dcda513901 Prevent no-op edit of node marking as changed due to icon 2018-08-16 10:54:27 +01:00
Nick O'Leary
72c400794c Change node must handle empty rule set 2018-08-16 09:41:43 +01:00
Nick O'Leary
042409f870 Update for 0.19.1 2018-08-15 15:33:24 +01:00
Nick O'Leary
5b8f4f4069 Pull in latest twitter node 2018-08-15 15:31:55 +01:00
Nick O'Leary
d132d63c1d Handle windows paths for context storage 2018-08-15 15:31:42 +01:00
cclauss
4374506981 Define raw_input() in Python 3 & fix time.sleep()
* __raw_input()__ was removed in Python 3 in favor of __input()__
* Fix __sleep()__ to match the import on line 22

[flake8](http://flake8.pycqa.org) testing of https://github.com/node-red/node-red on Python 3.7.0

$ __flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics__
```
./nodes/core/hardware/nrgpio.py:45:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:63:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:85:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:120:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:134:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:164:24: F821 undefined name 'raw_input'
                data = raw_input()
                       ^
./nodes/core/hardware/nrgpio.py:201:17: F821 undefined name 'time'
                time.sleep(10)
                ^
7     F821 undefined name 'raw_input'
7
```

@dceejay
2018-08-15 16:25:58 +02:00
Nick O'Leary
ef8b936069 Handle persisting objects with circular refs in context 2018-08-15 10:19:37 +01:00
Nick O'Leary
36e3bfffb4 Ensure js editor can expand to fill available space 2018-08-14 17:30:25 +01:00
Nick O'Leary
91a38bdb60 Add example localfilesystem contextStorage to settings 2018-08-14 16:28:59 +01:00
Nick O'Leary
f169a68319 Fix template node handling of nested context tags 2018-08-14 16:21:38 +01:00
Nick O'Leary
ee886f98dd Update dependencies 2018-08-13 11:19:05 +01:00
Nick O'Leary
a3826cc6a7 Bump version to 0.19 2018-08-13 11:06:38 +01:00
Nick O'Leary
ba33b832ba Info side putting info text in wrong pane 2018-08-13 11:05:33 +01:00
Nick O'Leary
f6c017176b Add core:toggle-navigator action 2018-08-13 11:04:34 +01:00
Nick O'Leary
7a01b115bb User settings view tab does not scroll properly 2018-08-13 11:03:55 +01:00
Nick O'Leary
1dc021e871 Improve custom context store module logging 2018-08-09 15:37:04 +01:00
Nick O'Leary
c9f916ebab Fixup context test case to block until context close completes 2018-08-09 15:36:43 +01:00
Nick O'Leary
ff627fd128 Fix localfilesystem clean handling 2018-08-09 14:39:20 +01:00
Kazuhito Yokoi
695873d35a Fix typo in test case for function node 2018-08-06 21:14:53 +09:00
Hiroyasu Nishiyama
15da19dcea fix use of HTML tag or CSS class specification as icon of typedInput 2018-08-06 10:28:02 +09:00
t4skforce
d5bdc1600b fixed some linting issues
* added some semicolons
* removed double parsing of ```err.stack``` into ```var stack```
2018-07-31 22:54:15 +02:00
Nick O'Leary
dfa077fd5f Update package versions 2018-07-30 15:25:10 +01:00
Nick O'Leary
5155770213 Ensure add/remove modules are handled sequentially 2018-07-30 10:08:39 +01:00
Nick O'Leary
f1d5bbb036 Remove type editors from master template 2018-07-28 22:36:31 +01:00
Nick O'Leary
69ed0aebc3 Merge pull request #1850 from node-red-hitachi/without-callback
Allow `get` and `keys` to be called without callback
2018-07-28 22:08:36 +01:00
Nick O'Leary
549e56e220 Add editorTheme.palette.theme to allow overriding colours 2018-07-27 22:05:42 +01:00
Nick O'Leary
450f4d9a5a Fix error reporting of invalid jsonata in Join/reduce 2018-07-27 22:05:42 +01:00
HirokiUchikawa
6533a9793c Allow get and keys to be called without callback 2018-07-27 21:33:38 +09:00
Nick O'Leary
2000cadb17 Merge pull request #1847 from node-red-hitachi/update-uitest
Add UItest for http endpoints
2018-07-27 13:21:20 +01:00
Nick O'Leary
083c321efa Merge pull request #1848 from node-red-hitachi/grunt-on-windows
Fix test cases on windows and under proxy
2018-07-27 13:21:06 +01:00
Nick O'Leary
f64c4a981f Merge pull request #1846 from node-red-hitachi/remove-html-element
Fix to remove unnecessary typedInput option element
2018-07-27 13:20:44 +01:00
Nick O'Leary
3ac8ce03bf Merge pull request #1845 from node-red-hitachi/fix-file-node-test-for-ordering-assumption-of-output-message
Update file node test to cope with occasional failure
2018-07-27 13:20:27 +01:00
nakanishi
66fca8710e Fix test cases on windows and under proxy 2018-07-27 15:40:55 +09:00
Yuma Matsuura
1e245ece46 Update cookbook uitest 2018-07-27 13:48:43 +09:00
Hiroyasu Nishiyama
81efce03ba update file node test to cope with occasional failure 2018-07-27 08:30:03 +09:00
Nick O'Leary
4e549dd426 Add function tests for multiple-set access to context 2018-07-26 21:15:32 +01:00
Nick O'Leary
52f74ff7e0 Join: reduce fails if count not in first msg received 2018-07-26 14:13:12 +01:00
HirokiUchikawa
3c71b815f5 Fix to remove unnecessary typedInput element 2018-07-26 20:13:08 +09:00
Nick O'Leary
9efd48fe51 Fixup Join node to apply reduce_fixup asynchronously 2018-07-25 11:08:03 +01:00
Nick O'Leary
4609ee75b6 Revert jsonata sync access to context stores
- store access only possible with callback
2018-07-25 11:07:29 +01:00
Nick O'Leary
963ea4177e Add store arg to sync $flowContext/$globalContext 2018-07-25 10:18:59 +01:00
Nick O'Leary
17e6940a42 Update context plugins to use get/setObjectProperty 2018-07-25 09:59:26 +01:00
Nick O'Leary
315a9ceba3 Add RED.util.get/setObjectProperty to avoid stripping msg. 2018-07-25 09:27:27 +01:00
Nick O'Leary
a2bdeedb09 Merge pull request #1843 from node-red-hitachi/test-runtime-util
Add tests for runtime util
2018-07-25 09:16:50 +01:00
Nick O'Leary
da5700d2d7 Merge pull request #1842 from node-red-hitachi/runtime-message-jp
Update Japanese message catalogue of runtime.json
2018-07-25 09:15:18 +01:00
nakanishi
90e7f30247 Add tests for runtime util 2018-07-25 09:15:27 +09:00
Hiroyasu Nishiyama
3ccf6ba892 update Japanese message catalogue of runtime.json 2018-07-24 21:53:59 +09:00
Nick O'Leary
e50cd5b745 Bump bcrypt dependency 2018-07-24 10:21:08 +01:00
Nick O'Leary
db77be5d72 Update i18next in runtime 2018-07-23 23:25:57 +01:00
Nick O'Leary
c36870c23e Bump sem-ver minor dependencies 2018-07-23 15:52:02 +01:00
Nick O'Leary
e9be007040 Tidy up context store error messages 2018-07-23 15:20:13 +01:00
Nick O'Leary
9e400d9aa6 Merge pull request #1838 from node-red-hitachi/translate-node-jp
Update Japanese translation(MQTT node and editor.json)
2018-07-23 13:37:09 +01:00
Nick O'Leary
490c8dae75 Merge pull request #1837 from node-red-hitachi/master-switch-change-test
Fix and add test cases for persistable context
2018-07-23 13:29:23 +01:00
Nick O'Leary
3bcffe375d Merge pull request #1834 from node-red-hitachi/add-tests-for-context-admin-api
Add tests for context admin api
2018-07-23 13:29:10 +01:00
Nick O'Leary
9f81a591e1 Move multiple-get/set logic into individual context stores 2018-07-23 13:28:06 +01:00
Yuma Matsuura
3db5306c70 Update Japanese text of type input 2018-07-23 10:11:35 +09:00
Yuma Matsuura
45029dd084 Update Japanese text of type input 2018-07-23 10:11:23 +09:00
Yuma Matsuura
b01cd30339 Update Japanease text of mqtt node 2018-07-23 10:11:10 +09:00
Hiroyasu Nishiyama
09329e1104 add tests for context admin api 2018-07-21 12:00:54 +09:00
Nick O'Leary
ab0fc2ecfa Merge pull request #1818 from node-red-hitachi/context-store-logging
Add logging of context store
2018-07-20 20:23:32 +01:00
Nick O'Leary
bf5d36d6bd Merge branch 'master' into context-store-logging 2018-07-20 20:23:19 +01:00
Hiroyasu Nishiyama
a29527ec96 use implicit logging of context store 2018-07-20 23:26:47 +09:00
Nick O'Leary
45e7ad8049 Merge pull request #1831 from node-red-hitachi/update-info-of-file-node
Update info text of file node (English & Japanese)
2018-07-20 12:47:17 +01:00
Nick O'Leary
4d54663efd Merge pull request #1832 from node-red-hitachi/fix-isempty-rule
Fix bugs about "isEmpty" rule in Switch node
2018-07-20 12:45:53 +01:00
Nick O'Leary
29d386cc51 Merge pull request #1833 from node-red-hitachi/update-test-for-file-node
Update test for file node for new output port
2018-07-20 11:45:39 +01:00
Nick O'Leary
ba1a67969b Merge pull request #1825 from node-red-hitachi/add-types-to-trigger-node
Add support of bin, data, and env type to trigger node
2018-07-20 11:45:17 +01:00
Nick O'Leary
390ea5419e Merge pull request #1826 from node-red-hitachi/update-join-httpreq-info-jp
Update Japanese info text of JSON and HTTP Request node
2018-07-20 11:45:03 +01:00
Nick O'Leary
0fdeec7cc4 Merge pull request #1827 from node-red-hitachi/context-test-localfile
Add test cases for localfilesystem context
2018-07-20 11:44:39 +01:00
Nick O'Leary
e34d883e50 Merge pull request #1828 from node-red-hitachi/master-editorjson
Update Japanese translation (editor.json)
2018-07-20 11:44:19 +01:00
Nick O'Leary
507871687b Merge pull request #1830 from node-red-hitachi/translate-switch
Translate switch and batch nodes and icon palette
2018-07-20 11:44:13 +01:00
Nick O'Leary
ed58f62cd1 Merge pull request #1824 from node-red-hitachi/update-exec-node-info-jp
Update Japanese info text of exec node
2018-07-20 11:43:11 +01:00
Nick O'Leary
94bc4e7125 Merge pull request #1822 from node-red-hitachi/context-test-memory
Add test cases for memory context
2018-07-20 11:42:45 +01:00
Nick O'Leary
5832f7930d Merge pull request #1821 from node-red-hitachi/uitest-httprequest
Add UItest for http request
2018-07-20 11:42:28 +01:00
Nick O'Leary
0066a20c22 Merge pull request #1820 from node-red-hitachi/context-test-index
Add test cases for index.js of context
2018-07-20 11:42:10 +01:00
Nick O'Leary
774e4bfced Merge pull request #1819 from node-red-hitachi/fix-template-node-test
Fix test for template node for persistable context
2018-07-20 11:41:41 +01:00
Hiroyasu Nishiyama
054c7a76a4 update test for file node for new output port 2018-07-20 18:28:49 +09:00
HirokiUchikawa
c7f3b77aac Fix test cases of empty rule 2018-07-20 17:04:49 +09:00
HirokiUchikawa
a6a4620374 Fix an error that occurs when evaluating null on isEmpty rule. 2018-07-20 17:03:23 +09:00
HirokiUchikawa
5148c62d1c Fix appearance about Empty rules. 2018-07-20 16:53:30 +09:00
HirokiUchikawa
6fc863a91e Fix wrong test cases 2018-07-20 15:24:44 +09:00
Hiroyasu Nishiyama
e066a154a1 update info text of file node (English & Japanese) 2018-07-20 14:59:52 +09:00
nakanishi
d432edaed2 Translate icon palette parts into Japanese 2018-07-20 13:36:59 +09:00
nakanishi
8f34f4e80b Update Japanese translation for switch and batch nodes 2018-07-20 13:35:24 +09:00
nakanishi
39b751acf5 Add test cases for localfilesystem context 2018-07-20 11:23:37 +09:00
Kazuhito Yokoi
bd5e8ba961 Add test case of persistalbe context for switch node 2018-07-20 10:34:43 +09:00
HirokiUchikawa
ed20327c41 Update Japanese text of editor.json 2018-07-20 10:23:28 +09:00
Kazuhito Yokoi
991c68c394 Update Japanese translation (editor.json) 2018-07-20 10:23:27 +09:00
HirokiUchikawa
2acc31a4e7 Update Japanese info text of json node 2018-07-19 21:35:00 +09:00
Hiroyasu Nishiyama
b9733e3dfa add support of bin, data, and env to trigger node 2018-07-19 21:20:02 +09:00
Hiroyasu Nishiyama
bb106bfce7 small update of Japanese info text of exec node 2018-07-19 20:13:27 +09:00
HirokiUchikawa
daf1388a6a Update Japanese info text of http request node 2018-07-19 19:32:53 +09:00
Nick O'Leary
8226f1fa75 Merge pull request #1823 from node-red-hitachi/fix-referenceerror
Fix the ReferenceError in change node
2018-07-19 10:08:36 +01:00
HirokiUchikawa
e675512fa3 Fix ReferenceError in change node
and add a test case
2018-07-19 14:44:21 +09:00
Hiroyasu Nishiyama
65e67b6c3e add a space to align position of ":" 2018-07-19 14:18:27 +09:00
Hiroyasu Nishiyama
7612481570 ignore default store from logging 2018-07-19 14:12:01 +09:00
nakanishi
f6c7cb5804 Add test cases for global context of memory context 2018-07-19 13:49:36 +09:00
Yuma Matsuura
2201c9062f Add UItest for http request 2018-07-19 13:17:41 +09:00
nakanishi
ca3da262da Add test cases for index.js of context 2018-07-19 12:58:42 +09:00
Hiroyasu Nishiyama
5847f92bef fix test for template node for persistable context 2018-07-19 11:06:57 +09:00
Hiroyasu Nishiyama
31ee1be81e add logging of context store 2018-07-19 07:40:52 +09:00
Nick O'Leary
6bccdd015f Update changelog 2018-07-18 22:17:11 +01:00
Nick O'Leary
cecea318da Merge branch 'master' into 0.19 2018-07-18 13:22:35 +01:00
Nick O'Leary
8663ec6880 Merge pull request #1817 from node-red-hitachi/0.19-add-test-cases-for-inject-node
Add test cases for inject node
2018-07-18 11:35:51 +01:00
Nick O'Leary
9b03f128aa Merge pull request #1815 from node-red-hitachi/0.19-add-tests-for-function-node
Add persistable context tests for function node
2018-07-18 11:35:35 +01:00
Nick O'Leary
8a51f97616 Merge pull request #1816 from node-red-hitachi/0.19-update-info-text-for-function-node
update Japanese info text of function node
2018-07-18 11:35:17 +01:00
Hiroyasu Nishiyama
ee74ed9ce9 add test cases for inject node 2018-07-18 18:13:07 +09:00
Hiroyasu Nishiyama
0f947c756e update Japanese info text of function node 2018-07-18 17:17:23 +09:00
Hiroyasu Nishiyama
cae7949a48 add persistable context tests for function node 2018-07-18 16:43:12 +09:00
Nick O'Leary
be58b614e1 Index all node properties when searching
Fixes #1446
2018-07-17 20:58:10 +01:00
Nick O'Leary
b0a01fa4b2 Merge pull request #1813 from node-red-hitachi/0.19-jsonata-persistablecontext
Add context store support to JSONata functions
2018-07-17 20:34:53 +01:00
Nick O'Leary
9734228001 Merge branch 'boneskull-issue/1414' into 0.19 2018-07-17 20:29:36 +01:00
Nick O'Leary
9df1d44bc4 Merge branch 'issue/1414' of https://github.com/boneskull/node-red into boneskull-issue/1414 2018-07-17 20:28:40 +01:00
HirokiUchikawa
13d887028a Add test cases accessing context with JSONata to Sort Node 2018-07-17 18:43:10 +09:00
HirokiUchikawa
83a8979309 Add test cases accessing context with JSONata to Switch Node 2018-07-17 17:46:21 +09:00
Nick O'Leary
75c29f1cb7 Disallow store names that are not A-Za-z0-9_ 2018-07-16 16:44:33 +01:00
Nick O'Leary
d9d15e41c7 Support multiple stores in context sidebar 2018-07-16 16:36:05 +01:00
Nick O'Leary
d3598d5854 NLS Context sidebar 2018-07-16 13:17:18 +01:00
HirokiUchikawa
3a8aaee5d7 Add test cases accessing context with JSONata to Join Node 2018-07-16 18:42:16 +09:00
HirokiUchikawa
4fcf57d42c Add test cases accessing context with JSONata to Change Node 2018-07-16 18:25:03 +09:00
HirokiUchikawa
adb0891335 Allow the JSONata Expression to handle persistable store. 2018-07-16 18:00:57 +09:00
Nick O'Leary
d21e719cc1 Merge pull request #1812 from node-red-hitachi/0.19-add-env-var-support-for-split-node
Allow environment variable as reduce init value in split node
2018-07-16 09:38:26 +01:00
Nick O'Leary
5807ab82c1 Merge pull request #1811 from node-red-hitachi/0.19-fix-typo-in-utils
Fix typos in util.js
2018-07-16 08:36:13 +01:00
Hiroyasu Nishiyama
65cb04da63 fix typos in utils.js 2018-07-16 16:09:15 +09:00
Hiroyasu Nishiyama
312e3611b1 allow environment variable as reduce init value 2018-07-16 13:45:59 +09:00
Nick O'Leary
46acc62279 Make Trigger node timeout test 1ms more tolerable 2018-07-15 21:13:02 +01:00
Nick O'Leary
529b358c9b Split out expandable editors and add JS editor 2018-07-15 21:06:51 +01:00
Nick O'Leary
7fca04404e Fix debug test for _enc_ change 2018-07-14 23:18:55 +01:00
Nick O'Leary
3a1cc6a2be Change __encoded__ to __enc__ for debug message encoding 2018-07-14 23:06:15 +01:00
Nick O'Leary
5b76c91004 Merge pull request #1806 from node-red-hitachi/0.19-template-node-for-persistable-context
Add support of persistable context to template node
2018-07-14 22:40:50 +01:00
Nick O'Leary
cf87837f7d Merge pull request #1810 from node-red-hitachi/0.19-fix-inject-node-notification-for-persistable-context
Fix context access appearance of inject node in editor
2018-07-14 22:40:19 +01:00
Nick O'Leary
5a0a7b907b Merge pull request #1809 from node-red-hitachi/0.19-fix-tests-for-trigger-node
Add multiple persistable store tests for trigger node
2018-07-14 22:39:05 +01:00
Nick O'Leary
6a2b1669b3 Merge pull request #1808 from node-red-hitachi/0.19-fix-tests-for-inject-node
Add multiple persistable store tests for inject node
2018-07-14 22:38:58 +01:00
Nick O'Leary
ca8264b3f4 Merge pull request #1807 from node-red-hitachi/0.19-fix-tests-for-change-node
Add multiple persistable store tests for change node
2018-07-14 22:38:17 +01:00
Hiroyasu Nishiyama
b44ecd8819 fix context access appearance of inject node in editor 2018-07-14 14:47:40 +09:00
Hiroyasu Nishiyama
987942959e empty commit to rerun travis 2018-07-14 12:37:30 +09:00
Hiroyasu Nishiyama
91992b48c1 add multiple persistable store tests 2018-07-14 12:15:26 +09:00
Hiroyasu Nishiyama
c9a335a6f9 add multiple persistable store tests 2018-07-14 11:50:49 +09:00
Hiroyasu Nishiyama
b7ed159b50 add multiple persistable context tests 2018-07-14 11:00:57 +09:00
Hiroyasu Nishiyama
c72961a52a add support of persistable context to template node 2018-07-14 00:11:59 +09:00
Nick O'Leary
afe6afca36 Merge pull request #1801 from node-red-hitachi/0.19-multi-values
Make it possible to set multiple values
2018-07-13 14:03:03 +01:00
HirokiUchikawa
050acd239c Allow arrays of different lengths to be passed to set. 2018-07-13 20:59:45 +09:00
Nick O'Leary
24505ee4f5 Merge pull request #1803 from kazuhitoyokoi/0.19-addtestcases
Add test cases of persistable context for trigger node
2018-07-13 11:59:40 +01:00
Nick O'Leary
63a249aba3 Merge pull request #1802 from kazuhitoyokoi/0.19-updatejapanese
Update Japanese language files (messages.json and jsonata.json)
2018-07-13 11:59:09 +01:00
Nick O'Leary
7bd94df2a0 Merge pull request #1804 from node-red-hitachi/0.19-fix-cache-error
Fix the error that the parent directory of the context does not exist
2018-07-13 11:58:14 +01:00
Kazuhito Yokoi
761161a8e5 Fix async problem in test cases 2018-07-13 17:34:04 +09:00
Kazuhito Yokoi
513579a7ee Empty commit to run travis again 2018-07-13 15:53:47 +09:00
Kazuhito Yokoi
7165483d83 Empty commit to run travis again 2018-07-13 15:40:21 +09:00
Kazuhito Yokoi
f8bcf219cb Empty commit to run travis again 2018-07-13 15:37:50 +09:00
Kazuhito Yokoi
590506e306 Add test cases of persistable context for trigger node 2018-07-13 15:26:07 +09:00
Kazuhito Yokoi
9a5439c580 Update Japanese language files (message.json and jsonata.json) 2018-07-13 15:17:30 +09:00
HirokiUchikawa
6b2f5fbb19 Allow multiple keys and values to be passed to set 2018-07-12 19:19:55 +09:00
Nick O'Leary
051c147b41 Merge pull request #1800 from node-red-hitachi/0.19-callback-called-twice
Prevent the callback to be called twice
2018-07-12 10:45:58 +01:00
HirokiUchikawa
9111adf15f Use ensureDir() insted of mkdir()
and add test case
2018-07-12 18:20:47 +09:00
HirokiUchikawa
ba18b27371 Prevent the callback to be called twice
and add test cases
2018-07-12 18:12:30 +09:00
Nick O'Leary
2a287b2ae6 Merge pull request #1796 from node-red-hitachi/0.19-multiple-values
Make it possible to get multiple values
2018-07-12 10:01:18 +01:00
Nick O'Leary
fc9040f715 Merge pull request #1799 from kazuhitoyokoi/0.19-addtestcases4persistablecontext
Add test cases of persistable context for inject node
2018-07-12 08:41:27 +01:00
Nick O'Leary
0029022ef6 Merge pull request #1797 from node-red-hitachi/0.19-uitest-flowcontrol
Added test cases of flow control on cookbook
2018-07-12 08:40:30 +01:00
Nick O'Leary
94f728d3fd Merge pull request #1798 from kazuhitoyokoi/0.19-updatelanguagefiles
Update Japanese language file (editor.json)
2018-07-12 08:39:29 +01:00
Kazuhito Yokoi
d53ced7830 Add test cases of persistable context for inject node 2018-07-12 16:26:16 +09:00
Kazuhito Yokoi
053d8c44c2 Update Japanese language file (editor.json) 2018-07-12 16:14:25 +09:00
nakanishi
9f5767ea16 Added test cases of flow control on cookbook 2018-07-12 15:28:40 +09:00
HirokiUchikawa
e8d76b0555 Allow multiple values to be passed to get 2018-07-12 14:05:36 +09:00
Nick O'Leary
c2675600f6 Fix Switch msg sequence test 2018-07-11 16:37:18 +01:00
Nick O'Leary
6f087b4ec1 Merge pull request #1795 from node-red-hitachi/0.19-fix-change-for-persistable-context
Fix persistable context handling of switch node
2018-07-11 16:17:26 +01:00
Nick O'Leary
e94708606d Add isEmpty check to switch node 2018-07-11 16:14:09 +01:00
Hiroyasu Nishiyama
c248f1a762 fix persistable context handling of switch node 2018-07-11 23:39:34 +09:00
Nick O'Leary
28402b0894 Add sidebar tab icons to drop-down menu 2018-07-11 14:15:31 +01:00
Nick O'Leary
7dd98e99f9 Node errors should be Strings not Errors
Fixes #1781
2018-07-11 13:40:53 +01:00
Nick O'Leary
dba195b396 Add detection of connection timeout in git communication
Fixes #1770
2018-07-11 13:26:45 +01:00
Nick O'Leary
88b153bc12 Merge pull request #1794 from kazuhitoyokoi/0.19-fixi18n
Remove and change keys in language files
2018-07-11 13:25:08 +01:00
Kazuhito Yokoi
d4a47dc974 Empty commit to run travis again 2018-07-11 16:13:32 +09:00
Kazuhito Yokoi
fe3ea6edfd : 2018-07-11 16:12:56 +09:00
Kazuhito Yokoi
6c8fc4846b Fix i18n bugs in projects 2018-07-11 15:33:25 +09:00
Nick O'Leary
54d9656f09 Add servername option to TLS config node for SNI 2018-07-10 23:24:32 +01:00
Nick O'Leary
49da324c5d Fix jsonata err reporting in sort node 2018-07-10 17:26:54 +01:00
Nick O'Leary
9bf87697fd Merge pull request #1780 from natcl/json-schema
Add JSON schema validation to JSON node
2018-07-10 17:06:17 +01:00
Nathanaël Lécaudé
f368f5a9c4 JSON node: Add link to JSON schema spec in node help 2018-07-10 11:29:01 -04:00
Nathanaël Lécaudé
eea85485e6 Merge remote-tracking branch 'upstream/0.19' into json-schema 2018-07-10 11:11:15 -04:00
YumaMatsuura
1a544b3b82 Headless option for ui test (#1784) 2018-07-10 12:42:56 +01:00
Kazuhito Yokoi
8b38fe9fe0 Support i18n in websocket node (#1785) 2018-07-10 12:42:32 +01:00
Hiroki Uchikawa
1bf4addf63 Fix an error when initializing the cache (#1788)
* Fix a error when initializing the cache

* Make context directory if it is not there  in initialization
2018-07-10 12:41:16 +01:00
Hiroyasu Nishiyama
407e16e900 Fix appearrence of switch node port label for flow/global ref. (#1793)
* fix appearrence of switch node port label for flow/global ref

* use RED.utils.parseContextKey
2018-07-10 12:40:52 +01:00
Hiroyasu Nishiyama
6e9fe3248a Fix appearrence of change node label for flow/global ref (#1792)
* fix appearence of change node label for flow/global ref

* use RED.utils.parseContextKey
2018-07-10 12:40:31 +01:00
Nick O'Leary
d8cf86fd6f Add RED.utils.parseContextKey 2018-07-10 11:41:46 +01:00
Nick O'Leary
f8aa4a9588 Merge branch 'async-split' into 0.19 2018-07-10 11:30:38 +01:00
Nick O'Leary
c249907846 Merge pull request #1791 from node-red/join-node-keep-top-level-properties
join-node-keep-top-level-properties
2018-07-10 11:28:55 +01:00
Nick O'Leary
57c1524a9a Add async jsonata support to join node 2018-07-10 11:24:57 +01:00
Nick O'Leary
d8d82e2ba3 Update sort node for async use of jsonata 2018-07-09 23:06:51 +01:00
Nick O'Leary
807b512ef7 Add JSONata async support to Switch and Change nodes 2018-07-09 21:56:39 +01:00
Nick O'Leary
b2f06b6777 Add async mode to evaluateJSONataExpression 2018-07-09 15:12:09 +01:00
Nick O'Leary
d7adff9a65 Add async message handling to Trigger node 2018-07-09 14:12:44 +01:00
Nick O'Leary
b0d7e11d48 Fix evaluateNodeProperty handling of unknown types 2018-07-09 12:40:25 +01:00
Nick O'Leary
fc9cdb61f2 Add async property handling to Switch node 2018-07-09 11:31:10 +01:00
Nick O'Leary
9c00492dc2 WIP: create async Switch node helper functions 2018-07-09 11:31:10 +01:00
Nick O'Leary
1a6babd199 Lint Switch code 2018-07-09 11:31:10 +01:00
Nick O'Leary
1b693eed37 Add async context support to Change node 2018-07-09 11:31:10 +01:00
Nick O'Leary
afb566b6b4 Add async context support to Inject node 2018-07-09 11:31:10 +01:00
Dave Conway-Jones
f870e9ed3e Let Join node accumulate top level properties
Last in is still most significant
2018-07-08 16:52:30 +01:00
Dave Conway-Jones
4bcf13cb58 Let nrgpio code work with python 3
(just in case that becomes default)
2018-07-07 19:01:14 +01:00
Nick O'Leary
946a6d6041 Update RED.util.evaluateNodeProperty to support context stores 2018-07-05 10:43:33 +01:00
Nick O'Leary
372c213c2c Still parse/export typedInput values even when no options set 2018-07-04 14:23:18 +01:00
Nick O'Leary
a5a79d3ab7 Merge branch '0.19' into typedInput-context 2018-07-04 13:37:05 +01:00
Nick O'Leary
e6c5cfb703 Do not show TypedInput context options if there's only one available 2018-07-04 13:36:23 +01:00
Nick O'Leary
7843eccae8 Merge pull request #1786 from node-red-hitachi/0.19-fix-typedInput-error
Fix typedInput error on initialization
2018-07-04 13:35:55 +01:00
Hiroyasu Nishiyama
7ca153abd0 fix error on typedInput initialization 2018-07-04 20:50:33 +09:00
Nick O'Leary
33b4774c49 Load typedinput context list from settings 2018-07-03 21:40:58 +01:00
Nick O'Leary
c243481432 Add sub options to Inject node 2018-07-03 21:40:58 +01:00
Nick O'Leary
80873e4ea9 fix settings api test for context stores 2018-07-03 21:27:55 +01:00
Nick O'Leary
4e4a1f11e6 Fix context admin api for empty contexts 2018-07-03 21:18:43 +01:00
Nick O'Leary
9bbe405cd0 Do not show blank popovers 2018-07-03 21:18:15 +01:00
Nick O'Leary
c440a4c730 Expose list of context stores to the editor 2018-07-03 14:17:42 +01:00
Nick O'Leary
a1251371d7 Avoid unnecessary re-reading of file context when caching is enabled 2018-07-03 11:29:45 +01:00
Nick O'Leary
7d702e8332 Remove console.log 2018-07-02 22:38:37 +01:00
Nick O'Leary
43d7c8d48c Add caching to localfilesystem context 2018-07-02 22:32:20 +01:00
Nick O'Leary
7423583508 Create default store for node tests to use 2018-07-02 15:47:47 +01:00
Nick O'Leary
08b0838f9a Fix linting in view.js 2018-07-02 15:32:29 +01:00
Nick O'Leary
038d821a7c Apply fGC to all global contexts for default values 2018-07-02 15:21:13 +01:00
Nathanaël Lécaudé
6a218814d3 Merge remote-tracking branch 'upstream/0.19' into json-schema 2018-06-30 16:20:13 -07:00
Nathanaël Lécaudé
905f89b0f5 JSON node: finalize JSON Schema validation 2018-06-30 16:19:39 -07:00
Nick O'Leary
14882bda78 Ensure runtime errors in Change node can be caught
Fixes #1769
2018-06-29 11:50:16 +01:00
Nick O'Leary
781fa4634b Merge pull request #1777 from node-red-hitachi/uitest-typedinput
Follow the change of typedinput interface for UI test
2018-06-29 10:53:27 +01:00
Nick O'Leary
cdb173fd6e Handle NaN and Infinity properly in debug sidebar
Fixes #1778 #1779
2018-06-29 10:50:07 +01:00
Nick O'Leary
466cb4be89 Small tidy up on context plugin loading 2018-06-29 09:48:38 +01:00
Nathanaël Lécaudé
c39e2ffd56 JSON node: add JSON schema validation via msg.schema 2018-06-28 23:16:43 -07:00
Kazuki-Nakanishi
17bf09e276 Follow the change of typedinput interface for UI test 2018-06-29 10:41:44 +09:00
Nick O'Leary
bc01f9f8fd add placeholder api/admin/context_spec 2018-06-28 17:00:17 +01:00
Nick O'Leary
c0870c5694 Merge branch '0.19' into context-tab 2018-06-27 16:05:17 +01:00
Nick O'Leary
3b5174a2ea Merge branch '0.19' of github.com:node-red/node-red into 0.19 2018-06-27 15:56:57 +01:00
Nick O'Leary
af6885f3e8 Merge pull request #1720 from node-red-hitachi/persistablecontext
Add persistable context backend
2018-06-27 15:37:46 +01:00
Nick O'Leary
e01996095f Add refresh timestamp to context sidebar 2018-06-27 10:00:23 +01:00
Nick O'Leary
5d86f7b6ba Refresh context sidebar tab based on selection 2018-06-26 23:34:32 +01:00
Nick O'Leary
8d6ac6406d Initial context sidebar tab 2018-06-26 11:32:24 +01:00
HirokiUchikawa
40ff54f67e Improve context storage handling 2018-06-26 11:43:37 +09:00
HirokiUchikawa
cce7ac09d0 Add callback handling to memory plugin 2018-06-26 11:36:37 +09:00
Nick O'Leary
73a18891c5 Add RED.popover.tooltip for common reuse 2018-06-25 22:32:34 +01:00
Nick O'Leary
fe22cedc1d Move debug encode/decode to utils for reuse 2018-06-25 22:32:34 +01:00
Nick O'Leary
fa09c7c8b2 Merge branch '0.19' of github.com:node-red/node-red into 0.19 2018-06-25 13:55:21 +01:00
Nick O'Leary
15e28e3cc0 Merge branch 'pr_1766' into 0.19 2018-06-25 13:55:07 +01:00
Nick O'Leary
2cb4f6b1fc Prevent horizontal scroll when palette name cannot wrap 2018-06-25 13:54:34 +01:00
Nick O'Leary
b17a483b85 Merge pull request #1771 from node-red-hitachi/0.19-fix-i18n-project-message
Fix appearance of retry button of remote branch management dialog
2018-06-25 13:50:11 +01:00
Nick O'Leary
7c3e5443ab Merge pull request #1772 from node-red-hitachi/uitest-debugtab
Follow the change of tab interface for UI test
2018-06-25 13:37:53 +01:00
Nick O'Leary
f95a2851c8 Ignore middle-click on node/ports to enable panning 2018-06-25 13:36:58 +01:00
Nick O'Leary
2b2eee352f Better wire layout when looping back 2018-06-25 13:18:37 +01:00
Kazuki-Nakanishi
11569d8056 Follow the change of tab interface for UI test 2018-06-25 18:48:01 +09:00
Nick O'Leary
bdf87452b6 Reset typedInput option when type changes 2018-06-25 10:39:20 +01:00
Hiroyasu Nishiyama
0c6bf81c24 fix appearence of retry button of remote branch management dialog 2018-06-23 12:49:09 +09:00
HirokiUchikawa
f2fa26fb07 Use the callback instead of Promise in context API
and remove unnecessary functions
2018-06-22 17:11:54 +09:00
Nick O'Leary
f5e212ff1e Refresh type options properly when typedInput.types called 2018-06-21 10:49:39 +01:00
Nick O'Leary
461e6562ca Allow typedInputs to have options plus value 2018-06-21 10:47:30 +01:00
HirokiUchikawa
fd67d08402 Remove unnecessary module
and skip persistable context test cases temporally
2018-06-20 20:09:02 +09:00
HirokiUchikawa
e6411d11b1 Remove unnecessary context storage APIs
and rename context storage APIs
2018-06-20 20:00:39 +09:00
HirokiUchikawa
dd81d947fc Use native Promise instead of when.js 2018-06-20 19:50:55 +09:00
HirokiUchikawa
23b887c30e Add a test case for context/index 2018-06-20 19:42:09 +09:00
HirokiUchikawa
c4eae3f130 Fix file extension 2018-06-20 19:42:08 +09:00
HirokiUchikawa
41a04a2849 Add async API to context
and add test cases for async
2018-06-20 19:42:07 +09:00
HirokiUchikawa
ed1d34e678 Use fs-extra instead of node-json-db 2018-06-20 19:42:06 +09:00
HirokiUchikawa
f44487338d Fix a wrong statement 2018-06-20 19:42:05 +09:00
Hiroki Uchikawa
7aced85a31 Use Array.indexOf() instead of Array.includes() 2018-06-20 19:42:04 +09:00
HirokiUchikawa
fbe0e2d6eb Delete async function in context/index 2018-06-20 19:42:03 +09:00
HirokiUchikawa
6e34f0697c Allow .get/set/keys to return asynchronous results 2018-06-20 19:42:02 +09:00
HirokiUchikawa
a835f9f0cb Fix ENOENT error in LocalFileSystem.clean() 2018-06-20 19:42:01 +09:00
HirokiUchikawa
16715673c3 Add test case 2018-06-20 19:42:00 +09:00
HirokiUchikawa
c48c74f173 Delete unused variables 2018-06-20 19:42:00 +09:00
HirokiUchikawa
f262348497 Add clean to context plugin
and don't delete local context unless the context is deleted by a user
2018-06-20 19:41:59 +09:00
HirokiUchikawa
7185bcd51f Add open/close API for context 2018-06-20 19:41:58 +09:00
HirokiUchikawa
28d05e2449 Allow multiple instances of a given storage module to exist 2018-06-20 19:41:57 +09:00
Hiroki Uchikawa
7fafa21a1b Change the order of arguments 2018-06-20 19:41:56 +09:00
HirokiUchikawa
84f598e143 Change prefix from $ to # 2018-06-20 19:41:51 +09:00
HirokiUchikawa
e30f8628db Revert runtime/util 2018-06-20 19:41:02 +09:00
Hiroki Uchikawa
0be9c88106 Improve processing when default is an alias
and fix test cases
2018-06-20 19:41:01 +09:00
Kazuki-Nakanishi
e046fc1ac5 Refactor parseKey and implement parseStorage 2018-06-20 19:41:00 +09:00
Kazuki-Nakanishi
3a476ac493 Implemented error handlings 2018-06-20 19:40:54 +09:00
Hiroki Uchikawa
e33ec0cf50 update external context
- Implement `delete` function
- Swap default easily
- Change memory context as a plugin
- Update localfilesystem plugin
  -  Change file/folder structure
2018-06-20 19:40:26 +09:00
Hiroki Uchikawa
b4b70a988e Change delimiter to "_" from ":" 2018-06-20 19:40:25 +09:00
Hiroki Uchikawa
e66b381070 add external context files 2018-06-20 19:40:25 +09:00
Hiroki Uchikawa
771b598c09 Add persistable context
and avoid exception when arg is undefined in util/getMessageProperty
2018-06-20 19:40:24 +09:00
Hiroki Uchikawa
cd44f13171 Move context_spec.js to context folder
and rename context_spec.js -> index_spec.js
2018-06-20 19:40:23 +09:00
Hiroki Uchikawa
aa6b72ac87 Move context.js to context folder
and rename context.js -> index.js
2018-06-20 19:40:22 +09:00
Nick O'Leary
a467fe5ed7 Reposition tab menu before opening 2018-06-19 10:49:50 +01:00
Hiroyasu Nishiyama
467411c6c3 merge 0.19 2018-06-17 01:26:31 +09:00
Nick O'Leary
2648b7ca54 Handle releasing ctrl when using quick-add node dialog 2018-06-15 22:33:53 +01:00
Nick O'Leary
de35c7024a Tab buttons should use editor-button-toggle style 2018-06-15 17:28:29 +01:00
Dave Conway-Jones
9d219c163d Don't accidentally re-use udp port when set to not do so
to close Issue #1764
2018-06-15 14:53:02 +01:00
Nick O'Leary
f7434b5ec8 Add output to File Out node and update icons 2018-06-15 13:25:28 +01:00
Nick O'Leary
5ed3360c0b Fix css for single toggle buttons 2018-06-15 13:25:28 +01:00
Dave Conway-Jones
6f5974f875 Fix join node manual mode array
msg.complete was adding an unwanted null to the array (if no payload)
Added tests for msg.complete with array and object
2018-06-14 20:00:42 +01:00
Nick O'Leary
56db1da3cf Merge pull request #1732 from node-red/pi-nodes-editable-when-na
let Pi nodes be visible/editable on all platforms
2018-06-13 15:46:21 +01:00
Nick O'Leary
fef71f29c4 Merge pull request #1750 from node-red-hitachi/logic-nodes-test
Add test cases for logic nodes
2018-06-13 15:45:13 +01:00
Nick O'Leary
d46b66878a Show unknown node properties in info tab 2018-06-13 14:56:09 +01:00
Nick O'Leary
6cad80c4ad Add node icon picker widget 2018-06-12 23:46:06 +01:00
Nick O'Leary
68779caa2e Only edit nodes on dbl click on primary button with no modifiers 2018-06-12 15:34:08 +01:00
Nick O'Leary
2a122ed283 Allow subflows to be put in any palette category 2018-06-12 12:54:32 +01:00
Nick O'Leary
17c5fdf0d5 Add flow navigator widget 2018-06-08 23:32:17 +01:00
Nick O'Leary
f6274445a2 Merge branch 'master' into 0.19 2018-06-06 21:41:48 +01:00
Nick O'Leary
3b0300b834 Cache flow library result to improve response time
Fixes #1753
2018-06-06 21:38:44 +01:00
Nick O'Leary
4fbf1fe780 Add middle-button-drag to pan the workspace 2018-06-06 20:51:30 +01:00
Hiroyasu Nishiyama
dcf44fed58 allow multi-line category name in editor 2018-06-05 20:18:40 +09:00
Nick O'Leary
7136dc1c72 Merge pull request #1749 from kazuhitoyokoi/0.19
Add i18n support for projectSettings.js
2018-06-04 23:32:55 +01:00
Kazuhito Yokoi
0e4cedbc5e Remove new lines 2018-06-04 07:21:48 +09:00
Kazuhito Yokoi
dc139bcc30 Remove new line 2018-06-04 07:15:26 +09:00
KatsuyaHoshii
b204b183de Add logic nodes test cases 2018-06-01 14:33:20 +09:00
Kazuhito Yokoi
ab788bc1e3 Add i18n support for projectSettings.js 2018-06-01 12:58:09 +09:00
Nick O'Leary
95b4c8d515 Merge pull request #1748 from node-red-hitachi/0.19-editor-diff-i18n-jp
Add i18n support for project
2018-05-31 10:44:36 +01:00
Nick O'Leary
b025644525 Merge pull request #1744 from node-red-hitachi/0.19-i18n-defaultFileSet
Add i18n support for default file set for a project
2018-05-31 08:51:53 +01:00
Yuma Matsuura
9e87a60597 modify translate project diff 2018-05-31 16:47:03 +09:00
Yuma Matsuura
5a70bea67a add translate project diff 2018-05-31 16:46:46 +09:00
Hiroyasu Nishiyama
2a95af3928 merged 0.19-allow-i18n-translation-in-runtime 2018-05-30 21:59:20 +09:00
Nick O'Leary
0a0ca380d3 Ensure apiMaxLength applies to HTTP Nodes
Fixes #1278
2018-05-30 13:32:38 +01:00
Nick O'Leary
4cfbf7f71c Merge pull request #1743 from node-red-hitachi/0.19-allow-i18n-translation-in-runtime
Allow i18n translation in runtime
2018-05-30 13:20:05 +01:00
Hiroyasu Nishiyama
de43148341 change current_locale to getCurrentLocale 2018-05-30 20:32:56 +09:00
Nick O'Leary
0a2aab7d68 Merge pull request #1746 from node-red-hitachi/0.19-user-settings-projects-i18n-jp
Modify i18n support for user settings of project
2018-05-30 10:30:25 +01:00
Nick O'Leary
745821c420 Merge pull request #1745 from node-red-hitachi/0.19-version-projects-i18n-jp
Add i18n support for version control of project
2018-05-30 10:30:12 +01:00
Nick O'Leary
57c4c754d0 Merge pull request #1742 from node-red-hitachi/0.19-editor-main-i18n-jp
Update i18n support for main editor interface and Japanese message …
2018-05-30 10:22:38 +01:00
Nick O'Leary
4b5c437533 Merge pull request #1741 from node-red-hitachi/0.19-editor-projects-i18n-jp
Add i18n support for projects interface and Japanese message catalogue
2018-05-30 10:21:29 +01:00
Nick O'Leary
8d63b6a1ed Merge pull request #1734 from node-red-hitachi/0.19-fix-icon-scan-test-for-win
Fix test failure of icon scan on Windows
2018-05-30 10:18:57 +01:00
Nick O'Leary
245a8adbf9 Merge pull request #1736 from node-red-hitachi/0.19-httpreq
Move to request module
2018-05-30 10:18:21 +01:00
Yuma Matsuura
4f7d98aace modify translate user settings 2018-05-29 10:12:52 +09:00
Kazuki-Nakanishi
b0c693cc3a move comment from json to js 2018-05-28 17:06:38 +09:00
Kazuki-Nakanishi
b2cca10e8b Add i18n support for version control of project 2018-05-28 17:01:53 +09:00
Hiroyasu Nishiyama
a84b2ab5bb update defaultFileSet test for i18n support 2018-05-27 22:30:05 +09:00
Hiroyasu Nishiyama
4565342b05 add i18n support for project default files generation 2018-05-27 01:38:54 +09:00
Hiroyasu Nishiyama
0ad54cc2d1 allow i18n translation in runtime 2018-05-27 01:05:50 +09:00
Hiroyasu Nishiyama
865853da19 add some i18n support for main editor interface and Japanese message catalogue 2018-05-26 20:08:39 +09:00
Hiroyasu Nishiyama
392ed706fd add i18n support for projects interface and Japanese message catalogue 2018-05-26 14:21:30 +09:00
Nick O'Leary
0ff0f25aaf Merge branch 'master' into 0.19 2018-05-25 13:58:15 +01:00
Nick O'Leary
c157960846 Change debug sidebar icon 2018-05-25 13:55:35 +01:00
YumaMatsuura
a5c00b5c81 add translate-user-settings (#1740) 2018-05-25 13:55:03 +01:00
Nick O'Leary
472bbdb59f Fix typo in CHANGELOG 2018-05-25 13:27:58 +01:00
Nick O'Leary
7877093713 Bump 0.18.7 2018-05-25 13:23:18 +01:00
Nick O'Leary
8cb2e51407 Relax twitter node version ready for major version bump 2018-05-25 11:40:14 +01:00
Nick O'Leary
d5cee81fb6 Merge branch 'pr_1739' 2018-05-25 11:37:37 +01:00
Nick O'Leary
bca020bc4d Tidy up default grunt task and fixup test break due to reorder
Fixes #1738
2018-05-25 11:36:17 +01:00
Nick O'Leary
5069f2844c Bump jsonata version 2018-05-25 10:55:44 +01:00
Nick O'Leary
252df81f59 Pass Date into the Function node sandbox to fix instanceof tests 2018-05-25 10:55:44 +01:00
KatsuyaHoshii
7f89a4a26f Update .travis.yml 2018-05-25 11:48:33 +09:00
Dave Conway-Jones
40f4167894 let TCP in node report remote ip and port when in single packet mode 2018-05-24 21:39:46 +01:00
Nick O'Leary
0ef16989cd Do not trim wires if node declares outputs in defaults but misses value
Fixes #1737
2018-05-24 20:27:07 +01:00
Dave Conway-Jones
3df3d6f516 add debug to trigger test to help work out fails 2018-05-24 10:02:51 +01:00
Edward Vielmetti
10395ef254 typo fix *hierarchy (#1735) 2018-05-24 09:48:05 +01:00
Hiroyasu Nishiyama
83854c28db fix test failure of icon scan on windows 2018-05-24 12:06:39 +09:00
Nick O'Leary
fcbea2629c Support flow.disabled and .info in /flow API 2018-05-23 22:41:39 +01:00
Nick O'Leary
26bc142cc2 Handle loading empty nodesDir 2018-05-23 10:59:08 +01:00
Nick O'Leary
a4eb8e11c3 Collapse sidebar tabs 2018-05-23 10:25:47 +01:00
HirokiUchikawa
9fd5d1db56 Move to request module 2018-05-23 17:16:20 +09:00
Dave Conway-Jones
1d05b4c981 relax test spec slightly 2018-05-23 08:58:04 +01:00
HirokiUchikawa
61f6535be8 Add test case for preventing following redirect 2018-05-23 16:54:03 +09:00
Dave Conway-Jones
7dd329b5ee Add basic loading tests for GPIO nodes 2018-05-22 17:26:52 +01:00
Dave Conway-Jones
b761904424 let Pi nodes be visible/editable on all platforms
even where they are not physically available.
2018-05-22 15:48:24 +01:00
Nick O'Leary
36105412b1 Add 'private' property to userDir generated package.json
This stops the warnings from npm about missing repo and license fields.
As there's no expectation for a user to publish their userDir to npm, then
setting private is entirely appropriate.
2018-05-22 11:41:22 +01:00
Nick O'Leary
184b1b018c Add missing resource file 2018-05-21 22:38:07 +01:00
Nick O'Leary
f3e1b85d82 Add RED.require to allow nodes to access other modules 2018-05-21 22:08:04 +01:00
Nick O'Leary
626d012775 Do not disable the export-clipboard menu option with empty selection 2018-05-21 16:14:43 +01:00
Nick O'Leary
9ad9c0ec6a Add $env function to JSONata expressions 2018-05-21 15:28:15 +01:00
Nick O'Leary
e13fed9fc6 Widen support for env var to use ${} or $() syntax 2018-05-21 15:19:50 +01:00
Nick O'Leary
eb6d093e56 Add env-var support to TypedInput 2018-05-21 15:10:06 +01:00
Hiroyasu Nishiyama
af1ea610ea allow id and name reference in function node code (#1731) 2018-05-21 11:34:56 +01:00
Christopher Hiller
6e2e36e7a0 tcp: queue messages while connecting; closes #1414
- queues messages on a per-client basis while waiting for TCP server
  connection
- add `denque` package for performance (`shift()` happens in constant
  instead of `Array`'s linear time)
- add tests
- remove a duplicate test in `31-tcp_request.spec.js`
- cap queue at value specified in settings (`tcpMsgQueueSize`); default
  to 1000
- add `tcpMsgQueueSize` to `settings.js`

Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
2018-03-20 13:45:44 -07:00
Christopher Hiller
9994df9601 tcprequest tests: normalize indents 2018-03-20 13:45:44 -07:00
186 changed files with 16372 additions and 3794 deletions

View File

@@ -4,7 +4,7 @@ matrix:
include:
- node_js: "10"
script:
- istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
- ./node_modules/.bin/grunt && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage
before_script:
- npm install -g istanbul coveralls
- node_js: "8"

View File

@@ -1,3 +1,132 @@
#### 0.19.4: Maintenance Release
- Fix race condition in non-cache lfs context Fixes #1888
- LocalFileSystem Context: Remove extra flush code
- Prevent race condition in caching mode of lfs context (#1889)
- Allow context store name to be provided in the key
- Switch node: only use promises when absolutely necessary
- Fix dbl-click handling on webkit-based browsers
- Ensure context.flow/global cannot be deleted or enumerated
- Handle context.get with multiple levels of unknown key Fixes #1883
- Fix global.get("foo.bar") for functionGlobalContext set values
- Fix node color bug (#1877)
- Merge pull request #1857 from cclauss/patch-1
- Define raw_input() in Python 3 & fix time.sleep()
#### 0.19.3: Maintenance Release
- Split node - fix complete to send msg for k/v object
- Remove unused Join node merged object key typed input
- Set the JavaScript editor to full-screen
- Filter global modules installed locally
- Add svg to permitted icon extension list
- Debug node - indicate status all the time if selected to do so
- pi nodes - increase test coverage slightly
- TCP-request node - only write payload
- JSON schema: perform validation when obj -> obj or str -> str
- JSON schema: add draft-06 support (via $schema keyword)
- Mqtt proxy configuration for websocket connection, #1651.
- Allows MQTT Shared Subscriptions for MQTT-In core node
- Fix use of HTML tag or CSS class specification as icon of typedInput
#### 0.19.2: Maintenance Release
- Ensure node default colour is used if palette.theme has no match
- fix lost messages / properties in TCPRequest Node; closes #1863 (#1864)
- Fix typo in template.html
- Improve error reporting from context plugin loading
- Prevent no-op edit of node marking as changed due to icon
- Change node must handle empty rule set
#### 0.19.1: Maintenance Release
- Pull in latest twitter node
- Handle windows paths for context storage
- Handle persisting objects with circular refs in context
- Ensure js editor can expand to fill available space
- Add example localfilesystem contextStorage to settings
- Fix template node handling of nested context tags
#### 0.19: Milestone Release
Editor
- Add editorTheme.palette.theme to allow overriding colours
- Index all node properties when searching Fixes #1446
- Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779
- Prevent horizontal scroll when palette name cannot wrap
- Ignore middle-click on node/ports to enable panning
- Better wire layout when looping back
- fix appearence of retry button of remote branch management dialog
- Handle releasing ctrl when using quick-add node dialog
- Add $env function to JSONata expressions
- Widen support for env var to use ${} or $() syntax
- Add env-var support to TypedInput
- Show unknown node properties in info tab
- Add node icon picker widget
- Only edit nodes on dbl click on primary button with no modifiers
- Allow subflows to be put in any palette category
- Add flow navigator widget
- Cache flow library result to improve response time Fixes #1753
- Add middle-button-drag to pan the workspace
- allow multi-line category name in editor
- Redesign sidebar tabs
- Do not disable the export-clipboard menu option with empty selection
Nodes
- Change: Ensure runtime errors in Change node can be caught Fixes #1769
- File: Add output to File Out node
- Function: add expandable JavaScript editor pane
- Function: allow id and name reference in function node code (#1731)
- HTTP Request: Move to request module
- HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278
- Join: accumulate top level properties
- Join: allow environment variable as reduce init value
- JSON: add JSON schema validation via msg.schema
- Pi: Let nrgpio code work with python 3
- Pi: let Pi nodes be visible/editable on all platforms
- Switch: add isEmpty rule
- TCP: queue messages while connecting; closes #1414
- TLS: Add servername option to TLS config node for SNI Fixes #1805
- UDP: Don't accidentally re-use udp port when set to not do so
Persistent Context
- Add Context data sidebar
- Add persistable context option
- Add default memory store
- Add file-based context store
- Add async mode to evaluateJSONataExpression
- Update RED.util.evaluateNodeProperty to support context stores
Runtime
- Support flow.disabled and .info in /flow API
- Node errors should be Strings not Errors Fixes #1781
- Add detection of connection timeout in git communication Fixes #1770
- Handle loading empty nodesDir
- Add 'private' property to userDir generated package.json
- Add RED.require to allow nodes to access other modules
- Ensure add/remove modules are run sequentially
#### 0.18.7: Maintenance Release
Editor Fixes
- Do not trim wires if node declares outputs in defaults but misses value Fixes #1737
Node Fixes
- Relax twitter node version ready for major version bump
- Pass Date into the Function node sandbox to fix instanceof tests
- let TCP in node report remote ip and port when in single packet mode
- typo fix in node help (#1735)
Other Fixes
- Tidy up default grunt task and fixup test break due to reorder Fixes #1738
- Bump jsonata version
#### 0.18.6: Maintenance Release
Editor Fixes

View File

@@ -24,6 +24,10 @@ module.exports = function(grunt) {
nodemonArgs.push(flowFile);
}
var nonHeadless = grunt.option('non-headless');
if (nonHeadless) {
process.env.NODE_RED_NON_HEADLESS = 'true';
}
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
paths: {
@@ -55,7 +59,7 @@ module.exports = function(grunt) {
reportFormats: ['lcov','html'],
print: 'both'
},
all: { src: ['test/**/*_spec.js'] },
all: { src: ["test/_spec.js","test/red/**/*_spec.js","test/nodes/**/*_spec.js"] },
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]}
},
@@ -144,12 +148,15 @@ module.exports = function(grunt) {
"editor/js/ui/keyboard.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
"editor/js/ui/view-navigator.js",
"editor/js/ui/sidebar.js",
"editor/js/ui/palette.js",
"editor/js/ui/tab-info.js",
"editor/js/ui/tab-config.js",
"editor/js/ui/tab-context.js",
"editor/js/ui/palette-editor.js",
"editor/js/ui/editor.js",
"editor/js/ui/editors/*.js",
"editor/js/ui/tray.js",
"editor/js/ui/clipboard.js",
"editor/js/ui/library.js",
@@ -474,7 +481,7 @@ module.exports = function(grunt) {
grunt.registerTask('default',
'Builds editor content then runs code style checks and unit tests on all components',
['build','test-core','test-editor','test-nodes']);
['build','jshint:editor','mocha_istanbul:all']);
grunt.registerTask('test-core',
'Runs code style check and unit tests on core runtime code',

BIN
editor/icons/file-in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

BIN
editor/icons/file-out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

View File

@@ -10,6 +10,7 @@
"ctrl-g i": "core:show-info-tab",
"ctrl-g d": "core:show-debug-tab",
"ctrl-g c": "core:show-config-tab",
"ctrl-g x": "core:show-context-tab",
"ctrl-e": "core:show-export-dialog",
"ctrl-i": "core:show-import-dialog",
"ctrl-space": "core:toggle-sidebar",

View File

@@ -153,13 +153,13 @@
loadFlows(function() {
var project = RED.projects.getActiveProject();
var message = {
"change-branch":"Change to local branch '"+project.git.branches.local+"'",
"merge-abort":"Git merge aborted",
"loaded":"Project '"+msg.project+"' loaded",
"updated":"Project '"+msg.project+"' updated",
"pull":"Project '"+msg.project+"' reloaded",
"revert": "Project '"+msg.project+"' reloaded",
"merge-complete":"Git merge completed"
"change-branch": RED._("notification.project.change-branch", {project: project.git.branches.local}),
"merge-abort": RED._("notification.project.merge-abort"),
"loaded": RED._("notification.project.loaded", {project: msg.project}),
"updated": RED._("notification.project.updated", {project: msg.project}),
"pull": RED._("notification.project.pull", {project: msg.project}),
"revert": RED._("notification.project.revert", {project: msg.project}),
"merge-complete": RED._("notification.project.merge-complete")
}[msg.action];
RED.notify("<p>"+message+"</p>");
RED.sidebar.info.refresh()
@@ -183,7 +183,7 @@
if (!!RED.projects.getActiveProject()) {
options.buttons = [
{
text: "Manage project dependencies",
text: RED._("notification.label.manage-project-dep"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.settings.show('deps');
@@ -194,7 +194,7 @@
} else {
options.buttons = [
{
text: "Close",
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
@@ -207,7 +207,7 @@
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup credentials",
text: RED._("notification.label.setup-cred"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showCredentialsPrompt();
@@ -218,7 +218,7 @@
} else {
options.buttons = [
{
text: "Close",
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
@@ -229,7 +229,7 @@
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup project files",
text: RED._("notification.label.setup-project"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showFilesPrompt();
@@ -241,7 +241,7 @@
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Create default package file",
text: RED._("notification.label.create-default-package"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultPackageFile();
@@ -253,13 +253,13 @@
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "No thanks",
text: RED._("notification.label.no-thanks"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
},
{
text: "Create default project files",
text: RED._("notification.label.create-default-project"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultFileSet();
@@ -273,7 +273,7 @@
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Show merge conflicts",
text: RED._("notification.label.show-merge-conflicts"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.sidebar.versionControl.showLocalChanges();
@@ -382,10 +382,10 @@
function loadEditor() {
var menuOptions = [];
if (RED.settings.theme("projects.enabled",false)) {
menuOptions.push({id:"menu-item-projects-menu",label:"Projects",options:[
{id:"menu-item-projects-new",label:"New",disabled:false,onselect:"core:new-project"},
{id:"menu-item-projects-open",label:"Open",disabled:false,onselect:"core:open-project"},
{id:"menu-item-projects-settings",label:"Project Settings",disabled:false,onselect:"core:show-project-settings"}
menuOptions.push({id:"menu-item-projects-menu",label:RED._("menu.label.projects"),options:[
{id:"menu-item-projects-new",label:RED._("menu.label.projects-new"),disabled:false,onselect:"core:new-project"},
{id:"menu-item-projects-open",label:RED._("menu.label.projects-open"),disabled:false,onselect:"core:open-project"},
{id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
]});
}
@@ -410,8 +410,8 @@
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]});
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:"core:show-export-dialog"},
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
]});
menuOptions.push(null);

View File

@@ -133,7 +133,7 @@ RED.nodes = (function() {
registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def;
def.type = nt;
if (def.category != "subflows") {
if (nt.substring(0,8) != "subflow:") {
def.set = nodeSets[typeToId[nt]];
nodeSets[typeToId[nt]].added = true;
nodeSets[typeToId[nt]].enabled = true;
@@ -356,7 +356,7 @@ RED.nodes = (function() {
defaults:{name:{value:""}},
info: sf.info,
icon: function() { return sf.icon||"subflow.png" },
category: "subflows",
category: sf.category || "subflows",
inputs: sf.in.length,
outputs: sf.out.length,
color: "#da9",
@@ -519,6 +519,7 @@ RED.nodes = (function() {
node.type = n.type;
node.name = n.name;
node.info = n.info;
node.category = n.category;
node.in = [];
node.out = [];
@@ -1033,15 +1034,31 @@ RED.nodes = (function() {
node.type = "unknown";
}
if (node._def.category != "config") {
node.inputs = n.inputs||node._def.inputs;
node.outputs = n.outputs||node._def.outputs;
// If 'wires' is longer than outputs, clip wires
if (n.hasOwnProperty('inputs')) {
node.inputs = n.inputs;
node._config.inputs = JSON.stringify(n.inputs);
} else {
node.inputs = node._def.inputs;
}
if (n.hasOwnProperty('outputs')) {
node.outputs = n.outputs;
node._config.outputs = JSON.stringify(n.outputs);
} else {
node.outputs = node._def.outputs;
}
if (node.hasOwnProperty('wires') && node.wires.length > node.outputs) {
console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs);
node.wires = node.wires.slice(0,node.outputs);
if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) {
// If 'wires' is longer than outputs, clip wires
console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs);
node.wires = node.wires.slice(0,node.outputs);
} else {
// The node declares outputs in its defaults, but has not got a valid value
// Defer to the length of the wires array
node.outputs = node.wires.length;
}
}
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
node[d] = n[d];
node._config[d] = JSON.stringify(n[d]);
}

View File

@@ -276,9 +276,20 @@ RED.clipboard = (function() {
if (typeof value !== "string" ) {
value = JSON.stringify(value, function(key,value) {
if (value !== null && typeof value === 'object') {
if (value.__encoded__ && value.hasOwnProperty('data') && value.hasOwnProperty('length')) {
truncated = value.data.length !== value.length;
return value.data;
if (value.__enc__) {
if (value.hasOwnProperty('data') && value.hasOwnProperty('length')) {
truncated = value.data.length !== value.length;
return value.data;
}
if (value.type === 'function' || value.type === 'internal') {
return undefined
}
if (value.type === 'number') {
// Handle NaN and Infinity - they are not permitted
// in JSON. We can either substitute with a String
// representation or null
return null;
}
}
}
return value;
@@ -309,18 +320,6 @@ RED.clipboard = (function() {
$('<input type="text" id="clipboard-hidden">').appendTo("body");
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});
RED.actions.add("core:show-export-dialog",exportNodes);
RED.actions.add("core:show-import-dialog",importNodes);

View File

@@ -19,12 +19,14 @@ RED.popover = (function() {
"default": {
top: 10,
leftRight: 17,
leftLeft: 25
leftLeft: 25,
leftBottom: 8,
},
"small": {
top: 5,
leftRight: 17,
leftLeft: 16
leftLeft: 16,
leftBottom: 3,
}
}
function createPopover(options) {
@@ -46,29 +48,39 @@ RED.popover = (function() {
var openPopup = function(instant) {
if (active) {
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>').appendTo("body");
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>');
if (size !== "default") {
div.addClass("red-ui-popover-size-"+size);
}
if (typeof content === 'function') {
content.call(res).appendTo(div);
var result = content.call(res);
if (result === null) {
return;
}
if (typeof result === 'string') {
div.text(result);
} else {
div.append(result);
}
} else {
div.html(content);
}
if (width !== "auto") {
div.width(width);
}
div.appendTo("body");
var targetPos = target.offset();
var targetWidth = target.width();
var targetHeight = target.height();
var targetWidth = target.outerWidth();
var targetHeight = target.outerHeight();
var divHeight = div.height();
var divWidth = div.width();
if (direction === 'right') {
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left+targetWidth+deltaSizes[size].leftRight});
} else if (direction === 'left') {
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth});
} else if (direction === 'bottom') {
div.css({top: targetPos.top+targetHeight+deltaSizes[size].top,left:targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom});
}
if (instant) {
div.show();
@@ -143,7 +155,17 @@ RED.popover = (function() {
}
return {
create: createPopover
create: createPopover,
tooltip: function(target,content) {
RED.popover.create({
target:target,
trigger: "hover",
size: "small",
direction: "bottom",
content: content,
delay: { show: 550, hide: 10 }
});
}
}
})();

View File

@@ -17,10 +17,15 @@
RED.tabs = (function() {
var defaultTabIcon = "fa fa-lemon-o";
function createTabs(options) {
var tabs = {};
var pinnedTabsCount = 0;
var currentTabWidth;
var currentActiveTabWidth = 0;
var collapsibleMenu;
var ul = options.element || $("#"+options.id);
var wrapper = ul.wrap( "<div>" ).parent();
@@ -50,6 +55,56 @@ RED.tabs = (function() {
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
}
if (options.collapsible) {
// var dropDown = $('<div>',{class:"red-ui-tabs-select"}).appendTo(wrapper);
// ul.hide();
wrapper.addClass("red-ui-tabs-collapsible");
var collapsedButtonsRow = $('<div class="red-ui-tab-link-buttons"></div>').appendTo(wrapper);
var selectButton = $('<a href="#"><i class="fa fa-caret-down"></i></a>').appendTo(collapsedButtonsRow);
selectButton.addClass("red-ui-tab-link-button-menu")
selectButton.click(function(evt) {
evt.preventDefault();
if (!collapsibleMenu) {
var pinnedOptions = [];
var options = [];
ul.children().each(function(i,el) {
var id = $(el).data('tabId');
var opt = {
id:"red-ui-tabs-menu-option-"+id,
icon: tabs[id].iconClass || defaultTabIcon,
label: tabs[id].name,
onselect: function() {
activateTab(id);
}
};
if (tabs[id].pinned) {
pinnedOptions.push(opt);
} else {
options.push(opt);
}
});
options = pinnedOptions.concat(options);
collapsibleMenu = RED.menu.init({id:"debug-message-option-menu",options: options});
collapsibleMenu.css({
position: "absolute"
})
collapsibleMenu.on('mouseleave', function(){ $(this).hide() });
collapsibleMenu.on('mouseup', function() { $(this).hide() });
collapsibleMenu.appendTo("body");
}
var elementPos = selectButton.offset();
collapsibleMenu.css({
top: (elementPos.top+selectButton.height()-20)+"px",
left: (elementPos.left - collapsibleMenu.width() + selectButton.width())+"px"
})
collapsibleMenu.toggle();
})
}
function scrollEventHandler(evt,dir) {
evt.preventDefault();
if ($(this).hasClass('disabled')) {
@@ -118,6 +173,9 @@ RED.tabs = (function() {
ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"});
link.parent().addClass("active");
var parentId = link.parent().attr('id');
wrapper.find(".red-ui-tab-link-button").removeClass("active selected");
$("#"+parentId+"-link-button").addClass("active selected");
if (options.scrollable) {
var pos = link.parent().position().left;
if (pos-21 < 0) {
@@ -155,41 +213,70 @@ RED.tabs = (function() {
var tabs = ul.find("li.red-ui-tab");
var width = wrapper.width();
var tabCount = tabs.size();
var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = currentTabWidth+"%";
if (options.scrollable) {
tabWidth = Math.max(tabWidth,140);
currentTabWidth = tabWidth+"px";
currentActiveTabWidth = 0;
var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount);
ul.width(listWidth);
updateScroll();
} else if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (tabWidth < options.minimumActiveTabWidth) {
tabCount -= 1;
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
var tabWidth;
if (options.collapsible) {
tabWidth = width - collapsedButtonsRow.width()-10;
if (tabWidth < 198) {
var delta = 198 - tabWidth;
var b = collapsedButtonsRow.find("a:last").prev();
while (b.is(":not(:visible)")) {
b = b.prev();
}
if (!b.hasClass("red-ui-tab-link-button-pinned")) {
b.hide();
}
tabWidth = width - collapsedButtonsRow.width()-10;
} else {
currentActiveTabWidth = 0;
var space = width - 198 - collapsedButtonsRow.width();
if (space > 40) {
collapsedButtonsRow.find("a:not(:visible):first").show();
tabWidth = width - collapsedButtonsRow.width()-10;
}
}
}
tabs.css({width:currentTabWidth});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide();
ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
tabs.css({width:tabWidth});
} else {
ul.find(".red-ui-tab-close").show();
ul.find(".red-ui-tab-icon").show();
ul.find(".red-ui-tab-label").css({paddingLeft:""})
}
if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = currentTabWidth+"%";
if (options.scrollable) {
tabWidth = Math.max(tabWidth,140);
currentTabWidth = tabWidth+"px";
currentActiveTabWidth = 0;
var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount);
ul.width(listWidth);
updateScroll();
} else if (options.hasOwnProperty("minimumActiveTabWidth")) {
if (tabWidth < options.minimumActiveTabWidth) {
tabCount -= 1;
tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = options.minimumActiveTabWidth+"px";
} else {
currentActiveTabWidth = 0;
}
}
if (options.collapsible) {
console.log(currentTabWidth);
}
tabs.css({width:currentTabWidth});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide();
ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
} else {
ul.find(".red-ui-tab-close").show();
ul.find(".red-ui-tab-icon").show();
ul.find(".red-ui-tab-label").css({paddingLeft:""})
}
if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
}
}
}
@@ -210,11 +297,15 @@ RED.tabs = (function() {
activateTab(tab.find("a"));
}
li.remove();
if (tabs[id].pinned) {
pinnedTabsCount--;
}
if (options.onremove) {
options.onremove(tabs[id]);
}
delete tabs[id];
updateTabWidths();
collapsibleMenu = null;
}
return {
@@ -223,13 +314,48 @@ RED.tabs = (function() {
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
li.data("tabId",tab.id);
if (options.maximumTabWidth) {
li.css("maxWidth",options.maximumTabWidth+"px");
}
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
} else if (tab.iconClass) {
$('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
}
var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link);
span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label));
if (options.collapsible) {
li.addClass("red-ui-tab-pinned");
var pinnedLink = $('<a href="#'+tab.id+'" class="red-ui-tab-link-button"></a>');
if (tab.pinned) {
if (pinnedTabsCount === 0) {
pinnedLink.prependTo(collapsedButtonsRow)
} else {
pinnedLink.insertAfter(collapsedButtonsRow.find("a.red-ui-tab-link-button-pinned:last"));
}
} else {
pinnedLink.insertBefore(collapsedButtonsRow.find("a:last"));
}
pinnedLink.attr('id',li.attr('id')+"-link-button");
if (tab.iconClass) {
$('<i>',{class:tab.iconClass}).appendTo(pinnedLink);
} else {
$('<i>',{class:defaultTabIcon}).appendTo(pinnedLink);
}
pinnedLink.click(function(evt) {
evt.preventDefault();
activateTab(tab.id);
});
if (tab.pinned) {
pinnedLink.addClass("red-ui-tab-link-button-pinned");
pinnedTabsCount++;
}
RED.popover.tooltip($(pinnedLink), tab.name);
}
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
if (tab.closeable) {
@@ -241,7 +367,6 @@ RED.tabs = (function() {
removeTab(tab.id);
});
}
updateTabWidths();
if (options.onadd) {
options.onadd(tab);
}
@@ -326,6 +451,10 @@ RED.tabs = (function() {
}
})
}
setTimeout(function() {
updateTabWidths();
},10);
collapsibleMenu = null;
},
removeTab: removeTab,
activateTab: activateTab,

View File

@@ -14,10 +14,38 @@
* limitations under the License.
**/
(function($) {
var contextParse = function(v) {
var parts = RED.utils.parseContextKey(v);
return {
option: parts.store,
value: parts.key
}
}
var contextExport = function(v,opt) {
if (!opt) {
return v;
}
var store = ((typeof opt === "string")?opt:opt.value)
if (store !== RED.settings.context.default) {
return "#:("+store+")::"+v;
} else {
return v;
}
}
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression},
global: {value:"global",label:"global.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
@@ -76,31 +104,51 @@
}
})
}
},
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.png"
}
};
var nlsd = false;
$.widget( "nodered.typedInput", {
_create: function() {
try {
if (!nlsd && RED && RED._) {
for (var i in allOptions) {
if (allOptions.hasOwnProperty(i)) {
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
}
}
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database" style="color: #'+(store==='memory'?'ddd':'777')+'"></i>'}
})
if (contextOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
}
}
nlsd = true;
var that = this;
this.disarmClick = false;
this.input = $('<input type="text"></input>');
this.input.insertAfter(this.element);
this.input.val(this.element.val());
this.element.addClass('red-ui-typedInput');
this.uiWidth = this.element.outerWidth();
this.elementDiv = this.element.wrap("<div>").parent().addClass('red-ui-typedInput-input');
this.elementDiv = this.input.wrap("<div>").parent().addClass('red-ui-typedInput-input');
this.uiSelect = this.elementDiv.wrap( "<div>" ).parent();
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) {
this.element.css('width','100%');
this.input.css('width','100%');
this.uiSelect.width(m[1]);
this.uiWidth = null;
} else {
@@ -109,17 +157,19 @@
["Right","Left"].forEach(function(d) {
var m = that.element.css("margin"+d);
that.uiSelect.css("margin"+d,m);
that.element.css("margin"+d,0);
that.input.css("margin"+d,0);
});
this.uiSelect.addClass("red-ui-typedInput-container");
this.element.attr('type','hidden');
this.options.types = this.options.types||Object.keys(allOptions);
this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect);
if (this.options.types.length > 1) {
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
}
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
$('<i class="red-ui-typedInput-icon fa fa-sort-desc"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger);
this.types(this.options.types);
@@ -133,14 +183,16 @@
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
}
this.element.on('focus', function() {
this.input.on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.element.on('blur', function() {
this.input.on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
});
this.element.on('change', function() {
this.input.on('change', function() {
that.validate();
that.element.val(that.value());
that.element.trigger('change',that.propertyType,that.value());
})
this.selectTrigger.click(function(event) {
event.preventDefault();
@@ -156,8 +208,11 @@
})
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
RED.popover.tooltip(this.optionSelectLabel,function() {
return that.optionValue;
});
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
that._showOptionSelectMenu();
@@ -172,17 +227,18 @@
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.type(this.options.default||this.typeList[0].value);
}catch(err) {
console.log(err.stack);
}
},
_showTypeMenu: function() {
if (this.typeList.length > 1) {
this._showMenu(this.menu,this.selectTrigger);
this.menu.find("[value='"+this.propertyType+"']").focus();
} else {
this.element.focus();
this.input.focus();
}
},
_showOptionSelectMenu: function() {
@@ -191,8 +247,8 @@
minWidth:this.optionSelectLabel.width()
});
this._showMenu(this.optionMenu,this.optionSelectLabel);
var selectedOption = this.optionMenu.find("[value='"+this.value()+"']");
this._showMenu(this.optionMenu,this.optionSelectTrigger);
var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
if (selectedOption.length === 0) {
selectedOption = this.optionMenu.children(":first");
}
@@ -204,7 +260,7 @@
$(document).off("mousedown.close-property-select");
menu.hide();
if (this.elementDiv.is(":visible")) {
this.element.focus();
this.input.focus();
} else if (this.optionSelectTrigger.is(":visible")){
this.optionSelectTrigger.focus();
} else {
@@ -223,10 +279,19 @@
op.text(opt.label);
}
if (opt.icon) {
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(op);
} else if (opt.icon.indexOf("/") !== -1) {
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
} else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
}
} else {
op.css({paddingLeft: "18px"});
}
if (!opt.icon && !opt.label) {
op.text(opt.value);
}
op.click(function(event) {
event.preventDefault();
@@ -305,7 +370,8 @@
if (this.uiWidth !== null) {
this.uiSelect.width(this.uiWidth);
}
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) {
var type = this.typeMap[this.propertyType];
if (type && type.hasValue === false) {
this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else {
this.selectTrigger.removeClass("red-ui-typedInput-full-width");
@@ -315,13 +381,68 @@
this.elementDiv.css('right',"22px");
} else {
this.elementDiv.css('right','0');
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
}
// if (this.optionSelectTrigger) {
// this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
// }
if (this.optionSelectTrigger) {
this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
if (type && type.options && type.hasValue === true) {
this.optionSelectLabel.css({'left':'auto'})
var lw = this._getLabelWidth(this.optionSelectLabel);
this.optionSelectTrigger.css({'width':(23+lw)+"px"});
this.elementDiv.css('right',(23+lw)+"px");
this.input.css({
'border-top-right-radius': 0,
'border-bottom-right-radius': 0
});
} else {
this.optionSelectLabel.css({'left':'0'})
this.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'});
if (!this.optionExpandButton.is(":visible")) {
this.elementDiv.css({'right':0});
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
}
}
}
}
},
_updateOptionSelectLabel: function(o) {
var opt = this.typeMap[this.propertyType];
this.optionSelectLabel.empty();
if (o.icon) {
if (o.icon.indexOf("<") === 0) {
$(o.icon).prependTo(this.optionSelectLabel);
} else if (o.icon.indexOf("/") !== -1) {
// url
$('<img>',{src:o.icon,style:"height: 18px;"}).prependTo(this.optionSelectLabel);
} else {
// icon class
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
}
} else if (o.label) {
this.optionSelectLabel.text(o.label);
} else {
this.optionSelectLabel.text(o.value);
}
if (opt.hasValue) {
this.optionValue = o.value;
this._resize();
this.input.trigger('change',this.propertyType,this.value());
}
},
_destroy: function() {
if (this.optionMenu) {
this.optionMenu.remove();
}
this.menu.remove();
},
types: function(types) {
@@ -339,13 +460,18 @@
return result;
});
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
this.selectTrigger.find(".fa-sort-desc").toggle(this.typeList.length > 1)
if (this.menu) {
this.menu.remove();
}
this.menu = this._createMenu(this.typeList, function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value);
} else {
this.propertyType = null;
this.type(currentType);
}
setTimeout(function() {that._resize();},0);
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
@@ -353,33 +479,33 @@
},
value: function(value) {
if (!arguments.length) {
return this.element.val();
var v = this.input.val();
if (this.typeMap[this.propertyType].export) {
v = this.typeMap[this.propertyType].export(v,this.optionValue)
}
return v;
} else {
var selectedOption;
if (this.typeMap[this.propertyType].options) {
var validValue = false;
var label;
for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) {
var op = this.typeMap[this.propertyType].options[i];
if (typeof op === "string") {
if (op === value) {
label = value;
validValue = true;
selectedOption = this.activeOptions[op];
break;
}
} else if (op.value === value) {
label = op.label||op.value;
validValue = true;
selectedOption = op;
break;
}
}
if (!validValue) {
value = "";
label = "";
if (!selectedOption) {
selectedOption = {value:""}
}
this.optionSelectLabel.text(label);
this._updateOptionSelectLabel(selectedOption)
}
this.element.val(value);
this.element.trigger('change',this.type(),value);
this.input.val(value);
this.input.trigger('change',this.type(),value);
}
},
type: function(type) {
@@ -390,14 +516,24 @@
var opt = this.typeMap[type];
if (opt && this.propertyType !== type) {
this.propertyType = type;
this.typeField.val(type);
if (this.typeField) {
this.typeField.val(type);
}
this.selectLabel.empty();
var image;
if (opt.icon) {
image = new Image();
image.name = opt.icon;
image.src = opt.icon;
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(this.selectLabel);
}
else if (opt.icon.indexOf("/") !== -1) {
image = new Image();
image.name = opt.icon;
image.src = opt.icon;
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
}
else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel);
}
} else {
this.selectLabel.text(opt.label);
}
@@ -407,37 +543,88 @@
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.show();
this.elementDiv.hide();
this.optionMenu = this._createMenu(opt.options,function(v){
that.optionSelectLabel.text(v);
that.value(v);
if (!opt.hasValue) {
this.elementDiv.hide();
} else {
this.elementDiv.show();
}
this.activeOptions = {};
opt.options.forEach(function(o) {
if (typeof o === 'string') {
that.activeOptions[o] = {label:o,value:o};
} else {
that.activeOptions[o.value] = o;
}
});
if (!that.activeOptions.hasOwnProperty(that.optionValue)) {
that.optionValue = null;
}
this.optionMenu = this._createMenu(opt.options,function(v){
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
that.value(that.activeOptions[v].value)
}
});
var currentVal = this.element.val();
var validValue = false;
var op;
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
if (typeof op === "string") {
if (op === currentVal) {
this.optionSelectLabel.text(currentVal);
if (!opt.hasValue) {
var currentVal = this.input.val();
var validValue = false;
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
if (typeof op === "string" && op === currentVal) {
that._updateOptionSelectLabel({value:currentVal});
validValue = true;
break;
} else if (op.value === currentVal) {
that._updateOptionSelectLabel(op);
validValue = true;
break;
}
} else if (op.value === currentVal) {
this.optionSelectLabel.text(op.label||op.value);
validValue = true;
break;
}
}
if (!validValue) {
op = opt.options[0];
if (typeof op === "string") {
this.value(op);
if (!validValue) {
op = opt.options[0];
if (typeof op === "string") {
this.value(op);
that._updateOptionSelectLabel({value:op});
} else {
this.value(op.value);
that._updateOptionSelectLabel(op);
}
}
} else {
var selectedOption = this.optionValue||opt.options[0];
if (opt.parse) {
var parts = opt.parse(this.input.val());
if (parts.option) {
selectedOption = parts.option;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
parts.option = Object.keys(this.activeOptions)[0];
selectedOption = parts.option
}
}
this.input.val(parts.value);
if (opt.export) {
this.element.val(opt.export(parts.value,parts.option||selectedOption));
}
}
if (typeof selectedOption === "string") {
this.optionValue = selectedOption;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
selectedOption = Object.keys(this.activeOptions)[0];
}
if (!selectedOption) {
this.optionSelectTrigger.hide();
} else {
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
}
} else if (selectedOption) {
this.optionValue = selectedOption.value;
this._updateOptionSelectLabel(selectedOption);
} else {
this.value(op.value);
this.optionSelectTrigger.hide();
}
}
console.log(validValue);
}
} else {
if (this.optionMenu) {
@@ -448,27 +635,29 @@
this.optionSelectTrigger.hide();
}
if (opt.hasValue === false) {
this.oldValue = this.element.val();
this.element.val("");
this.oldValue = this.input.val();
this.input.val("");
this.elementDiv.hide();
} else {
if (this.oldValue !== undefined) {
this.element.val(this.oldValue);
this.input.val(this.oldValue);
delete this.oldValue;
}
this.elementDiv.show();
}
if (opt.expand && typeof opt.expand === 'function') {
this.optionExpandButton.show();
this.optionExpandButton.off('click');
this.optionExpandButton.on('click',function(evt) {
evt.preventDefault();
opt.expand.call(that);
})
} else {
this.optionExpandButton.hide();
if (this.optionExpandButton) {
if (opt.expand && typeof opt.expand === 'function') {
this.optionExpandButton.show();
this.optionExpandButton.off('click');
this.optionExpandButton.on('click',function(evt) {
evt.preventDefault();
opt.expand.call(that);
})
} else {
this.optionExpandButton.hide();
}
}
this.element.trigger('change',this.propertyType,this.value());
this.input.trigger('change',this.propertyType,this.value());
}
if (image) {
image.onload = function() { that._resize(); }

View File

@@ -490,7 +490,7 @@ RED.diff = (function() {
}
function createNodeIcon(node,def) {
var nodeDiv = $("<div>",{class:"node-diff-node-entry-node"});
var colour = def.color;
var colour = RED.utils.getNodeColor(node.type,def);
var icon_url = RED.utils.getNodeIcon(def,node);
if (node.type === 'tab') {
colour = "#C0DEED";
@@ -881,7 +881,6 @@ RED.diff = (function() {
}
}
}
var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
if (def.defaults) {
properties = properties.concat(Object.keys(def.defaults));
@@ -889,6 +888,13 @@ RED.diff = (function() {
if (node.type !== 'tab') {
properties = properties.concat(['inputLabels','outputLabels']);
}
if ( ((localNode && localNode.hasOwnProperty('icon')) || (remoteNode && remoteNode.hasOwnProperty('icon'))) &&
properties.indexOf('icon') === -1
) {
properties.unshift('icon');
}
properties.forEach(function(d) {
localChanged = false;
remoteChanged = false;
@@ -1233,7 +1239,7 @@ RED.diff = (function() {
// currentDiff = diff;
var trayOptions = {
title: options.title||"Review Changes", //TODO: nls
title: options.title||RED._("diff.reviewChanges"),
width: Infinity,
overlay: true,
buttons: [
@@ -1416,7 +1422,7 @@ RED.diff = (function() {
function showTextDiff(textA,textB) {
var trayOptions = {
title: "Compare Changes", //TODO: nls
title: RED._("diff.compareChanges"),
width: Infinity,
overlay: true,
buttons: [
@@ -1747,7 +1753,7 @@ RED.diff = (function() {
try {
commonFlow = JSON.parse(commonVersion.content||"[]");
} catch(err) {
console.log("Common Version doesn't contain valid JSON:",commonVersionUrl);
console.log(RED._("diff.commonVersionError"),commonVersionUrl);
console.log(err);
return;
}
@@ -1755,7 +1761,7 @@ RED.diff = (function() {
try {
oldFlow = JSON.parse(oldVersion.content||"[]");
} catch(err) {
console.log("Old Version doesn't contain valid JSON:",oldVersionUrl);
console.log(RED._("diff.oldVersionError"),oldVersionUrl);
console.log(err);
return;
}
@@ -1765,7 +1771,7 @@ RED.diff = (function() {
try {
newFlow = JSON.parse(newVersion.content||"[]");
} catch(err) {
console.log("New Version doesn't contain valid JSON:",newFlow);
console.log(RED._("diff.newVersionError"),newFlow);
console.log(err);
return;
}
@@ -1797,11 +1803,11 @@ RED.diff = (function() {
if (isBinary) {
var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var binaryContent = $('<td colspan="3"></td>').appendTo(diffBinaryRow);
$('<span></span>').text("Cannot show binary file contents").appendTo(binaryContent);
$('<span></span>').text(RED._("diff.noBinaryFileShowed")).appendTo(binaryContent);
} else {
if (commitOptions.unmerged) {
conflictHeader = $('<span style="float: right;"><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(content);
conflictHeader = $('<span style="float: right;">'+RED._("diff.conflictHeader",{resolved:resolvedConflicts, unresolved:unresolvedConflicts})+'</span>').appendTo(content);
}
hunks.forEach(function(hunk) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
@@ -1914,7 +1920,7 @@ RED.diff = (function() {
diffRow.remove();
addedRows.find(".linetext").addClass('added');
conflictHeader.empty();
$('<span><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(conflictHeader);
$('<span>'+RED._("diff.conflictHeader",{resolved:resolvedConflicts, unresolved:unresolvedConflicts})+'</span>').appendTo(conflictHeader);
conflictResolutions[file.file] = conflictResolutions[file.file] || {};
conflictResolutions[file.file][hunk.localChangeStart] = {
@@ -1946,7 +1952,7 @@ RED.diff = (function() {
function showCommitDiff(options) {
var commit = parseCommitDiff(options.commit);
var trayOptions = {
title: "View Commit Changes", //TODO: nls
title: RED._("diff.viewCommitDiff"),
width: Infinity,
overlay: true,
buttons: [
@@ -2008,7 +2014,7 @@ RED.diff = (function() {
}
var trayOptions = {
title: title||"Compare Changes", //TODO: nls
title: title|| RED._("diff.compareChanges"),
width: Infinity,
overlay: true,
buttons: [
@@ -2041,7 +2047,7 @@ RED.diff = (function() {
trayOptions.buttons.push(
{
id: "node-diff-view-resolve-diff",
text: "Save conflict resolution",
text: RED._("diff.saveConflict"),
class: "primary disabled",
click: function() {
if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._buffer = (function() {
var template = '<script type="text/x-red" data-template-name="_buffer"><div id="node-input-buffer-panels"><div id="node-input-buffer-panel-str" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-buffer-str"></div></div></div><div id="node-input-buffer-panel-bin" class="red-ui-panel"><div class="form-row node-text-editor-row" style="margin-top: 10px"><div class="node-text-editor" id="node-input-buffer-bin"></div></div></div></div></script>';
function stringToUTF8Array(str) {
var data = [];
var i=0, l = str.length;
for (i=0; i<l; i++) {
var char = str.charCodeAt(i);
if (char < 0x80) {
data.push(char);
} else if (char < 0x800) {
data.push(0xc0 | (char >> 6));
data.push(0x80 | (char & 0x3f));
} else if (char < 0xd800 || char >= 0xe000) {
data.push(0xe0 | (char >> 12));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
} else {
i++;
char = 0x10000 + (((char & 0x3ff)<<10) | (str.charAt(i) & 0x3ff));
data.push(0xf0 | (char >>18));
data.push(0x80 | ((char>>12) & 0x3f));
data.push(0x80 | ((char>>6) & 0x3f));
data.push(0x80 | (char & 0x3f));
}
}
return data;
}
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_buffer"
RED.view.state(RED.state.EDITING);
var bufferStringEditor = [];
var bufferBinValue;
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
onComplete(JSON.stringify(bufferBinValue));
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
bufferStringEditor = RED.editor.createEditor({
id: 'node-input-buffer-str',
value: "",
mode:"ace/mode/text"
});
bufferStringEditor.getSession().setValue(value||"",-1);
bufferBinEditor = RED.editor.createEditor({
id: 'node-input-buffer-bin',
value: "",
mode:"ace/mode/text",
readOnly: true
});
var changeTimer;
var buildBuffer = function(data) {
var valid = true;
var isString = typeof data === 'string';
var binBuffer = [];
if (isString) {
bufferBinValue = stringToUTF8Array(data);
} else {
bufferBinValue = data;
}
var i=0,l=bufferBinValue.length;
var c = 0;
for(i=0;i<l;i++) {
var d = parseInt(bufferBinValue[i]);
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
valid = false;
break;
}
if (i>0) {
if (i%8 === 0) {
if (i%16 === 0) {
binBuffer.push("\n");
} else {
binBuffer.push(" ");
}
} else {
binBuffer.push(" ");
}
}
binBuffer.push((d<16?"0":"")+d.toString(16).toUpperCase());
}
if (valid) {
$("#node-input-buffer-type-string").toggle(isString);
$("#node-input-buffer-type-array").toggle(!isString);
bufferBinEditor.setValue(binBuffer.join(""),1);
}
return valid;
}
var bufferStringUpdate = function() {
var value = bufferStringEditor.getValue();
var isValidArray = false;
if (/^[\s]*\[[\s\S]*\][\s]*$/.test(value)) {
isValidArray = true;
try {
var data = JSON.parse(value);
isValidArray = buildBuffer(data);
} catch(err) {
isValidArray = false;
}
}
if (!isValidArray) {
buildBuffer(value);
}
}
bufferStringEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(bufferStringUpdate,200);
});
bufferStringUpdate();
dialogForm.i18n();
panels = RED.panels.create({
id:"node-input-buffer-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-buffer-panel-str");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-str").css("height",(p1Height-5)+"px");
bufferStringEditor.resize();
var p2 = $("#node-input-buffer-panel-bin");
editorRow = $(p2.children()[0]);
p2Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-buffer-bin").css("height",(p2Height-5)+"px");
bufferBinEditor.resize();
}
});
$(".node-input-buffer-type").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("bufferEditor.modeDesc"));
RED.sidebar.info.show();
})
},
close: function() {
if (options.onclose) {
options.onclose();
}
bufferStringEditor.destroy();
bufferBinEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,325 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._expression = (function() {
var template = '<script type="text/x-red" data-template-name="_expression"><div id="node-input-expression-panels"><div id="node-input-expression-panel-expr" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-expression-legacy"><i class="fa fa-exclamation-circle"></i> <span data-i18n="expressionEditor.compatMode"></span></span><button id="node-input-expression-reformat" class="editor-button editor-button-small"><span data-i18n="expressionEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-expression"></div></div></div><div id="node-input-expression-panel-info" class="red-ui-panel"><div class="form-row"><ul id="node-input-expression-tabs"></ul><div id="node-input-expression-tab-help" class="node-input-expression-tab-content hide"><div><select id="node-input-expression-func"></select><button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button></div><div id="node-input-expression-help"></div></div><div id="node-input-expression-tab-test" class="node-input-expression-tab-content hide"><div><span style="display: inline-block; width: calc(50% - 5px);"><span data-i18n="expressionEditor.data"></span><button style="float: right; margin-right: 5px;" id="node-input-example-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></span><span style="display: inline-block; width: calc(50% - 5px);" data-i18n="expressionEditor.result"></span></div><div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-data"></div><div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-result"></div></div></div></div></div></script>';
var expressionTestCache = {};
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var expressionTestCacheId = options.parent||"_";
var value = options.value;
var onComplete = options.complete;
var type = "_expression"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var testDataEditor;
var testResultEditor
var panels;
var trayOptions = {
title: options.title,
width: "inherit",
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
$("#node-input-expression-help").text("");
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
var height = $("#dialog-form").height();
if (panels) {
panels.resize(height);
}
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
trayBody.addClass("node-input-expression-editor")
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form','_expression','editor');
var funcSelect = $("#node-input-expression-func");
Object.keys(jsonata.functions).forEach(function(f) {
funcSelect.append($("<option></option>").val(f).text(f));
})
funcSelect.change(function(e) {
var f = $(this).val();
var args = RED._('jsonata:'+f+".args",{defaultValue:''});
var title = "<h5>"+f+"("+args+")</h5>";
var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
$("#node-input-expression-help").html(title+"<p>"+body+"</p>");
})
expressionEditor = RED.editor.createEditor({
id: 'node-input-expression',
value: "",
mode:"ace/mode/jsonata",
options: {
enableBasicAutocompletion:true,
enableSnippets:true,
enableLiveAutocompletion: true
}
});
var currentToken = null;
var currentTokenPos = -1;
var currentFunctionMarker = null;
expressionEditor.getSession().setValue(value||"",-1);
expressionEditor.on("changeSelection", function() {
var c = expressionEditor.getCursorPosition();
var token = expressionEditor.getSession().getTokenAt(c.row,c.column);
if (token !== currentToken || (token && /paren/.test(token.type) && c.column !== currentTokenPos)) {
currentToken = token;
var r,p;
var scopedFunction = null;
if (token && token.type === 'keyword') {
r = c.row;
scopedFunction = token;
} else {
var depth = 0;
var next = false;
if (token) {
if (token.type === 'paren.rparen') {
// If this is a block of parens ')))', set
// depth to offset against the cursor position
// within the block
currentTokenPos = c.column;
depth = c.column - (token.start + token.value.length);
}
r = c.row;
p = token.index;
} else {
r = c.row-1;
p = -1;
}
while ( scopedFunction === null && r > -1) {
var rowTokens = expressionEditor.getSession().getTokens(r);
if (p === -1) {
p = rowTokens.length-1;
}
while (p > -1) {
var type = rowTokens[p].type;
if (next) {
if (type === 'keyword') {
scopedFunction = rowTokens[p];
// console.log("HIT",scopedFunction);
break;
}
next = false;
}
if (type === 'paren.lparen') {
depth-=rowTokens[p].value.length;
} else if (type === 'paren.rparen') {
depth+=rowTokens[p].value.length;
}
if (depth < 0) {
next = true;
depth = 0;
}
// console.log(r,p,depth,next,rowTokens[p]);
p--;
}
if (!scopedFunction) {
r--;
}
}
}
expressionEditor.session.removeMarker(currentFunctionMarker);
if (scopedFunction) {
//console.log(token,.map(function(t) { return t.type}));
funcSelect.val(scopedFunction.value).change();
}
}
});
dialogForm.i18n();
$("#node-input-expression-func-insert").click(function(e) {
e.preventDefault();
var pos = expressionEditor.getCursorPosition();
var f = funcSelect.val();
var snippet = jsonata.getFunctionSnippet(f);
expressionEditor.insertSnippet(snippet);
expressionEditor.focus();
});
$("#node-input-expression-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = jsonata.format(v);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
var tabs = RED.tabs.create({
element: $("#node-input-expression-tabs"),
onchange:function(tab) {
$(".node-input-expression-tab-content").hide();
tab.content.show();
trayOptions.resize();
}
})
tabs.addTab({
id: 'expression-help',
label: RED._('expressionEditor.functionReference'),
content: $("#node-input-expression-tab-help")
});
tabs.addTab({
id: 'expression-tests',
label: RED._('expressionEditor.test'),
content: $("#node-input-expression-tab-test")
});
testDataEditor = RED.editor.createEditor({
id: 'node-input-expression-test-data',
value: expressionTestCache[expressionTestCacheId] || '{\n "payload": "hello world"\n}',
mode:"ace/mode/json",
lineNumbers: false
});
var changeTimer;
$(".node-input-expression-legacy").click(function(e) {
e.preventDefault();
RED.sidebar.info.set(RED._("expressionEditor.compatModeDesc"));
RED.sidebar.info.show();
})
var testExpression = function() {
var value = testDataEditor.getValue();
var parsedData;
var currentExpression = expressionEditor.getValue();
var expr;
var usesContext = false;
var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
$(".node-input-expression-legacy").toggle(legacyMode);
try {
expr = jsonata(currentExpression);
expr.assign('flowContext',function(val) {
usesContext = true;
return null;
});
expr.assign('globalContext',function(val) {
usesContext = true;
return null;
});
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1);
return;
}
try {
parsedData = JSON.parse(value);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.invalid-msg",{message:err.toString()}))
return;
}
try {
var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData);
if (usesContext) {
testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
return;
}
var formattedResult;
if (result !== undefined) {
formattedResult = JSON.stringify(result,null,4);
} else {
formattedResult = RED._("expressionEditor.noMatch");
}
testResultEditor.setValue(formattedResult,-1);
} catch(err) {
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
}
}
testDataEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
expressionTestCache[expressionTestCacheId] = testDataEditor.getValue();
});
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(testExpression,200);
});
testResultEditor = RED.editor.createEditor({
id: 'node-input-expression-test-result',
value: "",
mode:"ace/mode/json",
lineNumbers: false,
readOnly: true
});
panels = RED.panels.create({
id:"node-input-expression-panels",
resize: function(p1Height,p2Height) {
var p1 = $("#node-input-expression-panel-expr");
p1Height -= $(p1.children()[0]).outerHeight(true);
var editorRow = $(p1.children()[1]);
p1Height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-expression").css("height",(p1Height-5)+"px");
expressionEditor.resize();
var p2 = $("#node-input-expression-panel-info > .form-row > div:first-child");
p2Height -= p2.outerHeight(true) + 20;
$(".node-input-expression-tab-content").height(p2Height);
$("#node-input-expression-test-data").css("height",(p2Height-5)+"px");
testDataEditor.resize();
$("#node-input-expression-test-result").css("height",(p2Height-5)+"px");
testResultEditor.resize();
}
});
$("#node-input-example-reformat").click(function(evt) {
evt.preventDefault();
var v = testDataEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
testDataEditor.getSession().setValue(v||"",-1);
});
testExpression();
},
close: function() {
if (options.onclose) {
options.onclose();
}
expressionEditor.destroy();
testDataEditor.destroy();
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

102
editor/js/ui/editors/js.js Normal file
View File

@@ -0,0 +1,102 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._js = (function() {
var template = '<script type="text/x-red" data-template-name="_js"><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-js"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_js"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var trayOptions = {
title: options.title,
width: options.width||"inherit",
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
onComplete(expressionEditor.getValue(),expressionEditor.getCursorPosition());
RED.tray.close();
}
}
],
resize: function(dimensions) {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-js',
mode: 'ace/mode/javascript',
value: value,
globals: {
msg:true,
context:true,
RED: true,
util: true,
flow: true,
global: true,
console: true,
Buffer: true,
setTimeout: true,
clearTimeout: true,
setInterval: true,
clearInterval: true
}
});
if (options.cursor) {
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
}
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,118 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._json = (function() {
var template = '<script type="text/x-red" data-template-name="_json"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_json"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var changeTimer;
var checkValid = function() {
var v = expressionEditor.getValue();
try {
JSON.parse(v);
$("#node-dialog-ok").removeClass('disabled');
return true;
} catch(err) {
$("#node-dialog-ok").addClass('disabled');
return false;
}
}
var trayOptions = {
title: options.title,
width: "inherit",
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
if (options.requireValid && !checkValid()) {
return;
}
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-json',
value: "",
mode:"ace/mode/json"
});
expressionEditor.getSession().setValue(value||"",-1);
if (options.requireValid) {
expressionEditor.getSession().on('change', function() {
clearTimeout(changeTimer);
changeTimer = setTimeout(checkValid,200);
});
checkValid();
}
$("#node-input-json-reformat").click(function(evt) {
evt.preventDefault();
var v = expressionEditor.getValue()||"";
try {
v = JSON.stringify(JSON.parse(v),null,4);
} catch(err) {
// TODO: do an optimistic auto-format
}
expressionEditor.getSession().setValue(v||"",-1);
});
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -0,0 +1,90 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._markdown = (function() {
var template = '<script type="text/x-red" data-template-name="_markdown"><div class="form-row" id="node-input-markdown-title" style="margin-bottom: 3px; text-align: right;"></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-markdown"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
show: function(options) {
var value = options.value;
var onComplete = options.complete;
var type = "_markdown"
RED.view.state(RED.state.EDITING);
var expressionEditor;
var trayOptions = {
title: options.title,
width: "inherit",
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
text: RED._("common.label.done"),
class: "primary",
click: function() {
onComplete(expressionEditor.getValue());
RED.tray.close();
}
}
],
resize: function(dimensions) {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
expressionEditor = RED.editor.createEditor({
id: 'node-input-markdown',
value: value,
mode:"ace/mode/markdown"
});
if (options.header) {
options.header.appendTo(tray.find('#node-input-markdown-title'));
}
dialogForm.i18n();
},
close: function() {
expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}
},
show: function() {}
}
RED.tray.show(trayOptions);
}
}
})();

View File

@@ -414,12 +414,8 @@ RED.library = (function() {
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});

View File

@@ -96,7 +96,7 @@ RED.notifications = (function() {
if (options.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n)
options.buttons.forEach(function(buttonDef) {
var b = $('<button>').text(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) {
b.attr('id',buttonDef.id);
}

View File

@@ -233,7 +233,7 @@ RED.palette.editor = (function() {
if (set.enabled) {
var def = RED.nodes.getType(t);
if (def && def.color) {
swatch.css({background:def.color});
swatch.css({background:RED.utils.getNodeColor(t,def)});
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
} else {

View File

@@ -21,7 +21,18 @@ RED.palette = (function() {
var categoryContainers = {};
function createCategoryContainer(category, label) {
function createCategory(originalCategory,rootCategory,category,ns) {
if ($("#palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
}
$("#palette-container-"+rootCategory).show();
if ($("#palette-"+category).length === 0) {
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
}
}
function createCategoryContainer(originalCategory,category, labelId) {
var label = RED._(labelId, {defaultValue:category});
label = (label || category).replace(/_/g, " ");
var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
@@ -31,7 +42,8 @@ RED.palette = (function() {
'<div id="palette-'+category+'-function"></div>'+
'</div>'+
'</div>').appendTo("#palette-container");
catDiv.data('category',originalCategory);
catDiv.data('label',label);
categoryContainers[category] = {
container: catDiv,
close: function() {
@@ -133,6 +145,7 @@ RED.palette = (function() {
}
if (exclusion.indexOf(def.category)===-1) {
var originalCategory = def.category;
var category = def.category.replace(/ /g,"_");
var rootCategory = category.split("-")[0];
@@ -153,14 +166,13 @@ RED.palette = (function() {
d.className="palette_node";
if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
}
d.style.backgroundColor = def.color;
d.style.backgroundColor = RED.utils.getNodeColor(nt,def);
if (def.outputs > 0) {
var portOut = document.createElement("div");
@@ -174,21 +186,12 @@ RED.palette = (function() {
d.appendChild(portIn);
}
if ($("#palette-base-category-"+rootCategory).length === 0) {
if(coreCategories.indexOf(rootCategory) !== -1){
createCategoryContainer(rootCategory, RED._("node-red:palette.label."+rootCategory, {defaultValue:rootCategory}));
} else {
var ns = def.set.id;
createCategoryContainer(rootCategory, RED._(ns+":palette.label."+rootCategory, {defaultValue:rootCategory}));
}
}
$("#palette-container-"+rootCategory).show();
if ($("#palette-"+category).length === 0) {
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
}
createCategory(def.category,rootCategory,category,(coreCategories.indexOf(rootCategory) !== -1)?"node-red":def.set.id);
$("#palette-"+category).append(d);
$(d).data('category',rootCategory);
d.onmousedown = function(e) { e.preventDefault(); };
var popover = RED.popover.create({
@@ -308,7 +311,7 @@ RED.palette = (function() {
});
var nodeInfo = null;
if (def.category == "subflows") {
if (nt.indexOf("subflow:") === 0) {
$(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
@@ -382,6 +385,31 @@ RED.palette = (function() {
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
setIcon(paletteNode,sf);
var currentCategory = paletteNode.data('category');
var newCategory = (sf.category||"subflows");
if (currentCategory !== newCategory) {
var category = newCategory.replace(/ /g,"_");
createCategory(newCategory,category,category,"node-red");
var currentCategoryNode = paletteNode.closest(".palette-category");
var newCategoryNode = $("#palette-"+category);
newCategoryNode.append(paletteNode);
if (newCategoryNode.find(".palette_node").length === 1) {
categoryContainers[category].open();
}
paletteNode.data('category',newCategory);
if (currentCategoryNode.find(".palette_node").length === 0) {
if (currentCategoryNode.find("i").hasClass("expanded")) {
currentCategoryNode.find(".palette-content").slideToggle();
currentCategoryNode.find("i").toggleClass("expanded");
}
}
}
});
}
@@ -471,7 +499,7 @@ RED.palette = (function() {
categoryList = coreCategories
}
categoryList.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
createCategoryContainer(category, category, "palette.label."+category);
});
$("#palette-collapse-all").on("click", function(e) {
@@ -491,13 +519,20 @@ RED.palette = (function() {
}
});
}
function getCategories() {
var categories = [];
$("#palette-container .palette-category").each(function(i,d) {
categories.push({id:$(d).data('category'),label:$(d).data('label')});
})
return categories;
}
return {
init: init,
add:addNodeType,
remove:removeNodeType,
hide:hideNodeType,
show:showNodeType,
refresh:refreshNodeTypes
refresh:refreshNodeTypes,
getCategories: getCategories
};
})();

View File

@@ -49,7 +49,7 @@ RED.projects.settings = (function() {
var tabContainer;
var trayOptions = {
title: "Project Settings",// RED._("menu.label.userSettings"),, // TODO: nls
title: RED._("menu.label.userSettings"),
buttons: [
{
id: "node-dialog-ok",
@@ -173,14 +173,14 @@ RED.projects.settings = (function() {
container.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);
$('<button class="editor-button">Cancel</button>')
$('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(bg)
.click(function(evt) {
evt.preventDefault();
updateProjectSummary(activeProject.summary, container);
editButton.show();
});
$('<button class="editor-button">Save</button>')
$('<button class="editor-button">' + RED._("common.label.save") + '</button>')
.appendTo(bg)
.click(function(evt) {
evt.preventDefault();
@@ -223,7 +223,7 @@ RED.projects.settings = (function() {
if (summary) {
container.text(summary).removeClass('node-info-node');
} else {
container.text("No summary available").addClass('node-info-none');// TODO: nls
container.text(RED._("sidebar.project.projectSettings.noSummaryAvailable")).addClass('node-info-none');
}
}
@@ -235,7 +235,7 @@ RED.projects.settings = (function() {
var summaryContent = $('<div></div>',{style:"color: #999"}).appendTo(summary);
updateProjectSummary(activeProject.summary, summaryContent);
if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="float: right;">edit description</button>')
$('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.editDescription') + '</button>')
.prependTo(summary)
.click(function(evt) {
evt.preventDefault();
@@ -250,7 +250,7 @@ RED.projects.settings = (function() {
updateProjectDescription(activeProject, descriptionContent);
if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="float: right;">edit README.md</button>')
$('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.editReadme') + '</button>')
.prependTo(description)
.click(function(evt) {
evt.preventDefault();
@@ -316,7 +316,7 @@ RED.projects.settings = (function() {
// depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls
// }
if (totalCount === 0) {
depsList.editableList('addItem',{index:0, label:"None"}); // TODO: nls
depsList.editableList('addItem',{index:0, label:RED._("sidebar.project.projectSettings.none")});
}
}
@@ -381,7 +381,7 @@ RED.projects.settings = (function() {
function createDependenciesPane(activeProject) {
var pane = $('<div id="project-settings-tab-deps" class="project-settings-tab-pane node-help"></div>');
if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="margin-top:10px;float: right;">edit</button>')
$('<button class="editor-button editor-button-small" style="margin-top:10px;float: right;">' + RED._("sidebar.project.projectSettings.edit") + '</button>')
.appendTo(pane)
.click(function(evt) {
evt.preventDefault();
@@ -451,7 +451,7 @@ RED.projects.settings = (function() {
var buttons = $('<div class="palette-module-button-group"></div>').appendTo(metaRow);
if (RED.user.hasPermission("projects.write")) {
if (!entry.installed && RED.settings.theme('palette.editable') !== false) {
$('<a href="#" class="editor-button editor-button-small">install</a>').appendTo(buttons)
$('<a href="#" class="editor-button editor-button-small">' + RED._("sidebar.project.projectSettings.install") + '</a>').appendTo(buttons)
.click(function(evt) {
evt.preventDefault();
RED.palette.editor.install(entry,row,function(err) {
@@ -468,7 +468,7 @@ RED.projects.settings = (function() {
});
})
} else if (entry.known && entry.count === 0) {
$('<a href="#" class="editor-button editor-button-small">remove from project</a>').appendTo(buttons)
$('<a href="#" class="editor-button editor-button-small">' + RED._("sidebar.project.projectSettings.removeFromProject") + '</a>').appendTo(buttons)
.click(function(evt) {
evt.preventDefault();
var deps = $.extend(true, {}, activeProject.dependencies);
@@ -484,7 +484,7 @@ RED.projects.settings = (function() {
});
});
} else if (!entry.known) {
$('<a href="#" class="editor-button editor-button-small">add to project</a>').appendTo(buttons)
$('<a href="#" class="editor-button editor-button-small">' + RED._("sidebar.project.projectSettings.addToProject") + '</a>').appendTo(buttons)
.click(function(evt) {
evt.preventDefault();
var deps = $.extend(true, {}, activeProject.dependencies);
@@ -723,10 +723,10 @@ RED.projects.settings = (function() {
// }
function createFilesSection(activeProject,pane) {
var title = $('<h3></h3>').text("Files").appendTo(pane);
var title = $('<h3></h3>').text(RED._("sidebar.project.projectSettings.files")).appendTo(pane);
var filesContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
if (RED.user.hasPermission("projects.write")) {
var editFilesButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
var editFilesButton = $('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.projectSettings.edit') + '</button>')
.appendTo(title)
.click(function(evt) {
evt.preventDefault();
@@ -750,7 +750,7 @@ RED.projects.settings = (function() {
// Flow files
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text('Flow').appendTo(row);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.flow")).appendTo(row);
var flowFileLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row);
var flowFileLabelText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.files.flow).appendTo(flowFileLabel);
@@ -787,7 +787,7 @@ RED.projects.settings = (function() {
})
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text('Credentials').appendTo(row);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.credentials")).appendTo(row);
var credFileLabel = $('<div class="uneditable-input">').text(activeProject.files.credentials).appendTo(row);
var credFileInput = $('<div class="uneditable-input">').text(activeProject.files.credentials).hide().insertAfter(credFileLabel);
@@ -899,12 +899,12 @@ RED.projects.settings = (function() {
var credentialFormRows = $('<div>',{style:"margin-top:10px"}).hide().appendTo(credentialStateLabel);
var credentialSetLabel = $('<div style="margin: 20px 0 10px 5px;">Set the encryption key:</div>').hide().appendTo(credentialFormRows);
var credentialChangeLabel = $('<div style="margin: 20px 0 10px 5px;">Change the encryption key:</div>').hide().appendTo(credentialFormRows);
var credentialResetLabel = $('<div style="margin: 20px 0 10px 5px;">Reset the encryption key:</div>').hide().appendTo(credentialFormRows);
var credentialSetLabel = $('<div style="margin: 20px 0 10px 5px;">' + RED._("sidebar.project.projectSettings.setTheEncryptionKey") + '</div>').hide().appendTo(credentialFormRows);
var credentialChangeLabel = $('<div style="margin: 20px 0 10px 5px;">' + RED._("sidebar.project.projectSettings.changeTheEncryptionKey") + '</div>').hide().appendTo(credentialFormRows);
var credentialResetLabel = $('<div style="margin: 20px 0 10px 5px;">' + RED._("sidebar.project.projectSettings.resetTheEncryptionKey") + '</div>').hide().appendTo(credentialFormRows);
var credentialSecretExistingRow = $('<div class="user-settings-row user-settings-row-credentials"></div>').appendTo(credentialFormRows);
$('<label for=""></label>').text('Current key').appendTo(credentialSecretExistingRow);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.currentKey")).appendTo(credentialSecretExistingRow);
var credentialSecretExistingInput = $('<input type="password">').appendTo(credentialSecretExistingRow)
.on("change keyup paste",function() {
if (popover) {
@@ -917,10 +917,10 @@ RED.projects.settings = (function() {
var credentialSecretNewRow = $('<div class="user-settings-row user-settings-row-credentials"></div>').appendTo(credentialFormRows);
$('<label for=""></label>').text('New key').appendTo(credentialSecretNewRow);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.newKey")).appendTo(credentialSecretNewRow);
var credentialSecretNewInput = $('<input type="password">').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles);
var credentialResetWarning = $('<div class="form-tips form-warning" style="margin: 10px;"><i class="fa fa-warning"></i> This will delete all existing credentials</div>').hide().appendTo(credentialFormRows);
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);
var hideEditForm = function() {
@@ -950,13 +950,13 @@ RED.projects.settings = (function() {
}
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(filesContainer);
$('<button class="editor-button">Cancel</button>')
$('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var saveButton = $('<button class="editor-button">Save</button>')
var saveButton = $('<button class="editor-button">' + RED._("common.label.save") + '</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
@@ -1032,13 +1032,13 @@ RED.projects.settings = (function() {
var updateForm = function() {
if (activeProject.settings.credentialSecretInvalid) {
credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-warning");
credentialStateLabel.find(".user-settings-credentials-state").text("Invalid encryption key");
credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.invalidEncryptionKey"));
} else if (activeProject.settings.credentialsEncrypted) {
credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-lock");
credentialStateLabel.find(".user-settings-credentials-state").text("Encryption enabled");
credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.encryptionEnabled"));
} else {
credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-unlock");
credentialStateLabel.find(".user-settings-credentials-state").text("Encryption disabled");
credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.encryptionDisabled"));
}
credentialSecretResetButton.toggleClass('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted);
credentialSecretResetButton.prop('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted);
@@ -1050,7 +1050,7 @@ RED.projects.settings = (function() {
function createLocalBranchListSection(activeProject,pane) {
var localBranchContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<h4></h4>').text("Branches").appendTo(localBranchContainer);
$('<h4></h4>').text(RED._("sidebar.project.projectSettings.branches")).appendTo(localBranchContainer);
var row = $('<div class="user-settings-row projects-dialog-list"></div>').appendTo(localBranchContainer);
@@ -1063,7 +1063,7 @@ RED.projects.settings = (function() {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) {
container.addClass('red-ui-search-empty');
container.text("No branches");
container.text(RED._("sidebar.project.projectSettings.noBranches"));
return;
}
if (entry.current) {
@@ -1095,7 +1095,7 @@ RED.projects.settings = (function() {
.click(function(e) {
e.preventDefault();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to delete the local branch '"+entry.name+"'? This cannot be undone.", {
var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteConfirm", { name: entry.name }), {
type: "warning",
modal: true,
fixed: true,
@@ -1123,7 +1123,7 @@ RED.projects.settings = (function() {
},
400: {
'git_delete_branch_unmerged': function(error) {
notification = RED.notify("The local branch '"+entry.name+"' has unmerged changes that will be lost. Are you sure you want to delete it?", {
notification = RED.notify(RED._("sidebar.project.projectSettings.unmergedConfirm", { name: entry.name }), {
type: "warning",
modal: true,
fixed: true,
@@ -1135,7 +1135,7 @@ RED.projects.settings = (function() {
notification.close();
}
},{
text: 'Delete unmerged branch',
text: RED._("sidebar.project.projectSettings.deleteUnmergedBranch"),
click: function() {
options.url += "?force=true";
notification.close();
@@ -1183,14 +1183,14 @@ RED.projects.settings = (function() {
}
function createRemoteRepositorySection(activeProject,pane) {
$('<h3></h3>').text("Version Control").appendTo(pane);
$('<h3></h3>').text(RED._("sidebar.project.projectSettings.versionControl")).appendTo(pane);
createLocalBranchListSection(activeProject,pane);
var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var title = $('<h4></h4>').text("Git remotes").appendTo(repoContainer);
var title = $('<h4></h4>').text(RED._("sidebar.project.projectSettings.gitRemotes")).appendTo(repoContainer);
var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right; margin-right: 10px;">add remote</button>')
var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right; margin-right: 10px;">' + RED._("sidebar.project.projectSettings.addRemote") + '</button>')
.appendTo(title)
.click(function(evt) {
editRepoButton.attr('disabled',true);
@@ -1221,7 +1221,7 @@ RED.projects.settings = (function() {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) {
container.addClass('red-ui-search-empty');
container.text("No remotes");
container.text(RED._("sidebar.project.projectSettings.noRemotes"));
return;
} else {
$('<span class="entry-icon"><i class="fa fa-globe"></i></span>').appendTo(container);
@@ -1240,7 +1240,7 @@ RED.projects.settings = (function() {
.click(function(e) {
e.preventDefault();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to delete the remote '"+entry.name+"'?", {
var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteRemoteConfrim", { name: entry.name }), {
type: "warning",
modal: true,
fixed: true,
@@ -1252,7 +1252,7 @@ RED.projects.settings = (function() {
notification.close();
}
},{
text: 'Delete remote',
text: RED._("sidebar.project.projectSettings.deleteRemote"),
click: function() {
notification.close();
@@ -1315,10 +1315,10 @@ RED.projects.settings = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\.git)?(?:\/?|\#[\d\w\.\-_]+?)$/.test(remoteURLInput.val());
var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) {
remoteURLLabel.text("Do not include the username/password in the url");
remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule2"));
validRepo = false;
} else {
remoteURLLabel.text("https://, ssh:// or file://");
remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule"));
}
saveButton.attr('disabled',(!validName || !validRepo))
remoteNameInput.toggleClass('input-error',remoteNameInputChanged&&!validName);
@@ -1332,22 +1332,22 @@ RED.projects.settings = (function() {
var remoteNameInputChanged = false;
var remoteURLInputChanged = false;
$('<div class="projects-dialog-list-dialog-header">').text('Add remote').appendTo(addRemoteDialog);
$('<div class="projects-dialog-list-dialog-header">').text(RED._('sidebar.project.projectSettings.addRemote2')).appendTo(addRemoteDialog);
row = $('<div class="user-settings-row"></div>').appendTo(addRemoteDialog);
$('<label for=""></label>').text('Remote name').appendTo(row);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.remoteName")).appendTo(row);
var remoteNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
remoteNameInputChanged = true;
validateForm();
});
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
$('<label class="projects-edit-form-sublabel"><small>' + RED._("sidebar.project.projectSettings.nameRule") + '</small></label>').appendTo(row).find("small");
row = $('<div class="user-settings-row"></div>').appendTo(addRemoteDialog);
$('<label for=""></label>').text('URL').appendTo(row);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.url")).appendTo(row);
var remoteURLInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
remoteURLInputChanged = true;
validateForm()
});
var remoteURLLabel = $('<label class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row).find("small");
var remoteURLLabel = $('<label class="projects-edit-form-sublabel"><small>' + RED._("sidebar.project.projectSettings.urlRule") +'</small></label>').appendTo(row).find("small");
var hideEditForm = function() {
editRepoButton.attr('disabled',false);
@@ -1361,13 +1361,13 @@ RED.projects.settings = (function() {
}
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>')
.appendTo(addRemoteDialog);
$('<button class="editor-button">Cancel</button>')
$('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var saveButton = $('<button class="editor-button">Add remote</button>')
var saveButton = $('<button class="editor-button">' + RED._("sidebar.project.projectSettings.addRemote2") + '</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
@@ -1484,19 +1484,19 @@ RED.projects.settings = (function() {
utils = _utils;
addPane({
id:'main',
title: "Project", // TODO: nls
title: RED._("sidebar.project.name"),
get: createMainPane,
close: function() { }
});
addPane({
id:'deps',
title: "Dependencies", // TODO: nls
title: RED._("sidebar.project.dependencies"),
get: createDependenciesPane,
close: function() { }
});
addPane({
id:'settings',
title: "Settings", // TODO: nls
title: RED._("sidebar.project.settings"),
get: createSettingsPane,
close: function() {
if (popover) {

View File

@@ -24,18 +24,18 @@ RED.projects.userSettings = (function() {
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.user = currentGitSettings.user || {};
var title = $('<h3></h3>').text("Committer Details").appendTo(pane);
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.committerDetail")).appendTo(pane);
var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text("Leave blank to use system default");
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip"));
var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text('Username').appendTo(row);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text">').appendTo(row);
gitUsernameInput.val(currentGitSettings.user.name||"");
row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text('Email').appendTo(row);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||"");
}
@@ -44,10 +44,10 @@ RED.projects.userSettings = (function() {
function createSSHKeySection(pane) {
var container = $('<div class="user-settings-section"></div>').appendTo(pane);
var popover;
var title = $('<h3></h3>').text("SSH Keys").appendTo(container);
var subtitle = $('<div style="color:#aaa;"></div>').appendTo(container).text("Allows you to create secure connections to remote git repositories.");
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(container);
var subtitle = $('<div style="color:#aaa;"></div>').appendTo(container).text(RED._("editor:sidebar.project.userSettings.sshKeysTip"));
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="editor-button editor-button-small" style="float: right; margin-right: 10px;">add key</button>')
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="editor-button editor-button-small" style="float: right; margin-right: 10px;">'+RED._("editor:sidebar.project.userSettings.add")+'</button>')
.appendTo(subtitle)
.click(function(evt) {
addKeyButton.attr('disabled',true);
@@ -72,9 +72,9 @@ RED.projects.userSettings = (function() {
var validPassphrase = passphrase.length === 0 || passphrase.length >= 8;
passphraseInput.toggleClass('input-error',!validPassphrase);
if (!validPassphrase) {
passphraseInputSubLabel.text("Passphrase too short");
passphraseInputSubLabel.text(RED._("editor:sidebar.project.userSettings.passphraseShort"));
} else if (passphrase.length === 0) {
passphraseInputSubLabel.text("Optional");
passphraseInputSubLabel.text(RED._("editor:sidebar.project.userSettings.optional"));
} else {
passphraseInputSubLabel.text("");
}
@@ -91,11 +91,11 @@ RED.projects.userSettings = (function() {
var row = $('<div class="user-settings-row"></div>').appendTo(container);
var addKeyDialog = $('<div class="projects-dialog-list-dialog"></div>').hide().appendTo(row);
$('<div class="projects-dialog-list-dialog-header">').text('Add SSH Key').appendTo(addKeyDialog);
$('<div class="projects-dialog-list-dialog-header">').text(RED._("editor:sidebar.project.userSettings.addSshKey")).appendTo(addKeyDialog);
var addKeyDialogBody = $('<div>').appendTo(addKeyDialog);
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
$('<div style="color:#aaa;"></div>').appendTo(row).text("Generate a new public/private key pair");
$('<div style="color:#aaa;"></div>').appendTo(row).text(RED._("editor:sidebar.project.userSettings.addSshKeyTip"));
// var bg = $('<div></div>',{class:"button-group", style:"text-align: center"}).appendTo(row);
// var addLocalButton = $('<button class="editor-button toggle selected">use local key</button>').appendTo(bg);
// var uploadButton = $('<button class="editor-button toggle">upload key</button>').appendTo(bg);
@@ -125,19 +125,19 @@ RED.projects.userSettings = (function() {
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
$('<label for=""></label>').text('Name').appendTo(row);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.name")).appendTo(row);
var keyNameInputChanged = false;
var keyNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
keyNameInputChanged = true;
validateForm();
});
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
$('<label class="projects-edit-form-sublabel"><small>'+RED._("editor:sidebar.project.userSettings.nameRule")+'</small></label>').appendTo(row).find("small");
var generateKeyPane = $('<div>').appendTo(addKeyDialogBody);
row = $('<div class="user-settings-row"></div>').appendTo(generateKeyPane);
$('<label for=""></label>').text('Passphrase').appendTo(row);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.passphrase")).appendTo(row);
var passphraseInput = $('<input type="password">').appendTo(row).on("change keyup paste",validateForm);
var passphraseInputSubLabel = $('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row).find("small");
var passphraseInputSubLabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("editor:sidebar.project.userSettings.optional")+'</small></label>').appendTo(row).find("small");
// var addLocalKeyPane = $('<div>').hide().appendTo(addKeyDialogBody);
// row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane);
@@ -179,13 +179,13 @@ RED.projects.userSettings = (function() {
}
}
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(addKeyDialog);
$('<button class="editor-button">Cancel</button>')
$('<button class="editor-button">'+RED._("editor:sidebar.project.userSettings.cancel")+'</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var saveButton = $('<button class="editor-button">Generate key</button>')
var saveButton = $('<button class="editor-button">'+RED._("editor:sidebar.project.userSettings.generate")+'</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
@@ -264,7 +264,7 @@ RED.projects.userSettings = (function() {
utils.sendRequest(options);
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(row);
$('<button class="editor-button editor-button-small">Copy public key to clipboard</button>')
$('<button class="editor-button editor-button-small">'+RED._("editor:sidebar.project.userSettings.copyPublicKey")+'</button>')
.appendTo(formButtons)
.click(function(evt) {
try {
@@ -289,7 +289,7 @@ RED.projects.userSettings = (function() {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) {
container.addClass('red-ui-search-empty');
container.text("No SSH keys");
container.text(RED._("editor:sidebar.project.userSettings.noSshKeys"));
return;
}
var topRow = $('<div class="projects-dialog-ssh-key-header">').appendTo(container);
@@ -313,7 +313,7 @@ RED.projects.userSettings = (function() {
.click(function(e) {
e.stopPropagation();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to delete the SSH key '"+entry.name+"'? This cannot be undone.", {
var notification = RED.notify(RED._("editor:sidebar.project.userSettings.deleteConfirm", {name:entry.name}), {
type: 'warning',
modal: true,
fixed: true,
@@ -326,7 +326,7 @@ RED.projects.userSettings = (function() {
}
},
{
text: "Delete key",
text: RED._("editor:sidebar.project.userSettings.delete"),
click: function() {
notification.close();
var url = "settings/user/keys/"+entry.name;
@@ -400,7 +400,7 @@ RED.projects.userSettings = (function() {
utils = _utils;
RED.userSettings.add({
id:'gitconfig',
title: "Git config", // TODO: nls
title: RED._("editor:sidebar.project.userSettings.gitConfig"),
get: createSettingsPane,
close: function() {
var currentGitSettings = RED.settings.get('git') || {};

View File

@@ -22,18 +22,18 @@ RED.projects = (function() {
function reportUnexpectedError(error) {
var notification;
if (error.error === 'git_missing_user') {
notification = RED.notify("<p>You Git client is not configured with a username/email.</p>",{
notification = RED.notify("<p>"+RED._("projects.errors.no-username-email")+"</p>",{
fixed: true,
type:'error',
buttons: [
{
text: "Cancel",
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
},
{
text: "Configure Git client",
text: RED._("projects.config-git"),
click: function() {
RED.userSettings.show('gitconfig');
notification.close();
@@ -43,13 +43,13 @@ RED.projects = (function() {
})
} else {
console.log(error);
notification = RED.notify("<p>An unexpected error occurred:</p><p>"+error.message+"</p><small>code: "+error.error+"</small>",{
notification = RED.notify("<p>"+RED._("projects.errors.unexpected")+":</p><p>"+error.message+"</p><small>"+RED._("projects.errors.code")+": "+error.error+"</small>",{
fixed: true,
modal: true,
type: 'error',
buttons: [
{
text: "Close",
text: RED._("common.label.close"),
click: function() {
notification.close();
}
@@ -75,14 +75,14 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Hello! We have introduced 'projects' to Node-RED.").appendTo(body);
$('<p>').text("This is a new way for you to manage your flow files and includes version control of your flows.").appendTo(body);
$('<p>').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body);
$('<p>').text("If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.").appendTo(body);
$('<p>').text(RED._("projects.welcome.hello")).appendTo(body);
$('<p>').text(RED._("projects.welcome.desc0")).appendTo(body);
$('<p>').text(RED._("projects.welcome.desc1")).appendTo(body);
$('<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="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Create Project</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>Clone Repository</button>').appendTo(row);
var createAsEmpty = $('<button data-type="empty" class="editor-button 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="editor-button 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.click(function(e) {
e.preventDefault();
@@ -105,7 +105,7 @@ RED.projects = (function() {
buttons: [
{
// id: "clipboard-dialog-cancel",
text: "Not right now",
text: RED._("projects.welcome.not-right-now"),
click: function() {
createProjectOptions = {};
$( this ).dialog( "close" );
@@ -139,23 +139,23 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Setup your version control client").appendTo(body);
$('<p>').text("Node-RED uses the open source tool Git for version control. It tracks changes to your project files and lets you push them to remote repositories.").appendTo(body);
$('<p>').text("When you commit a set of changes, Git records who made the changes with a username and email address. The Username can be anything you want - it does not need to be your real name.").appendTo(body);
$('<p>').text(RED._("projects.git-config.setup")).appendTo(body);
$('<p>').text(RED._("projects.git-config.desc0")).appendTo(body);
$('<p>').text(RED._("projects.git-config.desc1")).appendTo(body);
if (isGlobalConfig) {
$('<p>').text("Your Git client is already configured with the details below.").appendTo(body);
$('<p>').text(RED._("projects.git-config.desc2")).appendTo(body);
}
$('<p>').text("You can change these settings later under the 'Git config' tab of the settings dialog.").appendTo(body);
$('<p>').text(RED._("projects.git-config.desc3")).appendTo(body);
var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="">Username</label>').appendTo(row);
$('<label for="">'+RED._("projects.git-config.username")+'</label>').appendTo(row);
gitUsernameInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.name)||"").appendTo(row);
// $('<div style="position:relative;"></div>').text("This does not need to be your real name").appendTo(row);
gitUsernameInput.on("change keyup paste",validateForm);
row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="">Email</label>').appendTo(row);
$('<label for="">'+RED._("projects.git-config.email")+'</label>').appendTo(row);
gitEmailInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.email)||"").appendTo(row);
gitEmailInput.on("change keyup paste",validateForm);
// $('<div style="position:relative;"></div>').text("Something something email").appendTo(row);
@@ -168,14 +168,14 @@ RED.projects = (function() {
buttons: [
{
// id: "clipboard-dialog-cancel",
text: "Back",
text: RED._("common.label.back"),
click: function() {
show('welcome');
}
},
{
id: "projects-dialog-git-config",
text: "Next", // TODO: nls
text: RED._("common.label.next"),
class: "primary",
click: function() {
var currentGitSettings = RED.settings.get('git') || {};
@@ -216,10 +216,10 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Create your project").appendTo(body);
$('<p>').text("A project is maintained as a Git repository. It makes it much easier to share your flows with others and to collaborate on them.").appendTo(body);
$('<p>').text("You can create multiple projects and quickly switch between them from the editor.").appendTo(body);
$('<p>').text("To begin, your project needs a name and an optional description.").appendTo(body);
$('<p>').text(RED._("projects.project-details.create")).appendTo(body);
$('<p>').text(RED._("projects.project-details.desc0")).appendTo(body);
$('<p>').text(RED._("projects.project-details.desc1")).appendTo(body);
$('<p>').text(RED._("projects.project-details.desc2")).appendTo(body);
var validateForm = function() {
var projectName = projectNameInput.val();
@@ -236,14 +236,14 @@ RED.projects = (function() {
projectNameValid = false;
valid = false;
if (projectList[projectName]) {
projectNameSublabel.text("Project already exists");
projectNameSublabel.text(RED._("projects.project-details.already-exists"));
} else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameSublabel.text(RED._("projects.project-details.must-contain"));
}
} else {
projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameSublabel.text(RED._("projects.project-details.must-contain"));
projectNameValid = true;
}
projectNameLastChecked = projectName;
@@ -253,7 +253,7 @@ RED.projects = (function() {
}
var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-name">'+RED._("projects.project-details.project-name")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').val(createProjectOptions.name||"").appendTo(subrow);
@@ -283,13 +283,13 @@ RED.projects = (function() {
checkProjectName = null;
},300)
});
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.project-details.must-contain")+'</small></label>').appendTo(row).find("small");
// Empty Project
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-desc">'+RED._("projects.project-details.desc")+'</label>').appendTo(row);
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').val(createProjectOptions.summary||"").appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.project-details.opt")+'</small></label>').appendTo(row);
setTimeout(function() {
projectNameInput.focus();
@@ -300,7 +300,7 @@ RED.projects = (function() {
buttons: function(options) {
return [
{
text: "Back",
text: RED._("common.label.back"),
click: function() {
show('git-config');
}
@@ -308,7 +308,7 @@ RED.projects = (function() {
{
id: "projects-dialog-create-name",
disabled: true,
text: "Next", // TODO: nls
text: RED._("common.label.next"),
class: "primary disabled",
click: function() {
createProjectOptions.name = projectNameInput.val();
@@ -344,8 +344,8 @@ RED.projects = (function() {
var container = $('<div class="projects-dialog-screen-start"></div>');
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Clone a project").appendTo(body);
$('<p>').text("If you already have a git repository containing a project, you can clone it to get started.").appendTo(body);
$('<p>').text(RED._("projects.clone-project.clone")).appendTo(body);
$('<p>').text(RED._("projects.clone-project.desc0")).appendTo(body);
var projectList = null;
var pendingFormValidation = false;
@@ -376,14 +376,14 @@ RED.projects = (function() {
projectNameValid = false;
valid = false;
if (projectList[projectName]) {
projectNameSublabel.text("Project already exists");
projectNameSublabel.text(RED._("projects.clone-project.already-exists"));
} else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameSublabel.text(RED._("projects.clone-project.must-contain"));
}
} else {
projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameSublabel.text(RED._("projects.clone-project.must-contain"));
projectNameValid = true;
}
projectNameLastChecked = projectName;
@@ -395,7 +395,7 @@ RED.projects = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) {
$("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.no-info-in-url"));
validRepo = false;
}
if (!validRepo) {
@@ -426,7 +426,7 @@ RED.projects = (function() {
var row;
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-name">'+RED._("projects.clone-project.project-name")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
@@ -456,19 +456,19 @@ RED.projects = (function() {
checkProjectName = null;
},300)
});
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.clone-project.must-contain")+'</small></label>').appendTo(row).find("small");
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo">'+RED._("projects.clone-project.git-url")+'</label>').appendTo(row);
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>'+RED._("projects.clone-project.protocols")+'</small></label>').appendTo(row);
var projectRepoChanged = false;
var lastProjectRepo = "";
projectRepoInput.on("change keyup paste",function() {
projectRepoChanged = true;
var repo = $(this).val();
if (lastProjectRepo !== repo) {
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.protocols"));
}
lastProjectRepo = repo;
@@ -486,24 +486,24 @@ RED.projects = (function() {
var cloneAuthRows = $('<div class="projects-dialog-screen-create-row"></div>').appendTo(body);
row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows);
$('<div><i class="fa fa-warning"></i> Authentication failed</div>').appendTo(row);
$('<div><i class="fa fa-warning"></i> '+RED._("projects.clone-project.auth-failed")+'</div>').appendTo(row);
// Repo credentials - username/password ----------------
row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows);
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-user">'+RED._("projects.clone-project.username")+'</label>').appendTo(subrow);
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-pass">'+RED._("projects.clone-project.passwd")+'</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
// -----------------------------------------------------
// Repo credentials - key/passphrase -------------------
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows);
subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Key</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.clone-project.ssh-key")+'</label>').appendTo(subrow);
projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow);
$.getJSON("settings/user/keys", function(data) {
@@ -523,14 +523,14 @@ RED.projects = (function() {
}
});
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">Passphrase</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.clone-project.passphrase")+'</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> Before you can clone a repository over ssh you must add an SSH key to access it.</div>').appendTo(sshwarningRow);
$('<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="editor-button">Add an ssh key</button>').appendTo(subrow).click(function(e) {
$('<button class="editor-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).click(function(e) {
e.preventDefault();
$('#projects-dialog-cancel').click();
RED.userSettings.show('gitconfig');
@@ -543,7 +543,7 @@ RED.projects = (function() {
// Secret - clone
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label>Credentials encryption key</label>').appendTo(row);
$('<label>'+RED._("projects.clone-project.credential-key")+'</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row);
@@ -553,7 +553,7 @@ RED.projects = (function() {
buttons: function(options) {
return [
{
text: "Back",
text: RED._("common.label.back"),
click: function() {
show('git-config');
}
@@ -561,7 +561,7 @@ RED.projects = (function() {
{
id: "projects-dialog-clone-project",
disabled: true,
text: "Clone project", // TODO: nls
text: RED._("common.label.clone"),
class: "primary disabled",
click: function() {
var projectType = $(".projects-dialog-screen-create-type.selected").data('type');
@@ -585,7 +585,7 @@ RED.projects = (function() {
};
}
else {
console.log("Error! Can't get selected SSH key path.");
console.log(RED._("projects.clone-project.cant-get-ssh-key"));
return;
}
}
@@ -602,7 +602,7 @@ RED.projects = (function() {
}
$(".projects-dialog-screen-create-row-auth-error").hide();
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.protocols"));
projectRepoUserInput.removeClass("input-error");
projectRepoPasswordInput.removeClass("input-error");
@@ -622,22 +622,22 @@ RED.projects = (function() {
},
400: {
'project_exists': function(error) {
console.log("already exists");
console.log(RED._("projects.clone-project.already-exists2"));
},
'git_error': function(error) {
console.log("git error",error);
console.log(RED._("projects.clone-project.git-error"),error);
},
'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Connection failed");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.connection-failed"));
},
'git_not_a_repository': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.not-git-repo"));
},
'git_repository_not_found': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Repository not found");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.repo-not-found"));
},
'git_auth_failed': function(error) {
$(".projects-dialog-screen-create-row-auth-error").show();
@@ -689,11 +689,11 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Create your project files").appendTo(body);
$('<p>').text("A project contains your flow files, a README file and a package.json file.").appendTo(body);
$('<p>').text("It can contain any other files you want to maintain in the Git repository.").appendTo(body);
$('<p>').text(RED._("projects.default-files.create")).appendTo(body);
$('<p>').text(RED._("projects.default-files.desc0")).appendTo(body);
$('<p>').text(RED._("projects.default-files.desc1")).appendTo(body);
if (!options.existingProject && RED.settings.files) {
$('<p>').text("Your existing flow and credential files will be copied into the project.").appendTo(body);
$('<p>').text(RED._("projects.default-files.desc2")).appendTo(body);
}
var validateForm = function() {
@@ -724,7 +724,7 @@ RED.projects = (function() {
$("#projects-dialog-create-default-files").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
}
var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json";
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
@@ -735,7 +735,7 @@ RED.projects = (function() {
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-credfile">Credentials file</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectCredentialFileInput = $('<div style="width: 100%" class="uneditable-input" id="projects-dialog-screen-create-project-credentials">').text(defaultCredentialsFile)
.appendTo(subrow);
@@ -752,7 +752,7 @@ RED.projects = (function() {
return [
{
// id: "clipboard-dialog-cancel",
text: options.existingProject?"Cancel":"Back",
text: RED._(options.existingProject ? "common.label.cancel": "common.label.back"),
click: function() {
if (options.existingProject) {
$(this).dialog('close');
@@ -763,7 +763,7 @@ RED.projects = (function() {
},
{
id: "projects-dialog-create-default-files",
text: "Next", // TODO: nls
text: RED._("common.label.next"),
class: "primary",
click: function() {
createProjectOptions.files = {
@@ -789,22 +789,22 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Setup encryption of your credentials file").appendTo(body);
$('<p>').text(RED._("projects.encryption-config.setup")).appendTo(body);
if (options.existingProject) {
$('<p>').text("Your flow credentials file can be encrypted to keep its contents secure.").appendTo(body);
$('<p>').text("If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.").appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc0")).appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc1")).appendTo(body);
} else {
if (RED.settings.flowEncryptionType === 'disabled') {
$('<p>').text("Your flow credentials file is not currently encrypted.").appendTo(body);
$('<p>').text("That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.").appendTo(body);
$('<p>').text("If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.").appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc2")).appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc3")).appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc4")).appendTo(body);
} else {
if (RED.settings.flowEncryptionType === 'user') {
$('<p>').text("Your flow credentials file is currently encrypted using the credentialSecret property from your settings file as the key.").appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc5")).appendTo(body);
} else if (RED.settings.flowEncryptionType === 'system') {
$('<p>').text("Your flow credentials file is currently encrypted using a system-generated key. You should provide a new secret key for this project.").appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc6")).appendTo(body);
}
$('<p>').text("The key will be stored separately from your project files. You will need to provide the key to use this project in another instance of Node-RED.").appendTo(body);
$('<p>').text(RED._("projects.encryption-config.desc7")).appendTo(body);
}
}
@@ -832,16 +832,16 @@ RED.projects = (function() {
var row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body);
$('<label>Credentials</label>').appendTo(row);
$('<label>'+RED._("projects.encryption-config.credentials")+'</label>').appendTo(row);
var credentialsBox = $('<div style="width: 550px">').appendTo(row);
var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox);
var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox);
var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">Enable encryption</span></label>').appendTo(credentialsEnabledBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.enable")+'</span></label>').appendTo(credentialsEnabledBox);
var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">Disable encryption</span></label>').appendTo(credentialsDisabledBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.disable")+'</span></label>').appendTo(credentialsDisabledBox);
credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) {
var val = $(this).val();
@@ -876,15 +876,15 @@ RED.projects = (function() {
})
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+'" style="margin-left: 5px"><input '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+' type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="default" name="projects-encryption-key"> <span style="vertical-align: middle;">Copy over existing key</span></label>').appendTo(row);
$('<label class="projects-edit-form-inline-label '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+'" style="margin-left: 5px"><input '+((RED.settings.flowEncryptionType !== 'user')?RED._("projects.encryption-config.disabled"):'')+' type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="default" name="projects-encryption-key"> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.copy")+'</span></label>').appendTo(row);
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="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;">Use custom key</span></label>').appendTo(row);
$('<label class="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.on("change keyup paste", validateForm);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> '+RED._("projects.encryption-config.desc8")+'</div>').appendTo(row);
credentialsRightBox.find("input[name=projects-encryption-key]").click(function() {
var val = $(this).val();
@@ -911,14 +911,14 @@ RED.projects = (function() {
return [
{
// id: "clipboard-dialog-cancel",
text: "Back",
text: RED._("common.label.back"),
click: function() {
show('default-files',options);
}
},
{
id: "projects-dialog-create-encryption",
text: options.existingProject?"Create project files":"Create project", // TODO: nls
text: RED._(options.existingProject?"projects.encryption-config.create-project-files":"projects.encryption-config.create-project"),
class: "primary disabled",
disabled: true,
click: function() {
@@ -966,10 +966,10 @@ RED.projects = (function() {
},
400: {
'project_exists': function(error) {
console.log("already exists");
console.log(RED._("projects.encryption-config.already-exists"));
},
'git_error': function(error) {
console.log("git error",error);
console.log(RED._("projects.encryption-config.git-error"),error);
},
'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error");
@@ -978,7 +978,7 @@ RED.projects = (function() {
projectRepoUserInput.addClass("input-error");
projectRepoPasswordInput.addClass("input-error");
// getRepoAuthDetails(req);
console.log("git auth error",error);
console.log(RED._("projects.encryption-config.git-auth-error"),error);
},
'*': function(error) {
reportUnexpectedError(error);
@@ -1004,19 +1004,16 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("You have successfully created your first project!").appendTo(body);
$('<p>').text("You can now continue to use Node-RED just as you always have.").appendTo(body);
$('<p>').text("The 'info' tab in the sidebar shows you what your current active project is. "+
"The button next to the name can be used to access the project settings view.").appendTo(body);
$('<p>').text("The 'history' tab in the sidebar can be used to view files that have changed "+
"in your project and to commit them. It shows you a complete history of your commits and "+
"allows you to push your changes to a remote repository.").appendTo(body);
$('<p>').text(RED._("projects.create-success.success")).appendTo(body);
$('<p>').text(RED._("projects.create-success.desc0")).appendTo(body);
$('<p>').text(RED._("projects.create-success.desc1")).appendTo(body);
$('<p>').text(RED._("projects.create-success.desc2")).appendTo(body);
return container;
},
buttons: [
{
text: "Done",
text: RED._("common.label.done"),
click: function() {
$( this ).dialog( "close" );
}
@@ -1043,7 +1040,7 @@ RED.projects = (function() {
var selectedProject;
return {
title: "Projects", // TODO: NLS
title: RED._("projects.create.projects"),
content: function(options) {
var projectList = null;
selectedProject = null;
@@ -1077,14 +1074,14 @@ RED.projects = (function() {
projectNameValid = false;
valid = false;
if (projectList[projectName]) {
projectNameSublabel.text("Project already exists");
projectNameSublabel.text(RED._("projects.create.already-exists"));
} else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameSublabel.text(RED._("projects.create.must-contain"));
}
} else {
projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameSublabel.text(RED._("projects.create.must-contain"));
projectNameValid = true;
}
projectNameLastChecked = projectName;
@@ -1102,7 +1099,7 @@ RED.projects = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) {
$("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.no-info-in-url"));
validRepo = false;
}
if (!validRepo) {
@@ -1159,10 +1156,10 @@ RED.projects = (function() {
row = $('<div class="form-row button-group"></div>').appendTo(container);
var openProject = $('<button data-type="open" class="editor-button 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/>Open Project</button>').appendTo(row);
var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Create Project</button>').appendTo(row);
var openProject = $('<button data-type="open" class="editor-button 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="editor-button 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="editor-button 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="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>Clone Repository</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="editor-button 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="editor-button 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(".projects-dialog-screen-create-type").click(function(evt) {
evt.preventDefault();
@@ -1173,9 +1170,9 @@ RED.projects = (function() {
validateForm();
projectNameInput.focus();
switch ($(this).data('type')) {
case "open": $("#projects-dialog-create").text("Open project"); break;
case "empty": $("#projects-dialog-create").text("Create project"); break;
case "clone": $("#projects-dialog-create").text("Clone project"); break;
case "open": $("#projects-dialog-create").text(RED._("projects.create.open")); break;
case "empty": $("#projects-dialog-create").text(RED._("projects.create.create")); break;
case "clone": $("#projects-dialog-create").text(RED._("projects.create.clone")); break;
}
})
@@ -1201,7 +1198,7 @@ RED.projects = (function() {
}).appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-name">'+RED._("projects.create.project-name")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
@@ -1231,16 +1228,16 @@ RED.projects = (function() {
checkProjectName = null;
},300)
});
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.create.must-contain")+'</small></label>').appendTo(row).find("small");
// Empty Project
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-desc">'+RED._("projects.create.desc")+'</label>').appendTo(row);
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.create.opt")+'</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val("flow.json")
.on("change keyup paste",validateForm)
@@ -1249,16 +1246,16 @@ RED.projects = (function() {
$('<label class="projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Credentials</label>').appendTo(row);
$('<label>'+RED._("projects.create.credentials")+'</label>').appendTo(row);
var credentialsBox = $('<div style="width: 550px">').appendTo(row);
var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox);
var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox);
var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" checked style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">Enable encryption</span></label>').appendTo(credentialsEnabledBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" checked style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">'+RED._("projects.create.enable-encryption")+'</span></label>').appendTo(credentialsEnabledBox);
var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">Disable encryption</span></label>').appendTo(credentialsDisabledBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">'+RED._("projects.create.disable-encryption")+'</span></label>').appendTo(credentialsDisabledBox);
credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) {
var val = $(this).val();
@@ -1292,15 +1289,15 @@ RED.projects = (function() {
})
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label">Encryption key</label>').appendTo(row);
$('<label class="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.on("change keyup paste", validateForm);
$('<label class="projects-edit-form-sublabel"><small>A phrase to secure your credentials with</small></label>').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.create.desc0")+'</small></label>').appendTo(row);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> '+RED._("projects.create.desc1")+'</div>').appendTo(row);
credentialsRightBox.find("input[name=projects-encryption-key]").click(function() {
var val = $(this).val();
@@ -1313,9 +1310,9 @@ RED.projects = (function() {
// Clone Project
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo">'+RED._("projects.create.git-url")+'</label>').appendTo(row);
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>'+RED._("projects.create.protocols")+'</small></label>').appendTo(row);
var projectRepoChanged = false;
var lastProjectRepo = "";
@@ -1323,7 +1320,7 @@ RED.projects = (function() {
projectRepoChanged = true;
var repo = $(this).val();
if (lastProjectRepo !== repo) {
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.protocols"));
}
lastProjectRepo = repo;
@@ -1342,24 +1339,24 @@ RED.projects = (function() {
var cloneAuthRows = $('<div class="hide projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').hide().appendTo(container);
row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows);
$('<div><i class="fa fa-warning"></i> Authentication failed</div>').appendTo(row);
$('<div><i class="fa fa-warning"></i> '+RED._("projects.create.auth-failed")+'</div>').appendTo(row);
// Repo credentials - username/password ----------------
row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows);
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-user">'+RED._("projects.create.username")+'</label>').appendTo(subrow);
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-pass">'+RED._("projects.create.password")+'</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
// -----------------------------------------------------
// Repo credentials - key/passphrase -------------------
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows);
subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Key</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.create.ssh-key")+'</label>').appendTo(subrow);
projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow);
$.getJSON("settings/user/keys", function(data) {
@@ -1379,14 +1376,14 @@ RED.projects = (function() {
}
});
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">Passphrase</label>').appendTo(subrow);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.create.passphrase")+'</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> Before you can clone a repository over ssh you must add an SSH key to access it.</div>').appendTo(sshwarningRow);
$('<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="editor-button">Add an ssh key</button>').appendTo(subrow).click(function(e) {
$('<button class="editor-button">'+RED._("projects.create.add-ssh-key")+'</button>').appendTo(subrow).click(function(e) {
e.preventDefault();
$('#projects-dialog-cancel').click();
RED.userSettings.show('gitconfig');
@@ -1399,7 +1396,7 @@ RED.projects = (function() {
// Secret - clone
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>Credentials encryption key</label>').appendTo(row);
$('<label>'+RED._("projects.create.credentials-encryption-key")+'</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row);
@@ -1421,9 +1418,9 @@ RED.projects = (function() {
buttons: function(options) {
var initialLabel;
switch (options.screen||"empty") {
case "open": initialLabel = "Open project"; break;
case "empty": initialLabel = "Create project"; break;
case "clone": initialLabel = "Clone project"; break;
case "open": initialLabel = RED._("projects.create.open"); break;
case "empty": initialLabel = RED._("projects.create.create"); break;
case "clone": initialLabel = RED._("projects.create.clone"); break;
}
return [
{
@@ -1477,7 +1474,7 @@ RED.projects = (function() {
};
}
else {
console.log("Error! Can't get selected SSH key path.");
console.log(RED._("projects.create.cant-get-ssh-key-path"));
return;
}
}
@@ -1497,14 +1494,14 @@ RED.projects = (function() {
dialog.dialog( "close" );
if (err) {
if (err.error !== 'credentials_load_failed') {
console.log("unexpected_error",err)
console.log(RED._("projects.create.unexpected_error"),err)
}
}
})
}
$(".projects-dialog-screen-create-row-auth-error").hide();
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.protocols"));
projectRepoUserInput.removeClass("input-error");
projectRepoPasswordInput.removeClass("input-error");
@@ -1524,22 +1521,22 @@ RED.projects = (function() {
},
400: {
'project_exists': function(error) {
console.log("already exists");
console.log(RED._("projects.create.already-exists-2"));
},
'git_error': function(error) {
console.log("git error",error);
console.log(RED._("projects.create.git-error"),error);
},
'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Connection failed");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.con-failed"));
},
'git_not_a_repository': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.not-git"));
},
'git_repository_not_found': function(error) {
projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Repository not found");
$("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.no-resource"));
},
'git_auth_failed': function(error) {
$(".projects-dialog-screen-create-row-auth-error").show();
@@ -1619,15 +1616,15 @@ RED.projects = (function() {
whitespace: "nowrap",
width:"1000px"
}).click(function(evt) { evt.stopPropagation(); }).appendTo(row);
$('<span>').css({"lineHeight":"40px"}).text("Are you sure you want to delete this project?").appendTo(cover);
$('<button style="margin-left:20px" class="editor-button">Cancel</button>')
$('<span>').css({"lineHeight":"40px"}).text(RED._("projects.delete.confirm")).appendTo(cover);
$('<button style="margin-left:20px" class="editor-button">'+RED._("common.label.cancel")+'</button>')
.appendTo(cover)
.click(function(e) {
e.stopPropagation();
cover.remove();
done(true);
});
$('<button style="margin-left:20px" class="editor-button primary">Delete</button>')
$('<button style="margin-left:20px" class="editor-button primary">'+RED._("common.label.delete")+'</button>')
.appendTo(cover)
.click(function(e) {
e.stopPropagation();
@@ -1681,7 +1678,7 @@ RED.projects = (function() {
var filterTerm = "";
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(container);
var searchInput = $('<input id="projects-dialog-project-list-search" type="text" placeholder="search your projects">').appendTo(searchDiv).searchBox({
var searchInput = $('<input id="projects-dialog-project-list-search" type="text" placeholder="'+RED._("projects.create-project-list.search")+'">').appendTo(searchDiv).searchBox({
//data-i18n="[placeholder]menu.label.searchInput"
delay: 200,
change: function() {
@@ -1790,7 +1787,7 @@ RED.projects = (function() {
$('<span class="projects-dialog-project-list-entry-name" style=""></span>').text(entry.name).appendTo(header);
if (activeProject && activeProject.name === entry.name) {
header.addClass("projects-list-entry-current");
$('<span class="projects-dialog-project-list-entry-current">current</span>').appendTo(header);
$('<span class="projects-dialog-project-list-entry-current">'+RED._("projects.create-project-list.current")+'</span>').appendTo(header);
if (options.canSelectActive === false) {
// active project cannot be selected; so skip the rest
return
@@ -1852,7 +1849,7 @@ RED.projects = (function() {
function requireCleanWorkspace(done) {
if (RED.nodes.dirty()) {
var message = '<p>You have undeployed changes that will be lost.</p><p>Do you want to continue?</p>';
var message = RED._("projects.require-clean.confirm");
var cleanNotification = RED.notify(message,{
type:"info",
fixed: true,
@@ -1867,7 +1864,7 @@ RED.projects = (function() {
done(true);
}
},{
text: 'Continue',
text: RED._("common.label.cont"),
click: function() {
cleanNotification.close();
done(false);
@@ -1945,14 +1942,14 @@ RED.projects = (function() {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
var message = $('<div>'+
'<div class="form-row">Authentication required for repository:</div>'+
'<div class="form-row">'+RED._("projects.send-req.auth-req")+':</div>'+
'<div class="form-row"><div style="margin-left: 20px;">'+url+'</div></div>'+
'</div>');
var isSSH = false;
if (/^https?:\/\//.test(url)) {
$('<div class="form-row"><label for="projects-user-auth-username">Username</label><input id="projects-user-auth-username" type="text"></input></div>'+
'<div class="form-row"><label for=projects-user-auth-password">Password</label><input id="projects-user-auth-password" type="password"></input></div>').appendTo(message);
$('<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);
} else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) {
isSSH = true;
var row = $('<div class="form-row"></div>').appendTo(message);
@@ -1969,7 +1966,7 @@ RED.projects = (function() {
}
});
row = $('<div class="form-row"></div>').appendTo(message);
$('<label for="projects-user-auth-passphrase">Passphrase</label>').appendTo(row);
$('<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);
}
@@ -1986,7 +1983,7 @@ RED.projects = (function() {
notification.close();
}
},{
text: $('<span><i class="fa fa-refresh"></i> Retry</span>'),
text: '<span><i class="fa fa-refresh"></i> ' +RED._("projects.send-req.retry") +'</span>',
click: function() {
body = body || {};
var authBody = {};
@@ -1999,7 +1996,7 @@ RED.projects = (function() {
}
var done = function(err) {
if (err) {
console.log("Failed to update auth");
console.log(RED._("projects.send-req.update-failed"));
console.log(err);
} else {
sendRequest(options,body);
@@ -2039,7 +2036,7 @@ RED.projects = (function() {
return;
}
}
console.log("Unhandled error response:");
console.log(RED._("projects.send-req.unhandled")+":");
console.log(xhr);
console.log(textStatus);
console.log(err);
@@ -2073,7 +2070,7 @@ RED.projects = (function() {
branchFilterCreateItem.addClass("input-error");
branchFilterCreateItem.find("i").addClass("fa-warning").removeClass("fa-code-fork");
}
branchFilterCreateItem.find("span").text("Invalid branch: "+branchPrefix+branchFilterTerm);
branchFilterCreateItem.find("span").text(RED._("projects.create-branch-list.invalid")+": "+branchPrefix+branchFilterTerm);
} else {
if (branchFilterCreateItem.hasClass("input-error")) {
branchFilterCreateItem.removeClass("input-error");
@@ -2093,14 +2090,14 @@ RED.projects = (function() {
if (!entry.hasOwnProperty('commit')) {
branchFilterCreateItem = container;
$('<i class="fa fa-code-fork"></i>').appendTo(container);
$('<span>').text("Create branch:").appendTo(container);
$('<span>').text(RED._("projects.create-branch-list.create")+":").appendTo(container);
$('<div class="sidebar-version-control-branch-list-entry-create-name" style="margin-left: 10px;">').text(entry.name).appendTo(container);
} else {
$('<i class="fa fa-code-fork"></i>').appendTo(container);
$('<span>').text(entry.name).appendTo(container);
if (entry.current) {
container.addClass("selected");
$('<span class="current"></span>').text(options.currentLabel||"current").appendTo(container);
$('<span class="current"></span>').text(options.currentLabel||RED._("projects.create-branch-list.current")).appendTo(container);
}
}
container.click(function(evt) {
@@ -2240,9 +2237,9 @@ RED.projects = (function() {
function createDefaultFileSet() {
if (!activeProject) {
throw new Error("Cannot create default file set without an active project");
throw new Error(RED._("projects.create-default-file-set.no-active"));
} else if (!activeProject.empty) {
throw new Error("Cannot create default file set on a non-empty project");
throw new Error(RED._("projects.create-default-file-set.no-empty"));
}
if (!RED.user.hasPermission("projects.write")) {
RED.notify(RED._("user.errors.notAuthorized"),"error");
@@ -2269,7 +2266,7 @@ RED.projects = (function() {
200: function(data) { },
400: {
'git_error': function(error) {
console.log("git error",error);
console.log(RED._("projects.create-default-file-set.git-error"),error);
},
'missing_flow_file': function(error) {
// This is a natural next error - but let the runtime event

View File

@@ -52,11 +52,11 @@ RED.sidebar.versionControl = (function() {
200: function(data) {
var title;
if (state === 'unstaged') {
title = 'Unstaged changes : '+entry.file
title = RED._("sidebar.project.versionControl.unstagedChanges")+' : '+entry.file
} else if (state === 'staged') {
title = 'Staged changes : '+entry.file
title = RED._("sidebar.project.versionControl.stagedChanges")+' : '+entry.file
} else {
title = 'Resolve conflicts : '+entry.file
title = RED._("sidebar.project.versionControl.resolveConflicts")+' : '+entry.file
}
var options = {
diff: data.diff,
@@ -65,18 +65,18 @@ RED.sidebar.versionControl = (function() {
project: activeProject
}
if (state == 'unstaged') {
options.oldRevTitle = entry.indexStatus === " "?"HEAD":"Staged";
options.newRevTitle = "Unstaged";
options.oldRevTitle = entry.indexStatus === " "?RED._("sidebar.project.versionControl.head"):RED._("sidebar.project.versionControl.staged");
options.newRevTitle = RED._("sidebar.project.versionControl.unstaged");
options.oldRev = entry.indexStatus === " "?"@":":0";
options.newRev = "_";
} else if (state === 'staged') {
options.oldRevTitle = "HEAD";
options.newRevTitle = "Staged";
options.oldRevTitle = RED._("sidebar.project.versionControl.head");
options.newRevTitle = RED._("sidebar.project.versionControl.staged");
options.oldRev = "@";
options.newRev = ":0";
} else {
options.oldRevTitle = "Local";
options.newRevTitle = "Remote";
options.oldRevTitle = RED._("sidebar.project.versionControl.local");
options.newRevTitle = RED._("sidebar.project.versionControl.remote");
options.commonRev = ":1";
options.oldRev = ":2";
options.newRev = ":3";
@@ -156,7 +156,7 @@ RED.sidebar.versionControl = (function() {
evt.preventDefault();
var spinner = utils.addSpinnerOverlay(container).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to revert the changes to '"+entry.file+"'? This cannot be undone.", {
var notification = RED.notify(RED._("sidebar.project.versionControl.revert",{file:entry.file}), {
type: "warning",
modal: true,
fixed: true,
@@ -168,7 +168,7 @@ RED.sidebar.versionControl = (function() {
notification.close();
}
},{
text: 'Revert changes',
text: RED._("sidebar.project.versionControl.revertChanges"),
click: function() {
notification.close();
var activeProject = RED.projects.getActiveProject();
@@ -281,6 +281,8 @@ RED.sidebar.versionControl = (function() {
entry["update"+((state==='unstaged')?"Unstaged":"Staged")](entry, status);
}
var utils;
var emptyStagedItem;
var emptyMergedItem;
function init(_utils) {
utils = _utils;
@@ -312,7 +314,7 @@ RED.sidebar.versionControl = (function() {
});
localChanges = sections.add({
title: "Local Changes",
title: RED._("sidebar.project.versionControl.localChanges"),
collapsible: true
});
localChanges.expand();
@@ -326,10 +328,12 @@ RED.sidebar.versionControl = (function() {
refresh(true);
})
emptyStagedItem = { label: RED._("sidebar.project.versionControl.none") };
emptyMergedItem = { label: RED._("sidebar.project.versionControl.conflictResolve") };
var unstagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
var header = $('<div class="sidebar-version-control-change-header">Local files</div>').appendTo(unstagedContent);
stageAllButton = $('<button class="editor-button editor-button-small" style="float: right"><i class="fa fa-plus"></i> all</button>')
var header = $('<div class="sidebar-version-control-change-header">'+RED._("sidebar.project.versionControl.localFiles")+'</div>').appendTo(unstagedContent);
stageAllButton = $('<button class="editor-button editor-button-small" style="float: right"><i class="fa fa-plus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
.appendTo(header)
.click(function(evt) {
evt.preventDefault();
@@ -359,9 +363,9 @@ RED.sidebar.versionControl = (function() {
unmergedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
header = $('<div class="sidebar-version-control-change-header">Unmerged changes</div>').appendTo(unmergedContent);
header = $('<div class="sidebar-version-control-change-header">'+RED._("sidebar.project.versionControl.unmergedChanges")+'</div>').appendTo(unmergedContent);
bg = $('<div style="float: right"></div>').appendTo(header);
var abortMergeButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">abort merge</button>')
var abortMergeButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.abortMerge")+'</button>')
.appendTo(bg)
.click(function(evt) {
evt.preventDefault();
@@ -399,7 +403,7 @@ RED.sidebar.versionControl = (function() {
addItem: function(row,index,entry) {
if (entry === emptyMergedItem) {
entry.button = {
label: 'commit',
label: RED._("sidebar.project.versionControl.commit"),
click: function(evt) {
evt.preventDefault();
evt.stopPropagation();
@@ -423,7 +427,7 @@ RED.sidebar.versionControl = (function() {
var stagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
header = $('<div class="sidebar-version-control-change-header">Changes to commit</div>').appendTo(stagedContent);
header = $('<div class="sidebar-version-control-change-header">'+RED._("sidebar.project.versionControl.changeToCommit")+'</div>').appendTo(stagedContent);
bg = $('<div style="float: right"></div>').appendTo(header);
var showCommitBox = function() {
@@ -446,14 +450,14 @@ RED.sidebar.versionControl = (function() {
abortMergeButton.attr("disabled",true);
commitMessage.focus();
}
commitButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">commit</button>')
commitButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.commit")+'</button>')
.appendTo(bg)
.click(function(evt) {
evt.preventDefault();
evt.stopPropagation();
showCommitBox();
});
unstageAllButton = $('<button class="editor-button editor-button-small"><i class="fa fa-minus"></i> all</button>')
unstageAllButton = $('<button class="editor-button editor-button-small"><i class="fa fa-minus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
.appendTo(bg)
.click(function(evt) {
evt.preventDefault();
@@ -480,14 +484,14 @@ RED.sidebar.versionControl = (function() {
commitBox = $('<div class="sidebar-version-control-slide-box sidebar-version-control-slide-box-bottom"></div>').hide().appendTo(localChanges.content);
var commitMessage = $('<textarea placeholder="Enter your commit message"></textarea>')
var commitMessage = $('<textarea placeholder='+RED._("sidebar.project.versionControl.commitPlaceholder")+'></textarea>')
.appendTo(commitBox)
.on("change keyup paste",function() {
submitCommitButton.attr('disabled',$(this).val().trim()==="");
});
var commitToolbar = $('<div class="sidebar-version-control-slide-box-toolbar button-group">').appendTo(commitBox);
var cancelCommitButton = $('<button class="editor-button">Cancel</button>')
var cancelCommitButton = $('<button class="editor-button">'+RED._("sidebar.project.versionControl.cancelCapital")+'</button>')
.appendTo(commitToolbar)
.click(function(evt) {
evt.preventDefault();
@@ -505,7 +509,7 @@ RED.sidebar.versionControl = (function() {
abortMergeButton.attr("disabled",false);
})
var submitCommitButton = $('<button class="editor-button">Commit</button>')
var submitCommitButton = $('<button class="editor-button">'+RED._("sidebar.project.versionControl.commitCapital")+'</button>')
.appendTo(commitToolbar)
.click(function(evt) {
evt.preventDefault();
@@ -541,7 +545,7 @@ RED.sidebar.versionControl = (function() {
var localHistory = sections.add({
title: "Commit History",
title: RED._("sidebar.project.versionControl.commitHistory"),
collapsible: true
});
@@ -555,7 +559,7 @@ RED.sidebar.versionControl = (function() {
var localBranchToolbar = $('<div class="sidebar-version-control-change-header" style="text-align: right;"></div>').appendTo(localHistory.content);
var localBranchButton = $('<button class="editor-button editor-button-small"><i class="fa fa-code-fork"></i> Branch: <span id="sidebar-version-control-local-branch"></span></button>')
var localBranchButton = $('<button class="editor-button editor-button-small"><i class="fa fa-code-fork"></i> '+RED._("sidebar.project.versionControl.branch")+' <span id="sidebar-version-control-local-branch"></span></button>')
.appendTo(localBranchToolbar)
.click(function(evt) {
evt.preventDefault();
@@ -612,7 +616,7 @@ RED.sidebar.versionControl = (function() {
row.addClass('sidebar-version-control-commit-entry');
if (entry.url) {
row.addClass('sidebar-version-control-commit-more');
row.text("+ "+(entry.total-entry.totalKnown)+" more commit(s)");
row.text("+ "+(entry.total-entry.totalKnown)+RED._("sidebar.project.versionControl.moreCommits"));
row.click(function(e) {
e.preventDefault();
getCommits(entry.url,localCommitList,row,entry.limit,entry.before);
@@ -626,8 +630,8 @@ RED.sidebar.versionControl = (function() {
result.parents = entry.parents;
result.oldRev = entry.sha+"~1";
result.newRev = entry.sha;
result.oldRevTitle = "Commit "+entry.sha.substring(0,7)+"~1";
result.newRevTitle = "Commit "+entry.sha.substring(0,7);
result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1";
result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
result.date = humanizeSinceDate(parseInt(entry.date));
RED.diff.showCommitDiff(result);
});
@@ -666,10 +670,10 @@ RED.sidebar.versionControl = (function() {
}
var localBranchBox = $('<div class="sidebar-version-control-slide-box sidebar-version-control-slide-box-top" style="top:30px;"></div>').hide().appendTo(localHistory.content);
$('<div class="sidebar-version-control-slide-box-header"></div>').text("Change local branch").appendTo(localBranchBox);
$('<div class="sidebar-version-control-slide-box-header"></div>').text(RED._("sidebar.project.versionControl.changeLocalBranch")).appendTo(localBranchBox);
var localBranchList = utils.createBranchList({
placeholder: "Find or create a branch",
placeholder: RED._("sidebar.project.versionControl.createBranchPlaceholder"),
container: localBranchBox,
onselect: function(body) {
if (body.current) {
@@ -701,7 +705,7 @@ RED.sidebar.versionControl = (function() {
400: {
'git_local_overwrite': function(error) {
spinner.remove();
RED.notify("You have local changes that would be overwritten by changing the branch. You must either commit or undo those changes first.",{
RED.notify(RED._("sidebar.project.versionControl.localOverwrite"),{
type:'error',
timeout: 8000
});
@@ -744,10 +748,10 @@ RED.sidebar.versionControl = (function() {
},200);
}
}
$('<div class="sidebar-version-control-slide-box-header"></div>').text("Manage remote branch").appendTo(remoteBox);
$('<div class="sidebar-version-control-slide-box-header"></div>').text(RED._("sidebar.project.versionControl.manageRemoteBranch")).appendTo(remoteBox);
var remoteBranchRow = $('<div style="margin-bottom: 5px;"></div>').appendTo(remoteBox);
var remoteBranchButton = $('<button id="sidebar-version-control-repo-branch" class="sidebar-version-control-repo-action editor-button"><i class="fa fa-code-fork"></i> Remote: <span id="sidebar-version-control-remote-branch"></span></button>')
var remoteBranchButton = $('<button id="sidebar-version-control-repo-branch" class="sidebar-version-control-repo-action editor-button"><i class="fa fa-code-fork"></i> '+RED._("sidebar.project.versionControl.remote")+': <span id="sidebar-version-control-remote-branch"></span></button>')
.appendTo(remoteBranchRow)
.click(function(evt) {
evt.preventDefault();
@@ -770,9 +774,9 @@ RED.sidebar.versionControl = (function() {
var errorMessage = $('<div id="sidebar-version-control-repo-toolbar-error-message" class="sidebar-version-control-slide-box-header" style="min-height: 100px;"></div>').hide().appendTo(remoteBox);
$('<div style="margin-top: 10px;"><i class="fa fa-warning"></i> Unable to access remote repository</div>').appendTo(errorMessage)
$('<div style="margin-top: 10px;"><i class="fa fa-warning"></i> '+RED._("sidebar.project.versionControl.unableToAccess")+'</div>').appendTo(errorMessage)
var buttonRow = $('<div style="margin: 10px 30px; text-align: center"></div>').appendTo(errorMessage);
$('<button class="editor-button" style="width: 80%;"><i class="fa fa-refresh"></i> Retry</button>')
$('<button class="editor-button" style="width: 80%;"><i class="fa fa-refresh"></i> '+RED._("sidebar.project.versionControl.retry")+'</button>')
.appendTo(buttonRow)
.click(function(e) {
e.preventDefault();
@@ -810,12 +814,12 @@ RED.sidebar.versionControl = (function() {
});
})
$('<div class="sidebar-version-control-slide-box-header" style="height: 20px;"><label id="sidebar-version-control-repo-toolbar-set-upstream-row" for="sidebar-version-control-repo-toolbar-set-upstream" class="hide"><input type="checkbox" id="sidebar-version-control-repo-toolbar-set-upstream"> Set as upstream branch</label></div>').appendTo(remoteBox);
$('<div class="sidebar-version-control-slide-box-header" style="height: 20px;"><label id="sidebar-version-control-repo-toolbar-set-upstream-row" for="sidebar-version-control-repo-toolbar-set-upstream" class="hide"><input type="checkbox" id="sidebar-version-control-repo-toolbar-set-upstream"> '+RED._("sidebar.project.versionControl.setUpstreamBranch")+'</label></div>').appendTo(remoteBox);
var remoteBranchSubRow = $('<div style="height: 0;overflow:hidden; transition: height 0.2s ease-in-out;"></div>').hide().appendTo(remoteBranchRow);
var remoteBranchList = utils.createBranchList({
placeholder: "Find or create a remote branch",
currentLabel: "upstream",
placeholder: RED._("sidebar.project.versionControl.createRemoteBranchPlaceholder"),
currentLabel: RED._("sidebar.project.versionControl.upstream"),
remote: function() {
var project = RED.projects.getActiveProject();
var remotes = Object.keys(project.git.remotes);
@@ -845,11 +849,11 @@ RED.sidebar.versionControl = (function() {
})
} else {
if (!activeProject.git.branches.remote) {
$('#sidebar-version-control-repo-toolbar-message').text("The created branch will be set as the tracked upstream branch.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.trackedUpstreamBranch"));
$("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked',true);
$("#sidebar-version-control-repo-toolbar-set-upstream").prop('disabled',true);
} else {
$('#sidebar-version-control-repo-toolbar-message').text("The branch will be created. Select below to set it as the tracked upstream branch.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.selectUpstreamBranch"));
}
$("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',false);
@@ -861,7 +865,7 @@ RED.sidebar.versionControl = (function() {
var row = $('<div style="margin-bottom: 5px;"></div>').appendTo(remoteBox);
$('<button id="sidebar-version-control-repo-push" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-up"></i> <span>push</span></button>')
$('<button id="sidebar-version-control-repo-push" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-up"></i> <span data-i18n="sidebar.project.versionControl.push"></span></button>')
.appendTo(row)
.click(function(e) {
e.preventDefault();
@@ -893,8 +897,8 @@ RED.sidebar.versionControl = (function() {
},
400: {
'git_push_failed': function(err) {
// TODO: better message + NLS
RED.notify("NLS: Push failed as the remote has more recent commits. Pull first and write a better error message!","error");
// TODO: better message
RED.notify(RED._("sidebar.project.versionControl.pushFailed"),"error");
},
'unexpected_error': function(error) {
console.log(error);
@@ -945,18 +949,18 @@ RED.sidebar.versionControl = (function() {
},
400: {
'git_local_overwrite': function(err) {
RED.notify("<p>Unable to pull remote changes; your unstaged local changes would be overwritten.</p><p>Commit your changes and try again.</p>"+
'<p><a href="#" onclick="RED.sidebar.versionControl.showLocalChanges(); return false;">'+'Show unstaged changes'+'</a></p>',"error",false,10000000);
RED.notify(RED._("sidebar.project.versionControl.unablePull")+
'<p><a href="#" onclick="RED.sidebar.versionControl.showLocalChanges(); return false;">'+RED._("sidebar.project.versionControl.showUnstagedChanges")+'</a></p>',"error",false,10000000);
},
'git_pull_merge_conflict': function(err) {
refresh(true);
closeRemoteBox();
},
'git_connection_failed': function(err) {
RED.notify("Could not connect to remote repository: "+err.toString(),"warning")
RED.notify(RED._("sidebar.project.versionControl.connectionFailed")+err.toString(),"warning")
},
'git_pull_unrelated_history': function(error) {
var notification = RED.notify("<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>",{
var notification = RED.notify(RED._("sidebar.project.versionControl.pullUnrelatedHistory"),{
type: 'error',
modal: true,
fixed: true,
@@ -967,7 +971,7 @@ RED.sidebar.versionControl = (function() {
notification.close();
}
},{
text: 'Pull changes',
text: RED._("sidebar.project.versionControl.pullChanges"),
click: function() {
notification.close();
options.allowUnrelatedHistories = true;
@@ -986,7 +990,7 @@ RED.sidebar.versionControl = (function() {
spinner.remove();
});
}
$('<button id="sidebar-version-control-repo-pull" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-down"></i> <span>pull</span></button>')
$('<button id="sidebar-version-control-repo-pull" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-down"></i> <span data-i18n="sidebar.project.versionControl.pull"></span></button>')
.appendTo(row)
.click(function(e) {
e.preventDefault();
@@ -999,10 +1003,12 @@ RED.sidebar.versionControl = (function() {
RED.sidebar.addTab({
id: "version-control",
label: "history",
label: RED._("sidebar.project.versionControl.history"),
name: "Project History",
content: sidebarContent,
enableOnEdit: false,
pinned: true,
iconClass: "fa fa-code-fork",
onchange: function() {
setTimeout(function() {
sections.resize();
@@ -1019,17 +1025,17 @@ RED.sidebar.versionControl = (function() {
if (daysDelta > 30) {
return (new Date(date*1000)).toLocaleDateString();
} else if (daysDelta > 0) {
return daysDelta+" day"+(daysDelta>1?"s":"")+" ago";
return RED._("sidebar.project.versionControl.daysAgo", {count:daysDelta})
}
var hoursDelta = Math.floor(delta / (60*60));
if (hoursDelta > 0) {
return hoursDelta+" hour"+(hoursDelta>1?"s":"")+" ago";
return RED._("sidebar.project.versionControl.hoursAgo", {count:hoursDelta})
}
var minutesDelta = Math.floor(delta / 60);
if (minutesDelta > 0) {
return minutesDelta+" minute"+(minutesDelta>1?"s":"")+" ago";
return RED._("sidebar.project.versionControl.minsAgo", {count:minutesDelta})
}
return "Seconds ago";
return RED._("sidebar.project.versionControl.secondsAgo");
}
function updateBulk(files,unstaged) {
@@ -1064,9 +1070,6 @@ RED.sidebar.versionControl = (function() {
var refreshInProgress = false;
var emptyStagedItem = { label:"None" };
var emptyMergedItem = { label:"All conflicts resolved. Commit the changes to complete the merge." };
function getCommits(url,targetList,spinnerTarget,limit,before) {
var spinner = utils.addSpinnerOverlay(spinnerTarget);
var fullUrl = url+"?limit="+(limit||20);
@@ -1274,7 +1277,7 @@ RED.sidebar.versionControl = (function() {
refreshFiles(result);
$('#sidebar-version-control-local-branch').text(result.branches.local);
$('#sidebar-version-control-remote-branch').text(result.branches.remote||"none");
$('#sidebar-version-control-remote-branch').text(result.branches.remote||RED._("sidebar.project.versionControl.none"));
var commitsAhead = result.commits.ahead || 0;
var commitsBehind = result.commits.behind || 0;
@@ -1304,7 +1307,7 @@ RED.sidebar.versionControl = (function() {
$('#sidebar-version-control-commits-ahead').text("");
$('#sidebar-version-control-commits-behind').text("");
$('#sidebar-version-control-repo-toolbar-message').text("Your local branch is not currently tracking a remote branch.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.notTracking"));
$("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',true);
}
@@ -1330,23 +1333,26 @@ RED.sidebar.versionControl = (function() {
$('#sidebar-version-control-commits-ahead').text(commitsAhead);
$('#sidebar-version-control-commits-behind').text(commitsBehind);
if (isMerging) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository has unmerged changes. You need to fix the conflicts and commit the result.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.statusUnmergedChanged"));
$("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',true);
} else if (commitsAhead > 0 && commitsBehind === 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is "+commitsAhead+" commit"+(commitsAhead===1?'':'s')+" ahead of the remote. You can push "+(commitsAhead===1?'this commit':'these commits')+" now.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.commitsAhead", {count:commitsAhead}));
$("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',false);
} else if (commitsAhead === 0 && commitsBehind > 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is "+commitsBehind+" commit"+(commitsBehind===1?'':'s')+" behind of the remote. You can pull "+(commitsBehind===1?'this commit':'these commits')+" now.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.commitsBehind",{ count: commitsBehind }));
$("#sidebar-version-control-repo-pull").attr('disabled',false);
$("#sidebar-version-control-repo-push").attr('disabled',true);
} else if (commitsAhead > 0 && commitsBehind > 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is "+commitsBehind+" commit"+(commitsBehind===1?'':'s')+" behind and "+commitsAhead+" commit"+(commitsAhead===1?'':'s')+" ahead of the remote. You must pull the remote commit"+(commitsBehind===1?'':'s')+" down before pushing.");
$('#sidebar-version-control-repo-toolbar-message').text(
RED._("sidebar.project.versionControl.commitsAheadAndBehind1",{ count:commitsBehind })+
RED._("sidebar.project.versionControl.commitsAheadAndBehind2",{ count:commitsAhead })+
RED._("sidebar.project.versionControl.commitsAheadAndBehind3",{ count:commitsBehind }));
$("#sidebar-version-control-repo-pull").attr('disabled',false);
$("#sidebar-version-control-repo-push").attr('disabled',true);
} else if (commitsAhead === 0 && commitsBehind === 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is up to date.");
$('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.repositoryUpToDate"));
$("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',true);
}

View File

@@ -26,6 +26,24 @@ RED.search = (function() {
var keys = [];
var results = [];
function indexProperty(node,label,property) {
if (typeof property === 'string' || typeof property === 'number') {
property = (""+property).toLowerCase();
index[property] = index[property] || {};
index[property][node.id] = {node:node,label:label};
} else if (Array.isArray(property)) {
property.forEach(function(prop) {
indexProperty(node,label,prop);
})
} else if (typeof property === 'object') {
for (var prop in property) {
if (property.hasOwnProperty(prop)) {
indexProperty(node,label,property[prop])
}
}
}
}
function indexNode(n) {
var l = RED.utils.getNodeLabel(n);
if (l) {
@@ -42,17 +60,11 @@ RED.search = (function() {
}
for (var i=0;i<properties.length;i++) {
if (n.hasOwnProperty(properties[i])) {
var v = n[properties[i]];
if (typeof v === 'string' || typeof v === 'number') {
v = (""+v).toLowerCase();
index[v] = index[v] || {};
index[v][n.id] = {node:n,label:l};
}
indexProperty(n, l, n[properties[i]]);
}
}
}
function indexWorkspace() {
index = {};
RED.nodes.eachWorkspace(indexNode);
@@ -181,7 +193,7 @@ RED.search = (function() {
var div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
var colour = def.color;
var colour = RED.utils.getNodeColor(node.type,def);
var icon_url = RED.utils.getNodeIcon(def,node);
if (node.type === 'tab') {
colour = "#C0DEED";

View File

@@ -35,7 +35,8 @@ RED.sidebar = (function() {
tab.onremove.call(tab);
}
},
minimumActiveTabWidth: 70
// minimumActiveTabWidth: 70,
collapsible: true
// scrollable: true
});
@@ -59,6 +60,8 @@ RED.sidebar = (function() {
options = title;
}
delete options.closeable;
options.wrapper = $('<div>',{style:"height:100%"}).appendTo("#sidebar-content")
options.wrapper.append(options.content);
options.wrapper.hide();
@@ -82,6 +85,8 @@ RED.sidebar = (function() {
group: "sidebar-tabs"
});
options.iconClass = options.iconClass || "fa fa-square-o"
knownTabs[options.id] = options;
if (options.visible !== false) {
@@ -213,6 +218,7 @@ RED.sidebar = (function() {
showSidebar();
RED.sidebar.info.init();
RED.sidebar.config.init();
RED.sidebar.context.init();
// hide info bar at start if screen rather narrow...
if ($(window).width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); }
}

View File

@@ -23,5 +23,6 @@ RED.state = {
EXPORT: 6,
IMPORT: 7,
IMPORT_DRAGGING: 8,
QUICK_JOINING: 9
QUICK_JOINING: 9,
PANNING: 10
}

View File

@@ -221,8 +221,7 @@ RED.sidebar.config = (function() {
name: RED._("sidebar.config.name"),
content: content,
toolbar: toolbar,
closeable: true,
visible: false,
iconClass: "fa fa-cog",
onchange: function() { refreshConfigNodeList(); }
});
RED.actions.add("core:show-config-tab",function() {RED.sidebar.show('config')});

292
editor/js/ui/tab-context.js Normal file
View File

@@ -0,0 +1,292 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.sidebar.context = (function() {
var content;
var sections;
var localCache = {};
var nodeSection;
// var subflowSection;
var flowSection;
var globalSection;
var currentNode;
var currentFlow;
function init() {
content = $("<div>").css({"position":"relative","height":"100%"});
content.className = "sidebar-context"
// var toolbar = $('<div class="sidebar-header">'+
// '</div>').appendTo(content);
var footerToolbar = $('<div>'+
// '<span class="button-group"><a class="sidebar-footer-button" href="#" data-i18n="[title]node-red:debug.sidebar.openWindow"><i class="fa fa-desktop"></i></a></span> ' +
'</div>');
var stackContainer = $("<div>",{class:"sidebar-context-stack"}).appendTo(content);
sections = RED.stack.create({
container: stackContainer
});
nodeSection = sections.add({
title: RED._("sidebar.context.node"),
collapsible: true,
// onexpand: function() {
// updateNode(currentNode,true);
// }
});
nodeSection.expand();
nodeSection.content.css({height:"100%"});
nodeSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(nodeSection.content);
var table = $('<table class="node-info"></table>').appendTo(nodeSection.content);
nodeSection.table = $('<tbody>').appendTo(table);
var bg = $('<div style="float: right"></div>').appendTo(nodeSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateNode(currentNode, true);
})
// subflowSection = sections.add({
// title: "Subflow",
// collapsible: true
// });
// subflowSection.expand();
// subflowSection.content.css({height:"100%"});
// bg = $('<div style="float: right"></div>').appendTo(subflowSection.header);
// $('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
// .appendTo(bg)
// .click(function(evt) {
// evt.stopPropagation();
// evt.preventDefault();
// })
//
// subflowSection.container.hide();
flowSection = sections.add({
title: RED._("sidebar.context.flow"),
collapsible: true
});
flowSection.expand();
flowSection.content.css({height:"100%"});
flowSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(flowSection.content);
var table = $('<table class="node-info"></table>').appendTo(flowSection.content);
flowSection.table = $('<tbody>').appendTo(table);
bg = $('<div style="float: right"></div>').appendTo(flowSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateFlow(currentFlow);
})
globalSection = sections.add({
title: RED._("sidebar.context.global"),
collapsible: true
});
globalSection.expand();
globalSection.content.css({height:"100%"});
globalSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(globalSection.content);
var table = $('<table class="node-info"></table>').appendTo(globalSection.content);
globalSection.table = $('<tbody>').appendTo(table);
bg = $('<div style="float: right"></div>').appendTo(globalSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateEntry(globalSection,"context/global","global");
})
RED.actions.add("core:show-context-tab",show);
RED.sidebar.addTab({
id: "context",
label: RED._("sidebar.context.label"),
name: RED._("sidebar.context.name"),
iconClass: "fa fa-database",
content: content,
toolbar: footerToolbar,
// pinned: true,
enableOnEdit: false
});
// var toggleLiveButton = $("#sidebar-context-toggle-live");
// toggleLiveButton.click(function(evt) {
// evt.preventDefault();
// if ($(this).hasClass("selected")) {
// $(this).removeClass("selected");
// $(this).find("i").removeClass("fa-pause");
// $(this).find("i").addClass("fa-play");
// } else {
// $(this).addClass("selected");
// $(this).find("i").removeClass("fa-play");
// $(this).find("i").addClass("fa-pause");
// }
// });
// RED.popover.tooltip(toggleLiveButton, function() {
// if (toggleLiveButton.hasClass("selected")) {
// return "Pause live updates"
// } else {
// return "Start live updates"
// }
// });
RED.events.on("view:selection-changed", function(event) {
var selectedNode = event.nodes && event.nodes.length === 1 && event.nodes[0];
updateNode(selectedNode);
})
RED.events.on("workspace:change", function(event) {
updateFlow(RED.nodes.workspace(event.workspace));
})
updateEntry(globalSection,"context/global","global");
}
function updateNode(node,force) {
currentNode = node;
if (force) {
if (node) {
updateEntry(nodeSection,"context/node/"+node.id,node.id);
// if (/^subflow:/.test(node.type)) {
// subflowSection.container.show();
// updateEntry(subflowSection,"context/flow/"+node.id,node.id);
// } else {
// subflowSection.container.hide();
// }
} else {
// subflowSection.container.hide();
updateEntry(nodeSection)
}
} else {
$(nodeSection.table).empty();
if (node) {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.refresh"></td></tr>').appendTo(nodeSection.table).i18n();
} else {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.none"></td></tr>').appendTo(nodeSection.table).i18n();
}
nodeSection.timestamp.html("&nbsp;");
}
}
function updateFlow(flow) {
currentFlow = flow;
if (flow) {
updateEntry(flowSection,"context/flow/"+flow.id,flow.id);
} else {
updateEntry(flowSection)
}
}
function refreshEntry(section,baseUrl,id) {
var contextStores = RED.settings.context.stores;
var container = section.table;
$.getJSON(baseUrl, function(data) {
$(container).empty();
var sortedData = {};
for (var store in data) {
if (data.hasOwnProperty(store)) {
for (var key in data[store]) {
if (data[store].hasOwnProperty(key)) {
if (!sortedData.hasOwnProperty(key)) {
sortedData[key] = [];
}
data[store][key].store = store;
sortedData[key].push(data[store][key])
}
}
}
}
var keys = Object.keys(sortedData);
keys.sort();
var l = keys.length;
for (var i = 0; i < l; i++) {
sortedData[keys[i]].forEach(function(v) {
var k = keys[i];
var l2 = sortedData[k].length;
var propRow = $('<tr class="node-info-node-row"><td class="sidebar-context-property"></td><td></td></tr>').appendTo(container);
var obj = $(propRow.children()[0]);
obj.text(k);
var tools = $('<span class="debug-message-tools button-group"></span>').appendTo(obj);
var refreshItem = $('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).click(function(e) {
e.preventDefault();
e.stopPropagation();
$.getJSON(baseUrl+"/"+k+"?store="+v.store, function(data) {
$(propRow.children()[1]).empty();
var payload = data.msg;
var format = data.format;
payload = RED.utils.decodeObject(payload,format);
RED.utils.createObjectElement(payload, {
typeHint: data.format,
sourceId: id+"."+k
}).appendTo(propRow.children()[1]);
})
});
var payload = v.msg;
var format = v.format;
payload = RED.utils.decodeObject(payload,format);
RED.utils.createObjectElement(payload, {
typeHint: v.format,
sourceId: id+"."+k
}).appendTo(propRow.children()[1]);
if (contextStores.length > 1) {
$("<span>",{class:"sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
}
});
}
if (l === 0) {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.empty"></td></tr>').appendTo(container).i18n();
}
$(section.timestamp).text(new Date().toLocaleString());
});
}
function updateEntry(section,baseUrl,id) {
var container = section.table;
if (id) {
refreshEntry(section,baseUrl,id);
} else {
$(container).empty();
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td data-i18n="sidebar.context.none"></td></tr>').appendTo(container).i18n();
}
}
function show() {
RED.sidebar.show("context");
}
return {
init: init
}
})();

View File

@@ -83,7 +83,9 @@ RED.sidebar.info = (function() {
id: "info",
label: RED._("sidebar.info.label"),
name: RED._("sidebar.info.name"),
iconClass: "fa fa-info",
content: content,
pinned: true,
enableOnEdit: true
});
if (tips.enabled()) {
@@ -168,25 +170,41 @@ RED.sidebar.info = (function() {
if (node.type === "tab") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
} else if (node.type === "subflow") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("subflow.category")+'</td><td></td></tr>').appendTo(tableBody);
var category = node.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
}
} else {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
if (node.type !== "subflow" && node.name) {
if (node.type !== "subflow" && node.type !== "unknown" && node.name) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody);
$('<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]);
}
if (!m) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.type")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text(node.type);
$(propRow.children()[1]).text((node.type === "unknown")?node._orig.type:node.type);
if (node.type === "unknown") {
$('<span style="float: right; font-size: 0.8em"><i class="fa fa-warning"></i></span>').prependTo($(propRow.children()[1]))
}
}
if (!m && node.type != "subflow" && node.type != "comment") {
if (node._def) {
var defaults;
if (node.type === 'unknown') {
defaults = {};
Object.keys(node._orig).forEach(function(k) {
if (k !== 'type') {
defaults[k] = {};
}
})
} else if (node._def) {
defaults = node._def.defaults;
}
if (defaults) {
var count = 0;
var defaults = node._def.defaults;
for (var n in defaults) {
if (n != "name" && defaults.hasOwnProperty(n)) {
var val = node[n];
@@ -203,7 +221,7 @@ RED.sidebar.info = (function() {
var div = $('<span>',{class:""}).appendTo(container);
var nodeDiv = $('<div>',{class:"palette_node palette_node_small"}).appendTo(div);
var colour = configNode._def.color;
var colour = RED.utils.getNodeColor(configNode.type,configNode._def);
var icon_url = RED.utils.getNodeIcon(configNode._def);
nodeDiv.css({'backgroundColor':colour, "cursor":"pointer"});
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
@@ -233,6 +251,9 @@ RED.sidebar.info = (function() {
}
}
if (m) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("subflow.category")+'</td><td></td></tr>').appendTo(tableBody);
var category = subflowNode.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
$('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+subflowUserCount+'</td></tr>').appendTo(tableBody);
}
@@ -259,8 +280,7 @@ RED.sidebar.info = (function() {
if (infoText) {
setInfoText(infoText);
}
$(".sidebar-node-info-stack").scrollTop(0);
$(".node-info-property-header").click(function(e) {
e.preventDefault();
expandedSections["property"] = !expandedSections["property"];
@@ -374,10 +394,9 @@ RED.sidebar.info = (function() {
function set(html,title) {
// tips.stop();
// sections.show();
// nodeSection.container.hide();
infoSection.title.text(title||"");
refresh(null);
$(infoSection.content).empty();
nodeSection.container.hide();
infoSection.title.text(title||RED._("sidebar.info.info"));
setInfoText(html);
$(".sidebar-node-info-stack").scrollTop(0);
}

View File

@@ -128,7 +128,7 @@ RED.typeSearch = (function() {
var div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
var colour = def.color;
var colour = RED.utils.getNodeColor(object.type,def);
var icon_url = RED.utils.getNodeIcon(def);
nodeDiv.css('backgroundColor',colour);

View File

@@ -34,6 +34,10 @@ RED.utils = (function() {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('buffer['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('array['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'function') {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('function');
} else if (value.hasOwnProperty('type') && value.type === 'number') {
result = $('<span class="debug-message-object-value debug-message-type-number"></span>').text(value.data);
} else {
result = $('<span class="debug-message-object-value debug-message-type-meta">object</span>');
}
@@ -45,6 +49,8 @@ RED.utils = (function() {
subvalue = sanitize(value);
}
result = $('<span class="debug-message-object-value debug-message-type-string"></span>').html('"'+formatString(subvalue)+'"');
} else if (typeof value === 'number') {
result = $('<span class="debug-message-object-value debug-message-type-number"></span>').text(""+value);
} else {
result = $('<span class="debug-message-object-value debug-message-type-other"></span>').text(""+value);
}
@@ -125,7 +131,7 @@ RED.utils = (function() {
e.stopPropagation();
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
})
if (strippedKey !== '') {
if (strippedKey !== undefined && strippedKey !== '') {
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
var pinPath = $('<button class="editor-button editor-button-small debug-message-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).click(function(e) {
@@ -292,13 +298,18 @@ RED.utils = (function() {
var isArray = Array.isArray(obj);
var isArrayObject = false;
if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__encoded__ && obj.type === 'array') || obj.type === 'Buffer')) {
if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__enc__ && obj.type === 'array') || obj.type === 'Buffer')) {
isArray = true;
isArrayObject = true;
}
if (obj === null || obj === undefined) {
$('<span class="debug-message-type-null">'+obj+'</span>').appendTo(entryObj);
} else if (obj.__enc__ && obj.type === 'number') {
e = $('<span class="debug-message-type-number debug-message-object-header"></span>').text(obj.data).appendTo(entryObj);
} else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) {
e = $('<span class="debug-message-type-meta debug-message-object-header"></span>').text("function").appendTo(entryObj);
} else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
e = $('<span class="debug-message-type-meta debug-message-object-header"></span>').text("[internal]").appendTo(entryObj);
} else if (typeof obj === 'string') {
if (/[\t\n\r]/.test(obj)) {
element.addClass('collapsed');
@@ -343,7 +354,7 @@ RED.utils = (function() {
if (originalLength === undefined) {
originalLength = data.length;
}
if (data.__encoded__) {
if (data.__enc__) {
data = data.data;
}
type = obj.type.toLowerCase();
@@ -783,6 +794,45 @@ RED.utils = (function() {
return RED.text.bidi.enforceTextDirectionWithUCC(l);
}
var nodeColorCache = {};
function getNodeColor(type, def) {
var result = def.color;
var paletteTheme = RED.settings.theme('palette.theme') || [];
if (paletteTheme.length > 0) {
if (!nodeColorCache.hasOwnProperty(type)) {
nodeColorCache[type] = def.color;
var l = paletteTheme.length;
for (var i = 0; i < l; i++ ){
var themeRule = paletteTheme[i];
if (themeRule.hasOwnProperty('category')) {
if (!themeRule.hasOwnProperty('_category')) {
themeRule._category = new RegExp(themeRule.category);
}
if (!themeRule._category.test(def.category)) {
continue;
}
}
if (themeRule.hasOwnProperty('type')) {
if (!themeRule.hasOwnProperty('_type')) {
themeRule._type = new RegExp(themeRule.type);
}
if (!themeRule._type.test(type)) {
continue;
}
}
nodeColorCache[type] = themeRule.color || def.color;
break;
}
}
result = nodeColorCache[type];
}
if (result) {
return result;
} else {
return "#ddd";
}
}
function addSpinnerOverlay(container,contain) {
var spinner = $('<div class="projects-dialog-spinner "><img src="red/images/spin.svg"/></div>').appendTo(container);
if (contain) {
@@ -791,6 +841,47 @@ RED.utils = (function() {
return spinner;
}
function decodeObject(payload,format) {
if ((format === 'number') && (payload === "NaN")) {
payload = Number.NaN;
} else if ((format === 'number') && (payload === "Infinity")) {
payload = Infinity;
} else if ((format === 'number') && (payload === "-Infinity")) {
payload = -Infinity;
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);
payload = (payload.name?payload.name+": ":"")+payload.message;
} else if (format === 'null') {
payload = null;
} else if (format === 'undefined') {
payload = undefined;
} else if (/^buffer/.test(format)) {
var buffer = payload;
payload = [];
for (var c = 0; c < buffer.length; c += 2) {
payload.push(parseInt(buffer.substr(c, 2), 16));
}
}
return payload;
}
function parseContextKey(key) {
var parts = {};
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
if (m) {
parts.store = m[1];
parts.key = m[2];
} else {
parts.key = key;
if (RED.settings.context) {
parts.store = RED.settings.context.default;
}
}
return parts;
}
return {
createObjectElement: buildMessageElement,
getMessageProperty: getMessageProperty,
@@ -800,6 +891,9 @@ RED.utils = (function() {
getDefaultNodeIcon: getDefaultNodeIcon,
getNodeIcon: getNodeIcon,
getNodeLabel: getNodeLabel,
addSpinnerOverlay: addSpinnerOverlay
getNodeColor: getNodeColor,
addSpinnerOverlay: addSpinnerOverlay,
decodeObject: decodeObject,
parseContextKey: parseContextKey
}
})();

View File

@@ -0,0 +1,168 @@
/**
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.view.navigator = (function() {
var nav_scale = 25;
var nav_width = 5000/nav_scale;
var nav_height = 5000/nav_scale;
var navContainer;
var navBox;
var navBorder;
var navVis;
var scrollPos;
var scaleFactor;
var chartSize;
var dimensions;
var isDragging;
var isShowing = false;
function refreshNodes() {
if (!isShowing) {
return;
}
var navNode = navVis.selectAll(".navnode").data(RED.view.getActiveNodes(),function(d){return d.id});
navNode.exit().remove();
navNode.enter().insert("rect")
.attr('class','navnode')
.attr("pointer-events", "none");
navNode.each(function(d) {
d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale })
.attr("y",function(d) { return (d.y-d.h/2)/nav_scale })
.attr("width",function(d) { return Math.max(9,d.w/nav_scale) })
.attr("height",function(d) { return Math.max(3,d.h/nav_scale) })
.attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def);})
});
}
function onScroll() {
if (!isDragging) {
resizeNavBorder();
}
}
function resizeNavBorder() {
if (navBorder) {
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
navBorder.attr('x',scrollPos[0]/nav_scale)
.attr('y',scrollPos[1]/nav_scale)
.attr('width',chartSize[0]/nav_scale/scaleFactor)
.attr('height',chartSize[1]/nav_scale/scaleFactor)
}
}
function toggle() {
if (!isShowing) {
isShowing = true;
$("#btn-navigate").addClass("selected");
resizeNavBorder();
refreshNodes();
$("#chart").on("scroll",onScroll);
navContainer.fadeIn(200);
} else {
isShowing = false;
navContainer.fadeOut(100);
$("#chart").off("scroll",onScroll);
$("#btn-navigate").removeClass("selected");
}
}
return {
init: function() {
$(window).resize(resizeNavBorder);
RED.events.on("sidebar:resize",resizeNavBorder);
RED.actions.add("core:toggle-navigator",toggle);
var hideTimeout;
navContainer = $('<div>').css({
"position":"absolute",
"bottom":$("#workspace-footer").height(),
"right":0,
zIndex: 1
}).appendTo("#workspace").hide();
navBox = d3.select(navContainer[0])
.append("svg:svg")
.attr("width", nav_width)
.attr("height", nav_height)
.attr("pointer-events", "all")
.style({
position: "absolute",
bottom: 0,
right:0,
zIndex: 101,
"border-left": "1px solid #ccc",
"border-top": "1px solid #ccc",
background: "rgba(245,245,245,0.5)",
"box-shadow": "-1px 0 3px rgba(0,0,0,0.1)"
});
navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({
fill:"none",
stroke:"none",
pointerEvents:"all"
}).on("mousedown", function() {
// Update these in case they have changed
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor];
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
isDragging = true;
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mousemove", function() {
if (!isDragging) { return }
if (d3.event.buttons === 0) {
isDragging = false;
return;
}
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mouseup", function() {
isDragging = false;
})
navBorder = navBox.append("rect")
.attr("stroke-dasharray","5,5")
.attr("pointer-events", "none")
.style({
stroke: "#999",
strokeWidth: 1,
fill: "white",
});
navVis = navBox.append("svg:g")
$("#btn-navigate").click(function(evt) {
evt.preventDefault();
toggle();
})
},
refresh: refreshNodes,
resize: resizeNavBorder,
toggle: toggle
}
})();

View File

@@ -58,7 +58,10 @@ RED.view = (function() {
lastClickNode = null,
dblClickPrimed = null,
clickTime = 0,
clickElapsed = 0;
clickElapsed = 0,
scroll_position = [],
quickAddActive = false,
quickAddLink = null;
var clipboard = "";
@@ -73,6 +76,8 @@ RED.view = (function() {
var PORT_TYPE_INPUT = 1;
var PORT_TYPE_OUTPUT = 0;
var chart = $("#chart");
var outer = d3.select("#chart")
.append("svg:svg")
.attr("width", space_width)
@@ -94,6 +99,16 @@ RED.view = (function() {
.on("mousemove", canvasMouseMove)
.on("mousedown", canvasMouseDown)
.on("mouseup", canvasMouseUp)
.on("mouseenter", function() {
if (lasso) {
if (d3.event.buttons !== 1) {
lasso.remove();
lasso = null;
}
} else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
resetMouseVars();
}
})
.on("touchend", function() {
clearTimeout(touchStartTime);
touchStartTime = null;
@@ -283,7 +298,6 @@ RED.view = (function() {
function init() {
RED.events.on("workspace:change",function(event) {
var chart = $("#chart");
if (event.old !== 0) {
workspaceScrollPositions[event.old] = {
left:chart.scrollLeft(),
@@ -320,6 +334,8 @@ RED.view = (function() {
redraw();
});
RED.view.navigator.init();
$("#btn-zoom-out").click(function() {zoomOut();});
$("#btn-zoom-zero").click(function() {zoomZero();});
$("#btn-zoom-in").click(function() {zoomIn();});
@@ -448,6 +464,82 @@ RED.view = (function() {
RED.actions.add("core:step-selection-left", function() { moveSelection(-20,0);});
}
function generateLinkPath(origX,origY, destX, destY, sc) {
var dy = destY-origY;
var dx = destX-origX;
var delta = Math.sqrt(dy*dy+dx*dx);
var scale = lineCurveScale;
var scaleY = 0;
if (dx*sc > 0) {
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
// scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
// if (Math.abs(dy) < 3*node_height) {
// scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
// }
}
} else {
scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width));
}
if (dx*sc > 0) {
return "M "+origX+" "+origY+
" C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+
(destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+
destX+" "+destY
} else {
var midX = Math.floor(destX-dx/2);
var midY = Math.floor(destY-dy/2);
//
if (dy === 0) {
midY = destY + node_height;
}
var cp_height = node_height/2;
var y1 = (destY + midY)/2
var topX =origX + sc*node_width*scale;
var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
var bottomX = destX - sc*node_width*scale;
var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
var x1 = (origX+topX)/2;
var scy = dy>0?1:-1;
var cp = [
// Orig -> Top
[x1,origY],
[topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
// Top -> Mid
// [Mirror previous cp]
[x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
// Mid -> Bottom
// [Mirror previous cp]
[bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
// Bottom -> Dest
// [Mirror previous cp]
[(destX+bottomX)/2,destY]
];
if (cp[2][1] === topY+scy*cp_height) {
if (Math.abs(dy) < cp_height*10) {
cp[1][1] = topY-scy*cp_height/2;
cp[3][1] = bottomY-scy*cp_height/2;
}
cp[2][0] = topX;
}
return "M "+origX+" "+origY+
" C "+
cp[0][0]+" "+cp[0][1]+" "+
cp[1][0]+" "+cp[1][1]+" "+
topX+" "+topY+
" S "+
cp[2][0]+" "+cp[2][1]+" "+
midX+" "+midY+
" S "+
cp[3][0]+" "+cp[3][1]+" "+
bottomX+" "+bottomY+
" S "+
cp[4][0]+" "+cp[4][1]+" "+
destX+" "+destY
}
}
function addNode(type,x,y) {
var m = /^subflow:(.+)$/.exec(type);
@@ -526,6 +618,15 @@ RED.view = (function() {
function canvasMouseDown() {
var point;
if (d3.event.button === 1) {
// Middle Click pan
mouse_mode = RED.state.PANNING;
mouse_position = [d3.event.pageX,d3.event.pageY]
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
return;
}
if (!mousedown_node && !mousedown_link) {
selected_link = null;
updateSelection();
@@ -546,14 +647,16 @@ RED.view = (function() {
mouse_mode = RED.state.QUICK_JOINING;
$(window).on('keyup',disableQuickJoinEventHandler);
}
quickAddActive = true;
RED.typeSearch.show({
x:d3.event.clientX-mainPos.left-node_width/2,
y:d3.event.clientY-mainPos.top-node_height/2,
cancel: function() {
quickAddActive = false;
resetMouseVars();
},
add: function(type) {
quickAddActive = false;
var result = addNode(type);
if (!result) {
return;
@@ -562,11 +665,10 @@ RED.view = (function() {
var historyEvent = result.historyEvent;
nn.x = point[0];
nn.y = point[1];
if (mouse_mode === RED.state.QUICK_JOINING) {
if (drag_lines.length > 0) {
var drag_line = drag_lines[0];
if (mouse_mode === RED.state.QUICK_JOINING || quickAddLink) {
if (quickAddLink || drag_lines.length > 0) {
var drag_line = quickAddLink||drag_lines[0];
var src = null,dst,src_port;
if (drag_line.portType === PORT_TYPE_OUTPUT && nn.inputs > 0) {
src = drag_line.node;
src_port = drag_line.port;
@@ -581,9 +683,9 @@ RED.view = (function() {
RED.nodes.addLink(link);
historyEvent.links = [link];
hideDragLines();
if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) {
if (!quickAddLink && drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) {
showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]);
} else if (drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) {
} else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) {
showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]);
} else {
resetMouseVars();
@@ -601,9 +703,9 @@ RED.view = (function() {
resetMouseVars();
}
}
quickAddLink = null;
}
RED.history.push(historyEvent);
RED.nodes.add(nn);
RED.editor.validateNode(nn);
@@ -644,7 +746,6 @@ RED.view = (function() {
function canvasMouseMove() {
var i;
var node;
mouse_position = d3.touches(this)[0]||d3.mouse(this);
// Prevent touch scrolling...
//if (d3.touches(this)[0]) {
// d3.event.preventDefault();
@@ -655,6 +756,22 @@ RED.view = (function() {
//if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; }
//console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
if (mouse_mode === RED.state.PANNING) {
var pos = [d3.event.pageX,d3.event.pageY];
var deltaPos = [
mouse_position[0]-pos[0],
mouse_position[1]-pos[1]
];
chart.scrollLeft(scroll_position[0]+deltaPos[0])
chart.scrollTop(scroll_position[1]+deltaPos[1])
return
}
mouse_position = d3.touches(this)[0]||d3.mouse(this);
if (lasso) {
var ox = parseInt(lasso.attr("ox"));
var oy = parseInt(lasso.attr("oy"));
@@ -753,28 +870,7 @@ RED.view = (function() {
var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1;
var dy = mousePos[1]-(drag_line.node.y+portY);
var dx = mousePos[0]-(drag_line.node.x+sc*drag_line.node.w/2);
var delta = Math.sqrt(dy*dy+dx*dx);
var scale = lineCurveScale;
var scaleY = 0;
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
}
if (dx*sc < 0) {
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
if (Math.abs(dy) < 3*node_height) {
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
}
}
drag_line.el.attr("d",
"M "+(drag_line.node.x+sc*drag_line.node.w/2)+" "+(drag_line.node.y+portY)+
" C "+(drag_line.node.x+sc*(drag_line.node.w/2+node_width*scale))+" "+(drag_line.node.y+portY+scaleY*node_height)+" "+
(mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
mousePos[0]+" "+mousePos[1]
);
drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc));
}
d3.event.preventDefault();
} else if (mouse_mode == RED.state.MOVING) {
@@ -906,6 +1002,10 @@ RED.view = (function() {
function canvasMouseUp() {
var i;
var historyEvent;
if (mouse_mode === RED.state.PANNING) {
resetMouseVars();
return
}
if (mouse_mode === RED.state.QUICK_JOINING) {
return;
}
@@ -1020,17 +1120,20 @@ RED.view = (function() {
function zoomIn() {
if (scaleFactor < 2) {
scaleFactor += 0.1;
RED.view.navigator.resize();
redraw();
}
}
function zoomOut() {
if (scaleFactor > 0.3) {
scaleFactor -= 0.1;
RED.view.navigator.resize();
redraw();
}
}
function zoomZero() {
scaleFactor = 1;
RED.view.navigator.resize();
redraw();
}
@@ -1367,6 +1470,9 @@ RED.view = (function() {
function disableQuickJoinEventHandler(evt) {
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
if (quickAddActive && drag_lines.length > 0) {
quickAddLink = drag_lines[0];
}
resetMouseVars();
hideDragLines();
redraw();
@@ -1378,6 +1484,10 @@ RED.view = (function() {
//console.log(d,portType,portIndex);
// disable zoom
//vis.call(d3.behavior.zoom().on("zoom"), null);
if (d3.event.button === 1) {
return;
}
mousedown_node = d;
mousedown_port_type = portType;
mousedown_port_index = portIndex || 0;
@@ -1609,6 +1719,9 @@ RED.view = (function() {
function nodeMouseDown(d) {
focusView();
if (d3.event.button === 1) {
return;
}
//var touch0 = d3.event;
//var pos = [touch0.pageX,touch0.pageY];
//RED.touch.radialMenu.show(d3.select(this),pos);
@@ -1652,7 +1765,9 @@ RED.view = (function() {
clickElapsed = now-clickTime;
clickTime = now;
dblClickPrimed = (lastClickNode == mousedown_node);
dblClickPrimed = (lastClickNode == mousedown_node &&
d3.event.button === 0 &&
!d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey);
lastClickNode = mousedown_node;
var i;
@@ -1923,7 +2038,7 @@ RED.view = (function() {
.attr("ry",4)
.attr("width",16)
.attr("height",node_height-12)
.attr("fill",function(d) { return d._def.color;})
.attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/})
.attr("cursor","pointer")
.on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}})
.on("mouseup",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}})
@@ -1944,7 +2059,7 @@ RED.view = (function() {
.classed("node_unknown",function(d) { return d.type == "unknown"; })
.attr("rx", 5)
.attr("ry", 5)
.attr("fill",function(d) { return d._def.color;})
.attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/})
.on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown)
.on("touchstart",function(d) {
@@ -2341,32 +2456,17 @@ RED.view = (function() {
var numOutputs = d.source.outputs || 1;
var sourcePort = d.sourcePort || 0;
var y = -((numOutputs-1)/2)*13 +13*sourcePort;
var dy = d.target.y-(d.source.y+y);
var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2);
var delta = Math.sqrt(dy*dy+dx*dx);
var scale = lineCurveScale;
var scaleY = 0;
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
}
if (dx < 0) {
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
if (Math.abs(dy) < 3*node_height) {
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
}
}
d.x1 = d.source.x+d.source.w/2;
d.y1 = d.source.y+y;
d.x2 = d.target.x-d.target.w/2;
d.y2 = d.target.y;
return "M "+d.x1+" "+d.y1+
" C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
(d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
d.x2+" "+d.y2;
// return "M "+d.x1+" "+d.y1+
// " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
// (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
// d.x2+" "+d.y2;
return generateLinkPath(d.x1,d.y1,d.x2,d.y2,1);
});
}
})
@@ -2502,7 +2602,7 @@ RED.view = (function() {
}
).classed("link_selected", false);
}
RED.view.navigator.refresh();
if (d3.event) {
d3.event.preventDefault();
}
@@ -2776,7 +2876,9 @@ RED.view = (function() {
gridSize = Math.max(5,v);
updateGrid();
}
},
getActiveNodes: function() {
return activeNodes;
}
};
})();

View File

@@ -19,3 +19,7 @@
div.btn-group, a.btn {
@include disable-selection;
}
.dropdown-menu>li>a {
color: #444;
}

View File

@@ -64,22 +64,22 @@
display: inline-block;
}
}
.debug-message-row {
.debug-message-tools-pin {
display: none;
}
&.debug-message-row-pinned .debug-message-tools-pin {
display: inline-block;
}
&:hover {
background: #f3f3f3;
&>.debug-message-tools {
.debug-message-tools-copy {
display: inline-block;
}
.debug-message-tools-pin {
display: inline-block;
}
}
.debug-message-row {
.debug-message-tools-pin {
display: none;
}
&.debug-message-row-pinned .debug-message-tools-pin {
display: inline-block;
}
&:hover {
background: #f3f3f3;
&>.debug-message-tools {
.debug-message-tools-copy {
display: inline-block;
}
.debug-message-tools-pin {
display: inline-block;
}
}
}

View File

@@ -353,3 +353,64 @@
}
}
#node-settings-icon {
margin-left: 10px;
width: calc(100% - 163px);
}
.red-ui-icon-picker {
position: absolute;
border: 1px solid $primary-border-color;
box-shadow: 0 1px 6px -3px black;
background: white;
z-Index: 21;
display: none;
select {
box-sizing: border-box;
margin: 3px;
width: calc(100% - 6px);
}
}
.red-ui-icon-list {
width: 308px;
height: 200px;
overflow-y: scroll;
line-height: 0px;
}
.red-ui-icon-list-icon {
display: inline-block;
margin: 2px;
padding: 4px;
cursor: pointer;
border-radius: 4px;
&:hover {
background: lighten($node-selected-color,20%);
}
&.selected {
background: lighten($node-selected-color,20%);
.red-ui-search-result-node {
border-color: white;
}
}
}
.red-ui-icon-list-module {
background: $palette-header-background;
font-size: 0.9em;
padding: 3px;
color: #666;
clear: both;
i {
margin-right: 5px;
}
}
.red-ui-icon-meta {
border-top: 1px solid $secondary-border-color;
span {
padding: 4px;
color: #666;
font-size: 0.9em;
}
button {
float: right;
margin: 2px;
}
}

View File

@@ -248,7 +248,7 @@
.link_outline {
stroke: #fff;
stroke-width: 4;
stroke-width: 5;
cursor: crosshair;
fill: none;
pointer-events: none;

View File

@@ -134,15 +134,18 @@
color: $workspace-button-toggle-color !important;
background:$workspace-button-background-active;
margin-bottom: 1px;
&.selected:not(.disabled) {
&.selected:not(.disabled):not(:disabled) {
color: $workspace-button-toggle-color-selected !important;
background: $workspace-button-background;
border-bottom-width: 2px;
border-bottom-color: $form-input-border-selected-color;
margin-bottom: 0;
cursor: default;
&:not(.single) {
cursor: default;
}
}
&.disabled {
&.disabled,&:disabled {
color: $workspace-button-toggle-color-disabled !important;
}
}
@@ -203,7 +206,7 @@
height: 25px;
line-height: 23px;
padding: 0 10px;
user-select: none;
.button-group:not(:last-child) {
margin-right: 5px;
@@ -227,6 +230,7 @@
font-size: 11px;
line-height: 17px;
height: 18px;
width: 18px;
&.text-button {
width: auto;
padding: 0 5px;

View File

@@ -90,13 +90,14 @@
text-align: left;
padding: 9px;
font-weight: bold;
padding-left: 30px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
}
.palette-header > i {
margin: 3px 10px 3px 3px;
position: absolute;
left: 11px;
top: 12px;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;

View File

@@ -30,7 +30,6 @@
}
.red-ui-popover:after, .red-ui-popover:before {
top: 50%;
border: solid transparent;
content: " ";
height: 0;
@@ -39,12 +38,18 @@
pointer-events: none;
}
.red-ui-popover.red-ui-popover-right:after, .red-ui-popover.red-ui-popover-right:before {
top: 50%;
right: 100%;
}
.red-ui-popover.red-ui-popover-left:after, .red-ui-popover.red-ui-popover-left:before {
top: 50%;
left: 100%;
}
.red-ui-popover.red-ui-popover-bottom:after, .red-ui-popover.red-ui-popover-bottom:before {
bottom: 100%;
left: 50%;
}
.red-ui-popover.red-ui-popover-right:after {
border-color: rgba(136, 183, 213, 0);
@@ -72,6 +77,21 @@
margin-top: -11px;
}
.red-ui-popover.red-ui-popover-bottom:after {
border-color: rgba(136, 183, 213, 0);
border-bottom-color: #fff;
border-width: 10px;
margin-left: -10px;
}
.red-ui-popover.red-ui-popover-bottom:before {
border-color: rgba(194, 225, 245, 0);
border-bottom-color: $primary-border-color;
border-width: 11px;
margin-left: -11px;
}
.red-ui-popover-size-small {
font-size: 11px;
padding: 5px;
@@ -93,4 +113,12 @@
border-width: 6px;
margin-top: -6px;
}
&.red-ui-popover-bottom:after {
border-width: 5px;
margin-left: -5px;
}
&.red-ui-popover-bottom:before {
border-width: 6px;
margin-left: -6px;
}
}

View File

@@ -40,6 +40,7 @@
@import "panels";
@import "tabs";
@import "tab-config";
@import "tab-context";
@import "tab-info";
@import "popover";
@import "flow";

View File

@@ -0,0 +1,54 @@
/**
* 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.
**/
.sidebar-context-stack {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow-y: scroll;
.palette-category {
&:not(.palette-category-expanded) button {
display: none;
}
}
}
.sidebar-context-property {
position: relative;
.debug-message-tools {
right: 0px;
margin-right: 5px;
display: none;
}
&:hover .debug-message-tools {
display: inline-block;
}
}
.sidebar-context-updated {
text-align: right;
font-size: 11px;
color: #bbb;
padding: 1px 3px;
}
.sidebar-context-property-storename {
display: block;
font-size: 0.8em;
font-style: italic;
color: #aaa;
}

View File

@@ -93,7 +93,7 @@
color: $workspace-button-color-hover;
}
}
.red-ui-tab-icon {
img.red-ui-tab-icon {
opacity: 0.2;
}
}
@@ -113,6 +113,21 @@
&.red-ui-tabs-add.red-ui-tabs-scrollable {
padding-right: 59px;
}
&.red-ui-tabs-collapsible {
li:not(.active) {
display: none;
&.red-ui-tab-pinned {
a {
padding-left: 0;
text-align: center;
}
span {
display: none;
}
width: 32px;
}
}
}
&.red-ui-tabs-vertical {
box-sizing: border-box;
@@ -157,6 +172,15 @@
}
}
}
.red-ui-tabs-select {
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
opacity: 0.4;
background: red;
}
}
.red-ui-tab-button {
position: absolute;
@@ -180,7 +204,32 @@
z-index: 2;
}
}
.red-ui-tab-link-buttons {
position: absolute;
box-sizing: border-box;
top: 0;
right: 0;
height: 35px;
background: #fff;
border-bottom: 1px solid $primary-border-color;
z-index: 2;
a {
@include workspace-button-toggle;
line-height: 26px;
height: 28px;
width: 28px;
margin: 4px 3px 3px;
z-index: 2;
&.red-ui-tab-link-button {
&:not(.active) {
background: #eee;
}
}
&.red-ui-tab-link-button-menu {
border-color: white;
}
}
}
.red-ui-tab-scroll {
width: 21px;
top: 0;
@@ -216,7 +265,7 @@
right: 38px;
}
.red-ui-tab-icon {
img.red-ui-tab-icon {
margin-left: -8px;
margin-right: 3px;
margin-top: -2px;
@@ -225,6 +274,11 @@
height: 20px;
vertical-align: middle;
}
i.red-ui-tab-icon {
opacity: 0.7;
width: 18px;
height: 20px;
}
.red-ui-tabs-badges {
position: absolute;

View File

@@ -23,7 +23,7 @@
margin: 0;
vertical-align: middle;
box-sizing: border-box;
overflow:hidden;
overflow:visible;
position: relative;
.red-ui-typedInput-input {
position: absolute;
@@ -43,6 +43,7 @@
border-bottom-left-radius: 0;
box-shadow: none;
vertical-align: middle;
// backgroun/d: #f0fff0;
}
&.red-ui-typedInput-focus:not(.input-error) {
@@ -63,7 +64,7 @@
line-height: 32px;
vertical-align: middle;
color: #555;
i {
i.red-ui-typedInput-icon {
position: relative;
top: -3px;
margin-left: 1px;
@@ -76,11 +77,11 @@
}
&.disabled {
cursor: default;
i {
i.red-ui-typedInput-icon {
color: #bbb;
}
}
span {
.red-ui-typedInput-type-label,.red-ui-typedInput-option-label {
display: inline-block;
height: 100%;
padding: 0 1px 0 5px;
@@ -121,26 +122,25 @@
border-bottom-right-radius: 4px;
padding: 0 0 0 0;
position:absolute;
width: calc( 100% );
i {
position:absolute;
right: 4px;
top: 7px;
}
right: 0;
.red-ui-typedInput-option-label {
background:#fff;
background:$typedInput-button-background;
position:absolute;
left:0;
right:23px;
top: 0;
padding: 0 5px 0 5px;
padding: 0 5px 0 8px;
i.red-ui-typedInput-icon {
margin-right: 4px;
margin-top: 4px;
}
}
.red-ui-typedInput-option-caret {
top: 0;
position: absolute;
right: 0;
width: 17px;
padding-left: 6px;
}
&:focus {
box-shadow: none;
@@ -175,4 +175,7 @@
background: $typedInput-button-background-active;
}
}
.red-ui-typedInput-icon {
margin-right: 4px;
}
}

View File

@@ -61,7 +61,13 @@
}
#user-settings-tab-view {
position: absolute;
top:0;
right: 0;
left: 0;
bottom: 0;
padding: 8px 20px 20px;
overflow-y: scroll;
}
.user-settings-row {
padding: 5px 10px 2px;

View File

@@ -47,7 +47,9 @@
.workspace-footer-button {
@include component-footer-button;
}
.workspace-footer-button-toggle {
@include component-footer-button-toggle;
}
#workspace-footer {
@include component-footer;
}

View File

@@ -51,6 +51,7 @@
<a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a>
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
<a class="workspace-footer-button-toggle single" id="btn-navigate" href="#"><i class="fa fa-map-o"></i></a>
</div>
<div id="editor-shade" class="hide"></div>
</div>
@@ -131,6 +132,10 @@
<i class="fa fa-tag"></i>
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div>
<div class="form-row">
<i class="fa fa-folder-o"></i>
<label for="subflow-input-category" data-i18n="editor:subflow.category"></label><select style="width: 250px;" id="subflow-input-category"></select><input style="display:none; margin-left: 10px; width:calc(100% - 250px)" type="text" id="subflow-input-custom-category">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="subflow-input-info" data-i18n="editor:subflow.info"></label>
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>
@@ -140,76 +145,6 @@
</div>
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</script>
<script type="text/x-red" data-template-name="_expression">
<div id="node-input-expression-panels">
<div id="node-input-expression-panel-expr" class="red-ui-panel">
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
<span class="node-input-expression-legacy"><i class="fa fa-exclamation-circle"></i> <span data-i18n="expressionEditor.compatMode"></span></span>
<button id="node-input-expression-reformat" class="editor-button editor-button-small"><span data-i18n="expressionEditor.format"></span></button>
</div>
<div class="form-row node-text-editor-row">
<div class="node-text-editor" id="node-input-expression"></div>
</div>
</div>
<div id="node-input-expression-panel-info" class="red-ui-panel">
<div class="form-row">
<ul id="node-input-expression-tabs"></ul>
<div id="node-input-expression-tab-help" class="node-input-expression-tab-content hide">
<div>
<select id="node-input-expression-func"></select>
<button id="node-input-expression-func-insert" class="editor-button" data-i18n="expressionEditor.insert"></button>
</div>
<div id="node-input-expression-help"></div>
</div>
<div id="node-input-expression-tab-test" class="node-input-expression-tab-content hide">
<div>
<span style="display: inline-block; width: calc(50% - 5px);">
<span data-i18n="expressionEditor.data"></span>
<button style="float: right; margin-right: 5px;" id="node-input-example-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button>
</span>
<span style="display: inline-block; width: calc(50% - 5px);" data-i18n="expressionEditor.result"></span>
</div>
<div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-data"></div>
<div style="display: inline-block; width: calc(50% - 5px);" class="node-text-editor" id="node-input-expression-test-result"></div>
</div>
</div>
</div>
</div>
</script>
<script type="text/x-red" data-template-name="_json">
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
<button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>
</div>
</script>
<script type="text/x-red" data-template-name="_markdown">
<div class="form-row" id="node-input-markdown-title" style="margin-bottom: 3px; text-align: right;">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-markdown"></div>
</div>
</script>
<script type="text/x-red" data-template-name="_buffer">
<div id="node-input-buffer-panels">
<div id="node-input-buffer-panel-str" class="red-ui-panel">
<div class="form-row" style="margin-bottom: 3px; text-align: right;">
<span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span>
</div>
<div class="form-row node-text-editor-row">
<div class="node-text-editor" id="node-input-buffer-str"></div>
</div>
</div>
<div id="node-input-buffer-panel-bin" class="red-ui-panel">
<div class="form-row node-text-editor-row" style="margin-top: 10px">
<div class="node-text-editor" id="node-input-buffer-bin"></div>
</div>
</div>
</div>
</script>
<script src="vendor/vendor.js"></script>
<script src="vendor/jsonata/jsonata.min.js"></script>
<script src="vendor/ace/ace.js"></script>

View File

@@ -117,6 +117,7 @@
'$contains':{ args:[ 'str', 'pattern' ]},
'$count':{ args:[ 'array' ]},
'$each':{ args:[ 'object', 'function' ]},
'$env': { args:[ 'arg' ]},
'$exists':{ args:[ 'arg' ]},
'$filter':{ args:[ 'array', 'function' ]},
'$floor':{ args:[ 'number' ]},

View File

@@ -67,6 +67,7 @@
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
color: "#ddd", // set icon color
// set the icon (held in icons dir below where you save the node)
icon: "myicon.png", // saved in icons/myicon.png
label: function() { // sets the default label contents

View File

@@ -237,10 +237,9 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
} else {
return this._("inject.timestamp")+suffix;
}
} else if (this.payloadType === 'flow' && this.payload.length < 19) {
return 'flow.'+this.payload+suffix;
} else if (this.payloadType === 'global' && this.payload.length < 17) {
return 'global.'+this.payload+suffix;
} else if (this.payloadType === 'flow' || this.payloadType === 'global') {
var key = RED.utils.parseContextKey(this.payload);
return this.payloadType+"."+key.key+suffix;
} else {
return this._("inject.inject")+suffix;
}
@@ -263,7 +262,7 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
$("#node-input-payload").typedInput({
default: 'str',
typeField: $("#node-input-payloadType"),
types:['flow','global','str','num','bool','json','bin','date']
types:['flow','global','str','num','bool','json','bin','date','env']
});
$("#inject-time-type-select").change(function() {
@@ -502,7 +501,13 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
if (this.changed) {
return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
}
var label = (this.name||this.payload);
var payload = this.payload;
if ((this.payloadType === 'flow') ||
(this.payloadType === 'global')) {
var key = RED.utils.parseContextKey(payload);
payload = this.payloadType+"."+key.key;
}
var label = (this.name||payload);
if (label.length > 30) {
label = label.substring(0,50)+"...";
}

View File

@@ -63,21 +63,33 @@ module.exports = function(RED) {
}
this.on("input",function(msg) {
try {
msg.topic = this.topic;
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else if (this.payloadType === 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
msg.topic = this.topic;
if (this.payloadType !== 'flow' && this.payloadType !== 'global') {
try {
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else if (this.payloadType === 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
}
this.send(msg);
msg = null;
} catch(err) {
this.error(err,msg);
}
this.send(msg);
msg = null;
} catch(err) {
this.error(err,msg);
} else {
RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) {
if (err) {
node.error(err,msg);
} else {
msg.payload = res;
node.send(msg);
}
});
}
});
}

View File

@@ -154,7 +154,9 @@
name: this._("debug.sidebar.name"),
content: uiComponents.content,
toolbar: uiComponents.footer,
enableOnEdit: true
enableOnEdit: true,
pinned: true,
iconClass: "fa fa-bug"
});
RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); });

View File

@@ -4,7 +4,6 @@ module.exports = function(RED) {
var util = require("util");
var events = require("events");
var path = require("path");
var safeJSONStringify = require("json-stringify-safe");
var debuglength = RED.settings.debugMaxLength || 1000;
var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red";
@@ -20,7 +19,11 @@ module.exports = function(RED) {
if (this.tosidebar === undefined) { this.tosidebar = true; }
this.severity = n.severity || 40;
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
this.status({});
if (this.tostatus) {
this.oldStatus = {fill:"grey", shape:"ring"};
this.status(this.oldStatus);
}
else { this.status({}); }
var node = this;
var levels = {
@@ -104,111 +107,7 @@ module.exports = function(RED) {
function sendDebug(msg) {
// don't put blank errors in sidebar (but do add to logs)
//if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; }
if (msg.msg instanceof Error) {
msg.format = "error";
var errorMsg = {};
if (msg.msg.name) {
errorMsg.name = msg.msg.name;
}
if (msg.msg.hasOwnProperty('message')) {
errorMsg.message = msg.msg.message;
} else {
errorMsg.message = msg.msg.toString();
}
msg.msg = JSON.stringify(errorMsg);
} else if (msg.msg instanceof Buffer) {
msg.format = "buffer["+msg.msg.length+"]";
msg.msg = msg.msg.toString('hex');
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength);
}
} else if (msg.msg && typeof msg.msg === 'object') {
try {
msg.format = msg.msg.constructor.name || "Object";
// Handle special case of msg.req/res objects from HTTP In node
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
msg.format = "Object";
}
} catch(err) {
msg.format = "Object";
}
if (/error/i.test(msg.format)) {
msg.msg = JSON.stringify({
name: msg.msg.name,
message: msg.msg.message
});
} else {
var isArray = util.isArray(msg.msg);
if (isArray) {
msg.format = "array["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.slice(0,debuglength);
msg.msg = {
__encoded__: true,
type: "array",
data: msg.msg.slice(0,debuglength),
length: msg.msg.length
}
}
}
if (isArray || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
if (key === '_req' || key === '_res') {
value = "[internal]"
} else if (value instanceof Error) {
value = value.toString()
} else if (util.isArray(value) && value.length > debuglength) {
value = {
__encoded__: true,
type: "array",
data: value.slice(0,debuglength),
length: value.length
}
} else if (typeof value === 'string') {
if (value.length > debuglength) {
value = value.substring(0,debuglength)+"...";
}
} else if (value && value.constructor) {
if (value.type === "Buffer") {
value.__encoded__ = true;
value.length = value.data.length;
if (value.length > debuglength) {
value.data = value.data.slice(0,debuglength);
}
} else if (value.constructor.name === "ServerResponse") {
value = "[internal]"
} else if (value.constructor.name === "Socket") {
value = "[internal]"
}
}
return value;
}," ");
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]"; }
}
}
} else if (typeof msg.msg === "boolean") {
msg.format = "boolean";
msg.msg = msg.msg.toString();
} else if (typeof msg.msg === "number") {
msg.format = "number";
msg.msg = msg.msg.toString();
} else if (msg.msg === 0) {
msg.format = "number";
msg.msg = "0";
} else if (msg.msg === null || typeof msg.msg === "undefined") {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength)+"...";
}
}
// if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.substr(0,debuglength) +" ....";
// }
msg = RED.util.encodeObject(msg,{maxLength:debuglength});
RED.comms.publish("debug",msg);
}
@@ -227,12 +126,12 @@ module.exports = function(RED) {
if (state === "enable") {
node.active = true;
res.sendStatus(200);
if (node.tostatus) { node.status({}); }
if (node.tostatus) { node.status({fill:"grey", shape:"dot"}); }
} else if (state === "disable") {
node.active = false;
res.sendStatus(201);
if (node.tostatus && node.hasOwnProperty("oldStatus")) {
node.oldStatus.shape = "ring";
node.oldStatus.shape = "dot";
node.status(node.oldStatus);
}
} else {

View File

@@ -9,7 +9,8 @@
<input type="hidden" id="node-input-func" autofocus="autofocus">
<input type="hidden" id="node-input-noerr">
</div>
<div class="form-row node-text-editor-row">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="position: absolute; right:0; bottom:calc(100% + 3px);"><button id="node-function-expand-js" class="editor-button editor-button-small"><i class="fa fa-expand"></i></button></div>
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div>
<div class="form-row">
@@ -52,6 +53,12 @@
<p>The Catch node can also be used to handle errors. To invoke a Catch node,
pass <code>msg</code> as a second argument to <code>node.error</code>:</p>
<pre>node.error("Error",msg);</pre>
<h4>Referring Node Information</h4>
<p>In the function block, id and name of the node can be referenced using the following properties:</p>
<ul>
<li><code>node.id</code> - id of the node</li>
<li><code>node.name</code> - name of the node</li>
</ul>
</script>
<script type="text/javascript">
@@ -113,6 +120,23 @@
fields:['name','outputs']
});
this.editor.focus();
$("#node-function-expand-js").click(function(e) {
e.preventDefault();
var value = that.editor.getValue();
RED.editor.editJavaScript({
value: value,
width: "Infinity",
cursor: that.editor.getCursorPosition(),
complete: function(v,cursor) {
that.editor.setValue(v, -1);
that.editor.gotoLine(cursor.row+1,cursor.column,false);
setTimeout(function() {
that.editor.focus();
},300);
}
})
})
},
oneditsave: function() {
var annot = this.editor.getSession().getAnnotations();

View File

@@ -42,7 +42,7 @@ module.exports = function(RED) {
if (type === 'object') {
type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
}
node.error(RED._("function.error.non-message-returned",{ type: type }))
node.error(RED._("function.error.non-message-returned",{ type: type }));
}
}
}
@@ -62,6 +62,8 @@ module.exports = function(RED) {
"results = (function(msg){ "+
"var __msgid__ = msg._msgid;"+
"var node = {"+
"id:__node__.id,"+
"name:__node__.name,"+
"log:__node__.log,"+
"error:__node__.error,"+
"warn:__node__.warn,"+
@@ -80,10 +82,13 @@ module.exports = function(RED) {
console:console,
util:util,
Buffer:Buffer,
Date: Date,
RED: {
util: RED.util
},
__node__: {
id: node.id,
name: node.name,
log: function() {
node.log.apply(node, arguments);
},
@@ -198,9 +203,9 @@ module.exports = function(RED) {
if (util.hasOwnProperty('promisify')) {
sandbox.setTimeout[util.promisify.custom] = function(after, value) {
return new Promise(function(resolve, reject) {
sandbox.setTimeout(function(){ resolve(value) }, after);
sandbox.setTimeout(function(){ resolve(value); }, after);
});
}
};
}
var context = vm.createContext(sandbox);
try {
@@ -236,7 +241,6 @@ module.exports = function(RED) {
var line = 0;
var errorMessage;
var stack = err.stack.split(/\r?\n/);
if (stack.length > 0) {
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
@@ -260,13 +264,13 @@ module.exports = function(RED) {
});
this.on("close", function() {
while (node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop())
clearTimeout(node.outstandingTimers.pop());
}
while (node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop())
clearInterval(node.outstandingIntervals.pop());
}
this.status({});
})
});
} catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
@@ -275,4 +279,4 @@ module.exports = function(RED) {
}
RED.nodes.registerType("function",FunctionNode);
RED.library.register("functions");
}
};

View File

@@ -77,7 +77,9 @@
}</pre>
<p>The resulting property will be:
<pre>Hello Fred. Today is Monday</pre>
<p>It is possible to use a property from the flow context or global context. Just use <code>{{flow.name}}</code> or <code>{{global.name}}</code>.
<p>It is possible to use a property from the flow context or global context. Just use <code>{{flow.name}}</code> or
<code>{{global.name}}</code>, or for persistable store <code>store</code> use <code>{{flow[store].name}}</code> or
<code>{{global[store].name}}</code>.
<p><b>Note: </b>By default, <i>mustache</i> will escape any HTML entities in the values it substitutes.
To prevent this, use <code>{{{triple}}}</code> braces.
</script>

View File

@@ -19,15 +19,41 @@ module.exports = function(RED) {
var mustache = require("mustache");
var yaml = require("js-yaml");
function extractTokens(tokens,set) {
set = set || new Set();
tokens.forEach(function(token) {
if (token[0] !== 'text') {
set.add(token[1]);
if (token.length > 4) {
extractTokens(token[4],set);
}
}
});
return set;
}
function parseContext(key) {
var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
if (match) {
var parts = {};
parts.type = match[1];
parts.store = (match[3] === '') ? "default" : match[3];
parts.field = match[4];
return parts;
}
return undefined;
}
/**
* Custom Mustache Context capable to resolve message property and node
* Custom Mustache Context capable to collect message property and node
* flow and global context
*/
function NodeContext(msg, nodeContext, parent, escapeStrings) {
function NodeContext(msg, nodeContext, parent, escapeStrings, cachedContextTokens) {
this.msgContext = new mustache.Context(msg,parent);
this.nodeContext = nodeContext;
this.escapeStrings = escapeStrings;
this.cachedContextTokens = cachedContextTokens;
}
NodeContext.prototype = new mustache.Context();
@@ -48,20 +74,18 @@ module.exports = function(RED) {
return value;
}
// try node context:
var dot = name.indexOf(".");
/* istanbul ignore else */
if (dot > 0) {
var contextName = name.substr(0, dot);
var variableName = name.substr(dot + 1);
if (contextName === "flow" && this.nodeContext.flow) {
return this.nodeContext.flow.get(variableName);
}
else if (contextName === "global" && this.nodeContext.global) {
return this.nodeContext.global.get(variableName);
// try flow/global context:
var context = parseContext(name);
if (context) {
var type = context.type;
var store = context.store;
var field = context.field;
var target = this.nodeContext[type];
if (target) {
return this.cachedContextTokens[name];
}
}
return '';
}
catch(err) {
throw err;
@@ -69,7 +93,7 @@ module.exports = function(RED) {
}
NodeContext.prototype.push = function push (view) {
return new NodeContext(view, this.nodeContext,this.msgContext);
return new NodeContext(view, this.nodeContext, this.msgContext, undefined, this.cachedContextTokens);
};
function TemplateNode(n) {
@@ -82,9 +106,37 @@ module.exports = function(RED) {
this.outputFormat = n.output || "str";
var node = this;
function output(msg,value) {
/* istanbul ignore else */
if (node.outputFormat === "json") {
value = JSON.parse(value);
}
/* istanbul ignore else */
if (node.outputFormat === "yaml") {
value = yaml.load(value);
}
if (node.fieldType === 'msg') {
RED.util.setMessageProperty(msg, node.field, value);
node.send(msg);
} else if ((node.fieldType === 'flow') ||
(node.fieldType === 'global')) {
var context = RED.util.parseContextStore(node.field);
var target = node.context()[node.fieldType];
target.set(context.key, value, context.store, function (err) {
if (err) {
node.error(err, msg);
} else {
node.send(msg);
}
});
}
}
node.on("input", function(msg) {
try {
var value;
/***
* Allow template contents to be defined externally
* through inbound msg.template IFF node.template empty
@@ -97,34 +149,46 @@ module.exports = function(RED) {
}
if (node.syntax === "mustache") {
if (node.outputFormat === "json") {
value = mustache.render(template,new NodeContext(msg, node.context(), null, true));
} else {
value = mustache.render(template,new NodeContext(msg, node.context(), null, false));
}
} else {
value = template;
}
/* istanbul ignore else */
if (node.outputFormat === "json") {
value = JSON.parse(value);
}
/* istanbul ignore else */
if (node.outputFormat === "yaml") {
value = yaml.load(value);
}
var is_json = (node.outputFormat === "json");
var promises = [];
var tokens = extractTokens(mustache.parse(template));
var resolvedTokens = {};
tokens.forEach(function(name) {
var context = parseContext(name);
if (context) {
var type = context.type;
var store = context.store;
var field = context.field;
var target = node.context()[type];
if (target) {
var promise = new Promise((resolve, reject) => {
target.get(field, store, (err, val) => {
if (err) {
reject(err);
} else {
resolvedTokens[name] = val;
resolve();
}
});
});
promises.push(promise);
return;
}
}
});
if (node.fieldType === 'msg') {
RED.util.setMessageProperty(msg,node.field,value);
} else if (node.fieldType === 'flow') {
node.context().flow.set(node.field,value);
} else if (node.fieldType === 'global') {
node.context().global.set(node.field,value);
Promise.all(promises).then(function() {
var value = mustache.render(template, new NodeContext(msg, node.context(), null, is_json, resolvedTokens));
output(msg, value);
}).catch(function (err) {
node.error(err.message,msg);
});
} else {
output(msg, template);
}
node.send(msg);
}
catch(err) {
node.error(err.message);
node.error(err.message, msg);
}
});
}

View File

@@ -162,7 +162,7 @@
$("#node-input-op1").typedInput({
default: 'str',
typeField: $("#node-input-op1type"),
types:['flow','global','str','num','bool','json',
types:['flow','global','str','num','bool','json','bin','date','env',
optionPayload,
optionNothing
]
@@ -170,7 +170,7 @@
$("#node-input-op2").typedInput({
default: 'str',
typeField: $("#node-input-op2type"),
types:['flow','global','str','num','bool','json',
types:['flow','global','str','num','bool','json','bin','date','env',
optionOriginalPayload,
optionLatestPayload,
optionNothing

View File

@@ -76,8 +76,43 @@ module.exports = function(RED) {
var node = this;
node.topics = {};
this.on("input", function(msg) {
var pendingMessages = [];
var activeMessagePromise = null;
var processMessageQueue = function(msg) {
if (msg) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
if (activeMessagePromise !== null) {
// The node is currently processing a message, so do nothing
// more with this message
return;
}
}
if (pendingMessages.length === 0) {
// There are no more messages to process, clear the active flag
// and return
activeMessagePromise = null;
return;
}
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
activeMessagePromise = processMessage(nextMsg)
.then(processMessageQueue)
.catch((err) => {
node.error(err,nextMsg);
return processMessageQueue();
});
}
this.on('input', function(msg) {
processMessageQueue(msg);
});
var processMessage = function(msg) {
var topic = msg.topic || "_none";
var promise;
if (node.bytopic === "all") { topic = "_none"; }
node.topics[topic] = node.topics[topic] || {};
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
@@ -88,48 +123,88 @@ module.exports = function(RED) {
}
else {
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
promise = Promise.resolve();
if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
else if (node.op2type !== "nul") {
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
}
if (node.op1type === "pay") { }
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
else if (node.op1type !== "nul") {
msg.payload = RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg);
}
if (node.duration === 0) { node.topics[topic].tout = 0; }
else if (node.loop === true) {
/* istanbul ignore else */
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
/* istanbul ignore else */
if (node.op1type !== "nul") {
var msg2 = RED.util.cloneMessage(msg);
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
}
}
else {
if (!node.topics[topic].tout) {
node.topics[topic].tout = setTimeout(function() {
var msg2 = null;
if (node.op2type !== "nul") {
msg2 = RED.util.cloneMessage(msg);
if (node.op2type === "flow" || node.op2type === "global") {
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
}
msg2.payload = node.topics[topic].m2;
delete node.topics[topic];
node.send(msg2);
promise = new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
if (err) {
reject(err);
} else {
node.topics[topic].m2 = value;
resolve();
}
else { delete node.topics[topic]; }
node.status({});
}, node.duration);
}
});
});
}
node.status({fill:"blue",shape:"dot",text:" "});
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
return promise.then(() => {
promise = Promise.resolve();
if (node.op1type === "pay") { }
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
else if (node.op1type !== "nul") {
promise = new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg,(err,value) => {
if (err) {
reject(err);
} else {
msg.payload = value;
resolve();
}
});
});
}
return promise.then(() => {
if (node.duration === 0) { node.topics[topic].tout = 0; }
else if (node.loop === true) {
/* istanbul ignore else */
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
/* istanbul ignore else */
if (node.op1type !== "nul") {
var msg2 = RED.util.cloneMessage(msg);
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
}
}
else {
if (!node.topics[topic].tout) {
node.topics[topic].tout = setTimeout(function() {
var msg2 = null;
if (node.op2type !== "nul") {
var promise = Promise.resolve();
msg2 = RED.util.cloneMessage(msg);
if (node.op2type === "flow" || node.op2type === "global") {
promise = new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
if (err) {
reject(err);
} else {
node.topics[topic].m2 = value;
resolve();
}
});
});
}
promise.then(() => {
msg2.payload = node.topics[topic].m2;
delete node.topics[topic];
node.send(msg2);
node.status({});
}).catch(err => {
node.error(err);
});
} else {
delete node.topics[topic];
node.status({});
}
}, node.duration);
}
}
node.status({fill:"blue",shape:"dot",text:" "});
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
});
});
}
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
/* istanbul ignore else */
@@ -138,25 +213,43 @@ module.exports = function(RED) {
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
node.topics[topic].tout = setTimeout(function() {
var msg2 = null;
var promise = Promise.resolve();
if (node.op2type !== "nul") {
if (node.op2type === "flow" || node.op2type === "global") {
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
}
if (node.topics[topic] !== undefined) {
msg2 = RED.util.cloneMessage(msg);
msg2.payload = node.topics[topic].m2;
promise = new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
if (err) {
reject(err);
} else {
node.topics[topic].m2 = value;
resolve();
}
});
});
}
}
delete node.topics[topic];
node.status({});
node.send(msg2);
promise.then(() => {
if (node.op2type !== "nul") {
if (node.topics[topic] !== undefined) {
msg2 = RED.util.cloneMessage(msg);
msg2.payload = node.topics[topic].m2;
}
}
delete node.topics[topic];
node.status({});
node.send(msg2);
}).catch(err => {
node.error(err);
});
}, node.duration);
}
else {
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
}
}
});
return Promise.resolve();
}
this.on("close", function() {
for (var t in node.topics) {
/* istanbul ignore else */

View File

@@ -455,24 +455,8 @@ RED.debug = (function() {
$('<span class="debug-message-name">'+name+'</span>').appendTo(metaRow);
}
if ((format === 'number') && (payload === "NaN")) {
payload = Number.NaN;
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);
payload = (payload.name?payload.name+": ":"")+payload.message;
} else if (format === 'null') {
payload = null;
} else if (format === 'undefined') {
payload = undefined;
} else if (/^buffer/.test(format)) {
var buffer = payload;
payload = [];
for (var c = 0; c < buffer.length; c += 2) {
payload.push(parseInt(buffer.substr(c, 2), 16));
}
}
payload = RED.utils.decodeObject(payload,format);
var el = $('<span class="debug-message-payload"></span>').appendTo(msg);
var path = o.property||'';
var debugMessage = RED.utils.createObjectElement(payload, {

View File

@@ -6,35 +6,36 @@ module.exports = function(RED) {
var fs = require('fs');
var gpioCommand = __dirname+'/nrgpio';
var allOK = true;
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
} catch(err) {
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
}
try {
fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
// /usr/lib/python2.7/dist-packages/RPi/GPIO
} catch(err) {
if (cpuinfo.indexOf(": BCM") === -1) {
allOK = false;
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
}
try {
fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
}
catch(err) {
fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
// /usr/lib/python2.7/dist-packages/RPi/GPIO
} catch(err) {
try {
fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
}
catch(err) {
RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
} catch(err) {
try {
fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
} catch(err) {
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound"));
allOK = false;
}
}
}
}
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
allOK = false;
}
} catch(err) {
allOK = false;
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
}
// the magic to make python print stuff immediately
@@ -61,48 +62,62 @@ module.exports = function(RED) {
}
}
if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true;
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
if (allOK === true) {
if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true;
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
var d = data.toString().trim().split("\n");
for (var i = 0; i < d.length; i++) {
if (d[i] === '') { return; }
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
node.child.stdout.on('data', function (data) {
var d = data.toString().trim().split("\n");
for (var i = 0; i < d.length; i++) {
if (d[i] === '') { return; }
if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
}
node.buttonState = d[i];
node.status({fill:"green",shape:"dot",text:d[i]});
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
}
node.buttonState = d[i];
node.status({fill:"green",shape:"dot",text:d[i]});
if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
}
});
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
});
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
}
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
if (node.read === true) {
var val;
if (node.intype == "up") { val = 1; }
if (node.intype == "down") { val = 0; }
setTimeout(function(){
node.send({ topic:"pi/"+node.pin, payload:val });
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})});
},250);
}
}
node.on("close", function(done) {
@@ -155,20 +170,83 @@ module.exports = function(RED) {
else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
}
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
node.status({fill:"green",shape:"dot",text:node.level});
} else {
node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
}
node.running = true;
if (allOK === true) {
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
node.status({fill:"green",shape:"dot",text:node.level});
} else {
node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
}
node.running = true;
node.on("input", inputlistener);
node.on("input", inputlistener);
node.child.stdout.on('data', function (data) {
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
}
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
node.on("input", function(msg){
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})});
});
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
function PiMouseNode(n) {
RED.nodes.createNode(this,n);
this.butt = n.butt || 7;
var node = this;
if (allOK === true) {
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
data = Number(data);
if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
});
node.child.stderr.on('data', function (data) {
@@ -192,69 +270,19 @@ module.exports = function(RED) {
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write("close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
function PiMouseNode(n) {
RED.nodes.createNode(this,n);
this.butt = n.butt || 7;
var node = this;
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
data = Number(data);
if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
RED.nodes.registerType("rpi-mouse",PiMouseNode);
@@ -262,39 +290,40 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
var node = this;
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
if (allOK === true) {
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
var b = data.toString().trim().split(",");
var act = "up";
if (b[1] === "1") { act = "down"; }
if (b[1] === "2") { act = "repeat"; }
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
});
node.child.stdout.on('data', function (data) {
var b = data.toString().trim().split(",");
var act = "up";
if (b[1] === "1") { act = "down"; }
if (b[1] === "2") { act = "repeat"; }
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('close', function (code) {
node.running = false;
node.child = null;
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.on("close", function(done) {
node.status({});
if (node.child != null) {
node.done = done;
@@ -303,24 +332,30 @@ module.exports = function(RED) {
}
else { done(); }
});
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
}
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
var pitype = { type:"" };
exec(gpioCommand+" info", function(err,stdout,stderr) {
if (err) {
RED.log.info(RED._("rpi-gpio.errors.version"));
}
else {
try {
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
pitype.type = info["TYPE"];
if (allOK === true) {
exec(gpioCommand+" info", function(err,stdout,stderr) {
if (err) {
RED.log.info(RED._("rpi-gpio.errors.version"));
}
catch(e) {
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
else {
try {
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
pitype.type = info["TYPE"];
}
catch(e) {
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
}
}
}
});
});
}
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype);

View File

@@ -21,11 +21,12 @@ import os
import subprocess
from time import sleep
bounce = 25;
try:
raw_input # Python 2
except NameError:
raw_input = input # Python 3
if sys.version_info >= (3,0):
print("Sorry - currently only configured to work with python 2.x")
sys.exit(1)
bounce = 25
if len(sys.argv) > 2:
cmd = sys.argv[1].lower()
@@ -34,7 +35,7 @@ if len(sys.argv) > 2:
GPIO.setwarnings(False)
if cmd == "pwm":
#print "Initialised pin "+str(pin)+" to PWM"
#print("Initialised pin "+str(pin)+" to PWM")
try:
freq = int(sys.argv[3])
except:
@@ -54,10 +55,10 @@ if len(sys.argv) > 2:
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
print("bad data: "+data)
elif cmd == "buzz":
#print "Initialised pin "+str(pin)+" to Buzz"
#print("Initialised pin "+str(pin)+" to Buzz")
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.stop()
@@ -76,10 +77,10 @@ if len(sys.argv) > 2:
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
print("bad data: "+data)
elif cmd == "out":
#print "Initialised pin "+str(pin)+" to OUT"
#print("Initialised pin "+str(pin)+" to OUT")
GPIO.setup(pin,GPIO.OUT)
if len(sys.argv) == 4:
GPIO.output(pin,int(sys.argv[3]))
@@ -103,11 +104,11 @@ if len(sys.argv) > 2:
GPIO.output(pin,data)
elif cmd == "in":
#print "Initialised pin "+str(pin)+" to IN"
#print("Initialised pin "+str(pin)+" to IN")
bounce = float(sys.argv[4])
def handle_callback(chan):
sleep(bounce/1000.0)
print GPIO.input(chan)
print(GPIO.input(chan))
if sys.argv[3].lower() == "up":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
@@ -116,7 +117,7 @@ if len(sys.argv) > 2:
else:
GPIO.setup(pin,GPIO.IN)
print GPIO.input(pin)
print(GPIO.input(pin))
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce))
while True:
@@ -129,7 +130,7 @@ if len(sys.argv) > 2:
sys.exit(0)
elif cmd == "byte":
#print "Initialised BYTE mode - "+str(pin)+
#print("Initialised BYTE mode - "+str(pin)+)
list = [7,11,13,12,15,16,18,22]
GPIO.setup(list,GPIO.OUT)
@@ -152,7 +153,7 @@ if len(sys.argv) > 2:
GPIO.output(list[bit], data & mask)
elif cmd == "borg":
#print "Initialised BORG mode - "+str(pin)+
#print("Initialised BORG mode - "+str(pin)+)
GPIO.setup(11,GPIO.OUT)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(15,GPIO.OUT)
@@ -190,7 +191,7 @@ if len(sys.argv) > 2:
button = ord( buf[0] ) & pin # mask out just the required button(s)
if button != oldbutt: # only send if changed
oldbutt = button
print button
print(button)
while True:
try:
@@ -202,7 +203,7 @@ if len(sys.argv) > 2:
elif cmd == "kbd": # catch keyboard button events
try:
while not os.path.isdir("/dev/input/by-path"):
time.sleep(10)
sleep(10)
infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip()
infile_path = "/dev/input/by-path/" + infile
EVENT_SIZE = struct.calcsize('llHHI')
@@ -215,7 +216,7 @@ if len(sys.argv) > 2:
# type,code,value
print("%u,%u" % (code, value))
event = file.read(EVENT_SIZE)
print "0,0"
print("0,0")
file.close()
sys.exit(0)
except:
@@ -225,14 +226,14 @@ if len(sys.argv) > 2:
elif len(sys.argv) > 1:
cmd = sys.argv[1].lower()
if cmd == "rev":
print GPIO.RPI_REVISION
print(GPIO.RPI_REVISION)
elif cmd == "ver":
print GPIO.VERSION
print(GPIO.VERSION)
elif cmd == "info":
print GPIO.RPI_INFO
print(GPIO.RPI_INFO)
else:
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
print " only ver (gpio version) and info (board information) accept no pin parameter."
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")
print(" only ver (gpio version) and info (board information) accept no pin parameter.")
else:
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")

View File

@@ -63,6 +63,11 @@
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-verifyservercert" style="width: calc(100% - 170px);" data-i18n="tls.label.verify-server-cert"></label>
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-servername"><i class="fa fa-server"></i> <span data-i18n="tls.label.servername"></span></label>
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-servername" data-i18n="[placeholder]tls.placeholder.servername">
</div>
<hr>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
@@ -96,6 +101,7 @@
certname: {value:""},
keyname: {value:""},
caname: {value:""},
servername: {value:""},
verifyservercert: {value: true}
},
credentials: {

View File

@@ -25,6 +25,7 @@ module.exports = function(RED) {
var certPath = n.cert.trim();
var keyPath = n.key.trim();
var caPath = n.ca.trim();
this.servername = (n.servername||"").trim();
if ((certPath.length > 0) || (keyPath.length > 0)) {
@@ -102,6 +103,9 @@ module.exports = function(RED) {
if (this.credentials && this.credentials.passphrase) {
opts.passphrase = this.credentials.passphrase;
}
if (this.servername) {
opts.servername = this.servername;
}
opts.rejectUnauthorized = this.verifyservercert;
}
return opts;

View File

@@ -41,7 +41,7 @@
<dt>payload <span class="property-type">string | buffer</span></dt>
<dd>a string unless detected as a binary buffer.</dd>
<dt>topic <span class="property-type">string</span></dt>
<dd>the MQTT topic, uses / as a heirarchy separator.</dd>
<dd>the MQTT topic, uses / as a hierarchy separator.</dd>
<dt>qos <span class="property-type">number</span> </dt>
<dd>0, fire and forget - 1, at least once - 2, once and once only.</dd>
<dt>retain <span class="property-type">boolean</span></dt>

View File

@@ -19,11 +19,26 @@ module.exports = function(RED) {
var mqtt = require("mqtt");
var util = require("util");
var isUtf8 = require('is-utf8');
var HttpsProxyAgent = require('https-proxy-agent');
var url = require('url');
function matchTopic(ts,t) {
if (ts == "#") {
return true;
}
/* The following allows shared subscriptions (as in MQTT v5)
http://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html#_Toc514345522
4.8.2 describes shares like:
$share/{ShareName}/{filter}
$share is a literal string that marks the Topic Filter as being a Shared Subscription Topic Filter.
{ShareName} is a character string that does not include "/", "+" or "#"
{filter} The remainder of the string has the same syntax and semantics as a Topic Filter in a non-shared subscription. Refer to section 4.7.
*/
else if(ts.startsWith("$share")){
ts = ts.replace(/^\$share\/[^#+/]+\/(.*)/g,"$1");
}
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
return re.test(t);
}
@@ -96,12 +111,29 @@ module.exports = function(RED) {
if (typeof this.cleansession === 'undefined') {
this.cleansession = true;
}
var prox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; }
// Create the URL to pass in to the MQTT.js library
if (this.brokerurl === "") {
// if the broker may be ws:// or wss:// or even tcp://
if (this.broker.indexOf("://") > -1) {
this.brokerurl = this.broker;
// Only for ws or wss, check if proxy env var for additional configuration
if (this.brokerurl.indexOf("wss://") > -1 || this.brokerurl.indexOf("ws://") > -1 )
// check if proxy is set in env
if (prox) {
var parsedUrl = url.parse(this.brokerurl);
var proxyOpts = url.parse(prox);
// true for wss
proxyOpts.secureEndpoint = parsedUrl.protocol ? parsedUrl.protocol === 'wss:' : true;
// Set Agent for wsOption in MQTT
var agent = new HttpsProxyAgent(proxyOpts);
this.options.wsOptions = {
agent: agent
}
}
} else {
// construct the std mqtt:// url
if (this.usetls) {
@@ -435,4 +467,4 @@ module.exports = function(RED) {
}
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
};
};

View File

@@ -21,8 +21,6 @@ module.exports = function(RED) {
var cookieParser = require("cookie-parser");
var getBody = require('raw-body');
var cors = require('cors');
var jsonParser = bodyParser.json();
var urlencParser = bodyParser.urlencoded({extended:true});
var onHeaders = require('on-headers');
var typer = require('media-typer');
var isUtf8 = require('is-utf8');
@@ -212,6 +210,10 @@ module.exports = function(RED) {
}
}
var maxApiRequestSize = RED.settings.apiMaxLength || '5mb';
var jsonParser = bodyParser.json({limit:maxApiRequestSize});
var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true});
var metricsHandler = function(req,res,next) { next(); }
if (this.metric()) {
metricsHandler = function(req, res, next) {

View File

@@ -16,9 +16,7 @@
module.exports = function(RED) {
"use strict";
var http = require("follow-redirects").http;
var https = require("follow-redirects").https;
var urllib = require("url");
var request = require("request");
var mustache = require("mustache");
var querystring = require("querystring");
var cookie = require("cookie");
@@ -78,9 +76,13 @@ module.exports = function(RED) {
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
}
var opts = urllib.parse(url);
var opts = {};
opts.url = url;
opts.timeout = node.reqTimeout;
opts.method = method;
opts.headers = {};
opts.encoding = null; // Force NodeJs to return a Buffer (instead of a string)
opts.maxRedirects = 21;
var ctSet = "Content-Type"; // set default camel case
var clSet = "Content-Length";
if (msg.headers) {
@@ -109,7 +111,7 @@ module.exports = function(RED) {
}
}
if (msg.hasOwnProperty('followRedirects')) {
opts.followRedirects = msg.followRedirects;
opts.followRedirect = msg.followRedirects;
}
if (msg.cookies) {
var cookies = [];
@@ -134,7 +136,10 @@ module.exports = function(RED) {
}
}
if (this.credentials && this.credentials.user) {
opts.auth = this.credentials.user+":"+(this.credentials.password||"");
opts.auth = {
user: this.credentials.user,
pass: this.credentials.password||""
};
}
var payload = null;
@@ -160,6 +165,7 @@ module.exports = function(RED) {
opts.headers[clSet] = Buffer.byteLength(payload);
}
}
opts.body = payload;
}
// revert to user supplied Capitalisation if needed.
if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) {
@@ -170,7 +176,6 @@ module.exports = function(RED) {
opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length'];
}
var urltotest = url;
var noproxy;
if (noprox) {
for (var i in noprox) {
@@ -180,22 +185,11 @@ module.exports = function(RED) {
if (prox && !noproxy) {
var match = prox.match(/^(http:\/\/)?(.+)?:([0-9]+)?/i);
if (match) {
//opts.protocol = "http:";
//opts.host = opts.hostname = match[2];
//opts.port = (match[3] != null ? match[3] : 80);
opts.headers['Host'] = opts.host;
var heads = opts.headers;
var path = opts.pathname = opts.href;
opts = urllib.parse(prox);
opts.path = opts.pathname = path;
opts.headers = heads;
opts.method = method;
urltotest = match[0];
if (opts.auth) {
opts.headers['Proxy-Authorization'] = "Basic "+new Buffer(opts.auth).toString('Base64')
}
opts.proxy = prox;
} else {
node.warn("Bad proxy url: "+ prox);
opts.proxy = null;
}
else { node.warn("Bad proxy url: "+process.env.http_proxy); }
}
if (tlsNode) {
tlsNode.addTLSOptions(opts);
@@ -204,42 +198,37 @@ module.exports = function(RED) {
opts.rejectUnauthorized = msg.rejectUnauthorized;
}
}
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) {
// Force NodeJs to return a Buffer (instead of a string)
// See https://github.com/nodejs/node/issues/6038
res.setEncoding(null);
delete res._readableState.decoder;
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.responseUrl = res.responseUrl;
msg.payload = [];
if (msg.headers.hasOwnProperty('set-cookie')) {
msg.responseCookies = {};
msg.headers['set-cookie'].forEach(function(c) {
var parsedCookie = cookie.parse(c);
var eq_idx = c.indexOf('=');
var key = c.substr(0, eq_idx).trim()
parsedCookie.value = parsedCookie[key];
delete parsedCookie[key];
msg.responseCookies[key] = parsedCookie;
})
}
msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
// msg.url = url; // revert when warning above finally removed
res.on('data',function(chunk) {
if (!Buffer.isBuffer(chunk)) {
// if the 'setEncoding(null)' fix above stops working in
// a new Node.js release, throw a noisy error so we know
// about it.
throw new Error("HTTP Request data chunk not a Buffer");
request(opts, function(err, res, body) {
if(err){
if(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
node.error(RED._("common.notification.errors.no-response"), msg);
node.status({fill:"red", shape:"ring", text:"common.notification.errors.no-response"});
}else{
node.error(err,msg);
node.status({fill:"red", shape:"ring", text:err.code});
}
msg.payload.push(chunk);
});
res.on('end',function() {
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.send(msg);
}else{
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.responseUrl = res.request.uri.href;
msg.payload = body;
if (msg.headers.hasOwnProperty('set-cookie')) {
msg.responseCookies = {};
msg.headers['set-cookie'].forEach(function(c) {
var parsedCookie = cookie.parse(c);
var eq_idx = c.indexOf('=');
var key = c.substr(0, eq_idx).trim()
parsedCookie.value = parsedCookie[key];
delete parsedCookie[key];
msg.responseCookies[key] = parsedCookie;
});
}
msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
// msg.url = url; // revert when warning above finally removed
if (node.metric()) {
// Calculate request time
var diff = process.hrtime(preRequestTimestamp);
@@ -251,44 +240,19 @@ module.exports = function(RED) {
}
}
// Check that msg.payload is an array - if the req error
// handler has been called, it will have been set to a string
// and the error already handled - so no further action should
// be taken. #1344
if (Array.isArray(msg.payload)) {
// Convert the payload to the required return type
msg.payload = Buffer.concat(msg.payload); // bin
if (node.ret !== "bin") {
msg.payload = msg.payload.toString('utf8'); // txt
// Convert the payload to the required return type
if (node.ret !== "bin") {
msg.payload = msg.payload.toString('utf8'); // txt
if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); } // obj
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
}
if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); } // obj
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
}
node.status({});
node.send(msg);
}
});
node.status({});
node.send(msg);
}
});
req.setTimeout(node.reqTimeout, function() {
node.error(RED._("common.notification.errors.no-response"),msg);
setTimeout(function() {
node.status({fill:"red",shape:"ring",text:"common.notification.errors.no-response"});
},10);
req.abort();
});
req.on('error',function(err) {
node.error(err,msg);
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.status({fill:"red",shape:"ring",text:err.code});
node.send(msg);
});
if (payload) {
req.write(payload);
}
req.end();
});
this.on("close",function() {

View File

@@ -212,11 +212,11 @@ module.exports = function(RED) {
if (this.serverConfig) {
this.serverConfig.registerInputNode(this);
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
this.serverConfig.on('closed', function(n) {
if (n > 0) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }
else { node.status({fill:"red",shape:"ring",text:"disconnected"}); }
if (n > 0) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); }
else { node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); }
});
} else {
this.error(RED._("websocket.errors.missing-conf"));
@@ -240,11 +240,11 @@ module.exports = function(RED) {
}
else {
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
this.serverConfig.on('closed', function(n) {
if (n > 0) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }
else { node.status({fill:"red",shape:"ring",text:"disconnected"}); }
if (n > 0) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); }
else { node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); }
});
}
this.on("input", function(msg) {

View File

@@ -18,10 +18,34 @@ module.exports = function(RED) {
"use strict";
var reconnectTime = RED.settings.socketReconnectTime||10000;
var socketTimeout = RED.settings.socketTimeout||null;
const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000;
const Denque = require('denque');
var net = require('net');
var connectionPool = {};
/**
* Enqueue `item` in `queue`
* @param {Denque} queue - Queue
* @param {*} item - Item to enqueue
* @private
* @returns {Denque} `queue`
*/
const enqueue = (queue, item) => {
// drop msgs from front of queue if size is going to be exceeded
if (queue.size() === msgQueueSize) { queue.shift(); }
queue.push(item);
return queue;
};
/**
* Shifts item off front of queue
* @param {Deque} queue - Queue
* @private
* @returns {*} Item previously at front of queue
*/
const dequeue = queue => queue.shift();
function TcpIn(n) {
RED.nodes.createNode(this,n);
this.host = n.host;
@@ -130,6 +154,8 @@ module.exports = function(RED) {
socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = (1+Math.random()*4294967295).toString(16);
var fromi;
var fromp;
connectionPool[id] = socket;
count++;
node.status({text:RED._("tcpin.status.connections",{count:count})});
@@ -155,18 +181,21 @@ module.exports = function(RED) {
msg._session = {type:"tcp",id:id};
node.send(msg);
}
} else {
}
else {
if ((typeof data) === "string") {
buffer = buffer+data;
} else {
buffer = Buffer.concat([buffer,data],buffer.length+data.length);
}
fromi = socket.remoteAddress;
fromp = socket.remotePort;
}
});
socket.on('end', function() {
if (!node.stream || (node.datatype === "utf8" && node.newline !== "")) {
if (buffer.length > 0) {
var msg = {topic:node.topic, payload:buffer, ip:socket.remoteAddress, port:socket.remotePort};
var msg = {topic:node.topic, payload:buffer, ip:fromi, port:fromp};
msg._session = {type:"tcp",id:id};
node.send(msg);
}
@@ -430,11 +459,15 @@ module.exports = function(RED) {
// the clients object will have:
// clients[id].client, clients[id].msg, clients[id].timeout
var connection_id = host + ":" + port;
clients[connection_id] = clients[connection_id] || {};
clients[connection_id].msg = msg;
clients[connection_id].connected = clients[connection_id].connected || false;
clients[connection_id] = clients[connection_id] || {
msgQueue: new Denque(),
connected: false,
connecting: false
};
enqueue(clients[connection_id].msgQueue, msg);
clients[connection_id].lastMsg = msg;
if (!clients[connection_id].connected) {
if (!clients[connection_id].connecting && !clients[connection_id].connected) {
var buf;
if (this.out == "count") {
if (this.splitc === 0) { buf = Buffer.alloc(1); }
@@ -446,14 +479,19 @@ module.exports = function(RED) {
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
if (host && port) {
clients[connection_id].connecting = true;
clients[connection_id].client.connect(port, host, function() {
//node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = true;
clients[connection_id].client.write(clients[connection_id].msg.payload);
clients[connection_id].connecting = false;
let msg;
while (msg = dequeue(clients[connection_id].msgQueue)) {
clients[connection_id].client.write(msg.payload);
}
if (node.out === "time" && node.splitc < 0) {
clients[connection_id].connected = false;
clients[connection_id].connected = clients[connection_id].connecting = false;
clients[connection_id].client.end();
delete clients[connection_id];
node.status({});
@@ -468,9 +506,9 @@ module.exports = function(RED) {
clients[connection_id].client.on('data', function(data) {
if (node.out === "sit") { // if we are staying connected just send the buffer
if (clients[connection_id]) {
if (!clients[connection_id].hasOwnProperty("msg")) { clients[connection_id].msg = {}; }
clients[connection_id].msg.payload = data;
node.send(RED.util.cloneMessage(clients[connection_id].msg));
const msg = clients[connection_id].lastMsg || {};
msg.payload = data;
node.send(RED.util.cloneMessage(msg));
}
}
// else if (node.splitc === 0) {
@@ -490,9 +528,10 @@ module.exports = function(RED) {
clients[connection_id].timeout = setTimeout(function () {
if (clients[connection_id]) {
clients[connection_id].timeout = null;
clients[connection_id].msg.payload = Buffer.alloc(i+1);
buf.copy(clients[connection_id].msg.payload,0,0,i+1);
node.send(clients[connection_id].msg);
const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i+1);
buf.copy(msg.payload,0,0,i+1);
node.send(msg);
if (clients[connection_id].client) {
node.status({});
clients[connection_id].client.destroy();
@@ -511,9 +550,10 @@ module.exports = function(RED) {
i += 1;
if ( i >= node.splitc) {
if (clients[connection_id]) {
clients[connection_id].msg.payload = Buffer.alloc(i);
buf.copy(clients[connection_id].msg.payload,0,0,i);
node.send(clients[connection_id].msg);
const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i);
buf.copy(msg.payload,0,0,i);
node.send(msg);
if (clients[connection_id].client) {
node.status({});
clients[connection_id].client.destroy();
@@ -529,9 +569,10 @@ module.exports = function(RED) {
i += 1;
if (data[j] == node.splitc) {
if (clients[connection_id]) {
clients[connection_id].msg.payload = Buffer.alloc(i);
buf.copy(clients[connection_id].msg.payload,0,0,i);
node.send(clients[connection_id].msg);
const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i);
buf.copy(msg.payload,0,0,i);
node.send(msg);
if (clients[connection_id].client) {
node.status({});
clients[connection_id].client.destroy();
@@ -549,7 +590,7 @@ module.exports = function(RED) {
//console.log("END");
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = false;
clients[connection_id].connected = clients[connection_id].connecting = false;
clients[connection_id].client = null;
}
});
@@ -557,7 +598,7 @@ module.exports = function(RED) {
clients[connection_id].client.on('close', function() {
//console.log("CLOSE");
if (clients[connection_id]) {
clients[connection_id].connected = false;
clients[connection_id].connected = clients[connection_id].connecting = false;
}
var anyConnected = false;
@@ -587,21 +628,23 @@ module.exports = function(RED) {
clients[connection_id].client.on('timeout',function() {
//console.log("TIMEOUT");
if (clients[connection_id]) {
clients[connection_id].connected = false;
clients[connection_id].connected = clients[connection_id].connecting = false;
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
//node.warn(RED._("tcpin.errors.connect-timeout"));
if (clients[connection_id].client) {
clients[connection_id].connecting = true;
clients[connection_id].client.connect(port, host, function() {
clients[connection_id].connected = true;
clients[connection_id].connecting = false;
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
}
}
});
}
else {
else if (!clients[connection_id].connecting && clients[connection_id].connected) {
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].client.write(clients[connection_id].msg.payload);
clients[connection_id].client.write(dequeue(clients[connection_id].msgQueue).payload);
}
}
});

View File

@@ -63,7 +63,7 @@ module.exports = function(RED) {
udpInputPortsInUse[this.port] = server;
}
else {
node.warn(RED._("udp.errors.alreadyused",{port:node.port}));
node.log(RED._("udp.errors.alreadyused",{port:node.port}));
server = udpInputPortsInUse[this.port]; // re-use existing
}
@@ -172,8 +172,7 @@ module.exports = function(RED) {
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var sock;
var p = this.port;
if (node.multicast != "false") { p = this.outport||"0"; }
var p = this.outport || this.port || "0";
if (udpInputPortsInUse[p]) {
sock = udpInputPortsInUse[p];
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));

View File

@@ -153,13 +153,15 @@
"key": "Private Key",
"passphrase": "Passphrase",
"ca": "CA Certificate",
"verify-server-cert":"Verify server certificate"
"verify-server-cert":"Verify server certificate",
"servername": "Server Name"
},
"placeholder": {
"cert":"path to certificate (PEM format)",
"key":"path to private key (PEM format)",
"ca":"path to CA certificate (PEM format)",
"passphrase":"private key passphrase (optional)"
"passphrase":"private key passphrase (optional)",
"servername":"for use with SNI"
},
"error": {
"missing-file": "No certificate/key file provided"
@@ -420,6 +422,10 @@
"url1": "URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.",
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
},
"status": {
"connected": "connected __count__",
"connected_plural": "connected __count__"
},
"errors": {
"connect-error": "An error occured on the ws connection: ",
"send-error": "An error occurred while sending: ",
@@ -575,6 +581,8 @@
"null":"is null",
"nnull":"is not null",
"istype":"is of type",
"empty":"is empty",
"nempty":"is not empty",
"head":"head",
"tail":"tail",
"index":"index between",
@@ -697,7 +705,9 @@
"errors": {
"dropped-object": "Ignored non-object payload",
"dropped": "Ignored unsupported payload type",
"dropped-error": "Failed to convert payload"
"dropped-error": "Failed to convert payload",
"schema-error": "JSON Schema error",
"schema-error-compile": "JSON Schema error: failed to compile schema"
},
"label": {
"o2j": "Object to JSON options",
@@ -786,8 +796,8 @@
"na": "N/A : __value__"
},
"errors": {
"ignorenode": "Ignoring Raspberry Pi specific node",
"version": "Version command failed",
"ignorenode": "Raspberry Pi specific node set inactive",
"version": "Failed to get version from Pi",
"sawpitype": "Saw Pi Type",
"libnotfound": "Cannot find Pi RPi.GPIO python library",
"alreadyset": "GPIO pin __pin__ already set as type: __type__",
@@ -924,8 +934,8 @@
"ascending" : "ascending",
"descending" : "descending",
"as-number" : "as number",
"invalid-exp" : "invalid JSONata expression in sort node",
"too-many" : "too many pending messages in sort node",
"invalid-exp" : "Invalid JSONata expression in sort node: __message__",
"too-many" : "Too many pending messages in sort node",
"clear" : "clear pending message in sort node"
},
"batch" : {

View File

@@ -26,7 +26,7 @@
<dt class="optional">kill <span class="property-type">文字列</span></dt>
<dd>execードのプロセスに対して送るシグナルの種別を指定します</dd>
<dt class="optional">pid <span class="property-type">数値|文字列</span></dt>
<dd>シグナル送信対象のexecードのプロセスID</dd>
<dd>シグナル送信対象のexecードのプロセスIDを指定します</dd>
</dl>
<h3>出力</h3>
@@ -60,13 +60,12 @@
</ol>
<h3>詳細</h3>
<p>デフォルトでは<code>exec</code><code>{ code: 0 }</code></p>
<p><code>spawn</code>使
標準出力および標準エラー出力へ出力を返すようにすることもできますこの場合通常1行毎に値を返しますコマンドの実行が完了すると3番目の端子にオブジェクトを出力します例えばコマンドの実行が成功した場合には<code>{ code: 0 }</code></p>
<p><code>spawn</code>使13<code>{ code: 0 }</code></p>
<p>エラー発生時には3番目の端子の<code>msg.payload</code><code>message</code><code>signal</code></p>
<p>実行対象のコマンドはノード設定で定義します<code>msg.payload</code></p>
<p>コマンドもしくはパラメータが空白を含む場合には引用符で囲みます- <code>"This is a single parameter"</code></p>
<p>コマンドもしくはパラメータが空白を含む場合には引用符で囲みます- <code>"これは一つのパラメータです"</code></p>
<p>返却する<code>payload</code>は通常<i>文字列</i>ですがUTF8文字以外が存在すると<i>バッファ</i></p>
<p>ノードが実行中の場合ステータスアイコンとPIDを表示しますこの状態変化は<code>status</code></p>
<p>ノードが実行中の場合ステータスアイコンとPIDを表示しますこの状態変化は<code>Status</code></p>
<h4>プロセスの停止</h4>
<p><code>msg.kill</code><code>msg.kill</code><code>SIGINT</code><code>SIGQUIT</code><code>SIGHUP</code><code>SIGTERM</code></p>
<p>ードが1つ以上のプロセスを実行している場合<code>msg.pid</code>PID</p>

View File

@@ -18,7 +18,7 @@
<p>受信メッセージに対して処理を行うJavaScriptコード(関数の本体)を定義します</p>
<p>入力メッセージは<code>msg</code>JavaScript</p>
<p><code>msg</code><code>msg.payload</code></p>
<p>通常コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します何も返却しない場合フロー実行を停止します</p>
<p>通常コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します後続フローの実行を停止したい場合はオブジェクトを返却しなくてもかまいません</p>
<h3>詳細</h3>
<p>コードの書き方の詳細については<a target="_blank" href="http://nodered.org/docs/writing-functions.html">オンラインドキュメント</a></p>
<h4>メッセージの送信</h4>
@@ -40,4 +40,10 @@
</p>
<p>catchードを用いてエラー処理が可能ですcatchードで処理させるためには<code>msg</code><code>node.error</code>:</p>
<pre>node.error("エラー",msg);</pre>
<h4>ノード情報の参照</h4>
<p>コード中ではードのIDおよび名前を以下のプロパティで参照できます:</p>
<ul>
<li><code>node.id</code> - ID</li>
<li><code>node.name</code> - </li>
</ul>
</script>

View File

@@ -63,6 +63,8 @@
<p>ードにクライアントIDを設定しておらずセッションの初期化を設定している場合ランダムなクライアントIDを生成しますクライアントIDを設定する場合接続先のブローカで一意となるようにしてください</p>
<h4>Birthメッセージ</h4>
<p>接続を確立した際に設定したトピックに対して発行するメッセージ</p>
<h4>Closeメッセージ</h4>
<p>接続が正常に終了する前にノードの再デプロイまたはシャットダウンした場合に設定したトピックに対して発行するメッセージ</p>
<h4>Willメッセージ</h4>
<p>予期せず接続が切断された場合にブローカが発行するメッセージ</p>
<h4>WebSocket</h4>

View File

@@ -30,7 +30,9 @@
<dt class="optional">payload</dt>
<dd>リクエストボディとして送るデータ</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd><code>true</code>使https</dd>
<dd><code>false</code>使https</dd>
<dt class="optional">followRedirects</dt>
<dd><code>false</code><code>true</code></dd>
</dl>
<h3>出力</h3>
<dl class="message-properties">

View File

@@ -29,7 +29,11 @@
<li><b>その他</b> - </li>
</ol>
<h3>注釈</h3>
<p><code>is true/false</code><code>is null</code></p>
<p><code>is empty</code><code>null</code><code>undefined</code></p>
<h3>メッセージ列の扱い</h3>
<p>switchードは入力メッセージの列に関する情報を保持する<code>msg.parts</code></p>
<p><b>メッセージ列の補正</b>switch<code>nodeMessageBufferMaxLength</code></p>
<p><b>メッセージ列の補正</b>switch<b>settings.js</b><code>nodeMessageBufferMaxLength</code></p>
</script>

View File

@@ -30,5 +30,5 @@
</dd>
</dl>
<h4>メッセージの蓄積</h4>
<p>このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します<code>nodeMessageBufferMaxLength</code></p>
<p>このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します<b>settings.js</b><code>nodeMessageBufferMaxLength</code></p>
</script>

View File

@@ -153,19 +153,23 @@
"key": "秘密鍵",
"passphrase": "パスフレーズ",
"ca": "CA証明書",
"verify-server-cert": "サーバ証明書を確認"
"verify-server-cert": "サーバ証明書を確認",
"servername": "サーバ名"
},
"placeholder": {
"cert": "証明書(PEM形式)のパス",
"key": "秘密鍵(PEM形式)のパス",
"ca": "CA証明書(PEM形式)のパス",
"passphrase":"秘密鍵のパスフレーズ (任意)"
"passphrase": "秘密鍵のパスフレーズ (任意)",
"servername": "SNIで使用"
},
"error": {
"missing-file": "証明書と秘密鍵のファイルが設定されていません"
}
},
"exec": {
"exec": "exec",
"spawn": "spawn",
"label": {
"command": "コマンド",
"append": "引数",
@@ -184,6 +188,7 @@
"oldrc": "旧型式の出力を使用(互換モード)"
},
"function": {
"function": "",
"label": {
"function": "コード",
"outputs": "出力数"
@@ -195,6 +200,7 @@
"tip": "コードの記述方法はノードの「情報」を参照してください。"
},
"template": {
"template": "template",
"label": {
"template": "テンプレート",
"property": "設定先",
@@ -301,6 +307,7 @@
}
},
"comment": {
"comment": "comment",
"label": {
"title": "タイトル",
"body": "本文"
@@ -318,6 +325,7 @@
"broker": "サーバ",
"example": "例) localhost",
"qos": "QoS",
"retain": "保持",
"clientid": "クライアント",
"port": "ポート",
"keepalive": "キープアライブ時間",
@@ -327,10 +335,10 @@
"verify-server-cert": "サーバの証明書を確認",
"compatmode": "旧MQTT 3.1のサポート"
},
"sections-label":{
"sections-label": {
"birth-message": "接続時の送信メッセージ(Birthメッセージ)",
"will-message":"予期しない切断時の送信メッセージ(Willメッセージ)",
"close-message":"切断前の送信メッセージ(Closeメッセージ)"
"will-message": "予期しない切断時の送信メッセージ(Willメッセージ)",
"close-message": "切断前の送信メッセージ(Closeメッセージ)"
},
"tabs-label": {
"connection": "接続",
@@ -414,6 +422,10 @@
"url1": "URLには ws:&#47;&#47; または wss:&#47;&#47; スキーマを使用して、存在するwebsocketリスナを設定してください。",
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
},
"status": {
"connected": "接続数 __count__",
"connected_plural": "接続数 __count__"
},
"errors": {
"connect-error": "ws接続でエラーが発生しました: ",
"send-error": "送信中にエラーが発生しました: ",
@@ -421,6 +433,7 @@
}
},
"watch": {
"watch": "watch",
"label": {
"files": "ファイル",
"recursive": "サブディレクトリを再帰的に監視"
@@ -543,15 +556,15 @@
"port-notset": "udp: ポートが設定されていません",
"port-invalid": "udp: ポート番号が不正です",
"alreadyused": "udp: 既に__port__番ポートが使用されています",
"ifnotfound": "udp: インターフェイス __iface__ がありません",
"alreadyused": "udp: 既にポートが使用されています"
"ifnotfound": "udp: インターフェイス __iface__ がありません"
}
},
"switch": {
"switch": "switch",
"label": {
"property": "プロパティ",
"rule": "条件",
"repair" : "メッセージ列の補正"
"repair": "メッセージ列の補正"
},
"and": "",
"checkall": "全ての条件を適用",
@@ -565,15 +578,18 @@
"false": "is false",
"null": "is null",
"nnull": "is not null",
"head":"head",
"tail":"tail",
"index":"index between",
"exp":"JSONata式",
"istype": "is of type",
"empty": "is empty",
"nempty": "is not empty",
"head": "head",
"tail": "tail",
"index": "index between",
"exp": "JSONata式",
"else": "その他"
},
"errors": {
"invalid-expr": "不正な表現: __error__",
"too-many" : "switchード内で保持しているメッセージが多すぎます"
"too-many": "switchード内で保持しているメッセージが多すぎます"
}
},
"change": {
@@ -603,6 +619,7 @@
}
},
"range": {
"range": "range",
"label": {
"action": "動作",
"inputrange": "入力値の範囲",
@@ -670,7 +687,7 @@
"label": {
"select": "抽出する要素",
"output": "出力",
"in": "対象:"
"in": "対象:"
},
"output": {
"html": "要素内のHTML",
@@ -686,7 +703,9 @@
"errors": {
"dropped-object": "オブジェクト形式でないペイロードを無視しました",
"dropped": "対応していない形式のペイロードを無視しました",
"dropped-error": "ペイロードの変換処理が失敗しました"
"dropped-error": "ペイロードの変換処理が失敗しました",
"schema-error": "JSONスキーマエラー",
"schema-error-compile": "JSONスキーマエラー: スキーマのコンパイルが失敗しました"
},
"label": {
"o2j": "オブジェクトからJSONへ変換",
@@ -695,8 +714,8 @@
"property": "プロパティ",
"actions": {
"toggle": "JSON文字列とオブジェクト間の相互変換",
"str":"常にJSON文字列に変換",
"obj":"常にJavaScriptオブジェクトに変換"
"str": "常にJSON文字列に変換",
"obj": "常にJavaScriptオブジェクトに変換"
}
}
},
@@ -791,6 +810,7 @@
}
},
"tail": {
"tail": "tail",
"label": {
"filename": "ファイル名",
"type": "ファイル形式",
@@ -844,6 +864,7 @@
"tip": "注釈: 「ファイル名」はフルパスを設定する必要があります。"
},
"split": {
"split": "split",
"intro": "型に基づいて <code>msg.payload</code> を分割:",
"object": "<b>オブジェクト</b>",
"objectSend": "各key/valueペアのメッセージを送信",
@@ -855,11 +876,12 @@
"addname": " keyのコピー先"
},
"join": {
"join": "join",
"mode": {
"mode": "動作",
"auto": "自動",
"merge":"列のマージ",
"reduce":"列の集約",
"merge": "列のマージ",
"reduce": "列の集約",
"custom": "手動"
},
"combine": "結合",
@@ -882,12 +904,12 @@
"seconds": "秒",
"complete": "<code>msg.complete</code> プロパティが設定されたメッセージ受信後",
"tip": "このモードでは、本ノードが <i>split</i> ノードと組となるか、 <code>msg.parts</code> プロパティが設定されたメッセージを受け取ることが前提となります。",
"too-many" : "joinード内部で保持しているメッセージが多すぎます",
"too-many": "joinード内部で保持しているメッセージが多すぎます",
"merge": {
"topics-label":"対象トピック",
"topics":"トピック",
"topic" : "トピック",
"on-change":"新規トピックを受け取るとメッセージを送信する"
"topics-label": "対象トピック",
"topics": "トピック",
"topic": "トピック",
"on-change": "新規トピックを受け取るとメッセージを送信する"
},
"reduce": {
"exp": "集約式",
@@ -900,43 +922,45 @@
"invalid-expr": "JSONata式が不正: __error__"
}
},
"sort" : {
"target" : "対象",
"seq" : "メッセージ列",
"key" : "キー",
"elem" : "要素の値",
"order" : "順序",
"ascending" : "順",
"descending" : "順",
"as-number" : "数値として比較",
"invalid-exp" : "sortードで不正なJSONata式が指定されました",
"too-many" : "sortードの未処理メッセージの数が許容数を超えました",
"clear" : "sortードの未処理メッセージを破棄しました"
"sort": {
"sort": "sort",
"target": "対象",
"seq": "メッセージ列",
"key": "キー",
"elem": "要素の値",
"order": "順",
"ascending": "順",
"descending": "降順",
"as-number": "数値として比較",
"invalid-exp": "sortードで不正なJSONata式が指定されました",
"too-many": "sortードの未処理メッセージの数が許容数を超えました",
"clear": "sortードの未処理メッセージを破棄しました"
},
"batch" : {
"batch": {
"batch": "batch",
"mode": {
"label" : "モード",
"num-msgs" : "メッセージ数でグループ化",
"interval" : "時間間隔でグループ化",
"concat" : "列の結合"
"label": "モード",
"num-msgs": "メッセージ数でグループ化",
"interval": "時間間隔でグループ化",
"concat": "列の結合"
},
"count": {
"label" : "メッセージ数",
"overlap" : "オーバラップ",
"count" : "数",
"invalid" : "メッセージ数とオーバラップ数が不正"
"label": "メッセージ数",
"overlap": "オーバラップ",
"count": "数",
"invalid": "メッセージ数とオーバラップ数が不正"
},
"interval": {
"label" : "時間間隔",
"seconds" : "秒",
"empty" : "メッセージを受信しない場合、空のメッセージを送信"
"label": "時間間隔",
"seconds": "秒",
"empty": "メッセージを受信しない場合、空のメッセージを送信"
},
"concat": {
"topics-label": "トピック",
"topic" : "トピック"
"topic": "トピック"
},
"too-many" : "batchード内で保持しているメッセージが多すぎます",
"unexpected" : "想定外のモード",
"no-parts" : "メッセージにpartsプロパティがありません"
"too-many": "batchード内で保持しているメッセージが多すぎます",
"unexpected": "想定外のモード",
"no-parts": "メッセージにpartsプロパティがありません"
}
}

View File

@@ -20,6 +20,8 @@
<dl class="message-properties">
<dt>payload<span class="property-type">オブジェクト | 文字列</span></dt>
<dd>JavaScriptオブジェクトもしくはJSON文字列</dd>
<dt>schema<span class="property-type">オブジェクト</span></dt>
<dd>JSONの検証に利用するJSONスキーマ設定されていない場合は検証を行いません</dd>
</dl>
<h3>出力</h3>
<dl class="message-properties">
@@ -30,9 +32,12 @@
<li>入力がJavaScriptオブジェクトの場合JSON文字列に変換しますJSON文字列は整形することも可能です</li>
</ul>
</dd>
<dt>schemaError<span class="property-type">配列</span></dt>
<dd>JSONの検証でエラーが発生した場合Catchードを利用しエラーを配列として<code>schemaError</code></dd>
</dl>
<h3>詳細</h3>
<p>デフォルトの変換対象は<code>msg.payload</code></p>
<p>双方向の変換を自動選択するのではなく特定の変換のみ行うように設定できますこの機能は例えば<code>HTTP In</code>content-typeJSONJavaScript</p>
<p>JSON文字列への変換が指定されている場合受信した文字列に対してさらなるチェックは行いませんすなわち文字列がJSONとして正しいかどうかの検査や整形オプションを指定していたとしても整形処理を実施しません</p>
<p>JSONスキーマの詳細については<a href="http://json-schema.org/latest/json-schema-validation.html">こちら</a></p>
</script>

View File

@@ -21,6 +21,8 @@
<dt class="optional">filename <span class="property-type">文字列</span></dt>
<dd>対象ファイル名をノードに設定していない場合このプロパティでファイルを指定できます</dd>
</dl>
<h3>出力</h3>
<p>書き込みの完了時入力メッセージを出力端子に送出します</p>
<h3>詳細</h3>
<p>入力メッセージのペイロードをファイルの最後に追記します改行(\n)を各データの最後に追加することもできます</p>
<p><code>msg.filename</code>使</p>

View File

@@ -60,6 +60,12 @@
<li>An <b>Otherwise</b> rule can be used to match if none of the preceeding
rules have matched.</li>
</ol>
<h4>Notes</h4>
<p>The <code>is true/false</code> and <code>is null</code> rules perform strict
comparisons against those types. They do not convert between types.</p>
<p>The <code>is empty</code> rule passes for Strings, Arrays and Buffers that have
a length of 0, or Objects that have no properties. It does not pass for <code>null</code>
or <code>undefined</code> values.</p>
<h4>Handling message sequences</h4>
<p>By default, the node does not modify the <code>msg.parts</code> property of messages
that are part of a sequence.</p>
@@ -86,6 +92,8 @@
{v:"null",t:"switch.rules.null",kind:'V'},
{v:"nnull",t:"switch.rules.nnull",kind:'V'},
{v:"istype",t:"switch.rules.istype",kind:'V'},
{v:"empty",t:"switch.rules.empty",kind:'V'},
{v:"nempty",t:"switch.rules.nempty",kind:'V'},
{v:"head",t:"switch.rules.head",kind:'S'},
{v:"index",t:"switch.rules.index",kind:'S'},
{v:"tail",t:"switch.rules.tail",kind:'S'},
@@ -99,11 +107,17 @@
}
return v;
}
function prop2name(key) {
var result = RED.utils.parseContextKey(key);
return result.key;
}
function getValueLabel(t,v) {
if (t === 'str') {
return '"'+clipValueLength(v)+'"';
} else if (t === 'msg' || t==='flow' || t==='global') {
} else if (t === 'msg') {
return t+"."+clipValueLength(v);
} else if (t === 'flow' || t === 'global') {
return t+"."+clipValueLength(prop2name(v));
}
return clipValueLength(v);
}
@@ -133,7 +147,7 @@
}
if ((rule.t === 'btwn') || (rule.t === 'index')) {
label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'else' ) {
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'empty' && rule.t !== 'nempty' && rule.t !== 'else' ) {
label += " "+getValueLabel(rule.vt,rule.v);
}
return label;
@@ -185,7 +199,7 @@
} else if (type === "istype") {
typeField.typedInput("width",(newWidth-selectWidth-70));
} else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else") {
// valueField.hide();
} else {
valueField.typedInput("width",(newWidth-selectWidth-70));
@@ -230,12 +244,12 @@
selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
}
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var numValueField = $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata']});
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
var numValueField = $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata','env']});
var expValueField = $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'jsonata',types:['jsonata']});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
var typeValueField = $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row)
.typedInput({default:'string',types:[
{value:"string",label:"string",hasValue:false},
@@ -281,7 +295,7 @@
numValueField.typedInput('hide');
typeValueField.typedInput('hide');
valueField.typedInput('hide');
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else") {
valueField.typedInput('hide');
typeValueField.typedInput('hide');
}
@@ -382,7 +396,7 @@
var rule = $(this);
var type = rule.find("select").val();
var r = {t:type};
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "empty" || type === "nempty" || type === "else")) {
if ((type === "btwn") || (type === "index")) {
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');

View File

@@ -31,6 +31,23 @@ module.exports = function(RED) {
'false': function(a) { return a === false; },
'null': function(a) { return (typeof a == "undefined" || a === null); },
'nnull': function(a) { return (typeof a != "undefined" && a !== null); },
'empty': function(a) {
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
return a.length === 0;
} else if (typeof a === 'object' && a !== null) {
return Object.keys(a).length === 0;
}
return false;
},
'nempty': function(a) {
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
return a.length !== 0;
} else if (typeof a === 'object' && a !== null) {
return Object.keys(a).length !== 0;
}
return false;
},
'istype': function(a, b) {
if (b === "array") { return Array.isArray(a); }
else if (b === "buffer") { return Buffer.isBuffer(a); }
@@ -59,21 +76,254 @@ module.exports = function(RED) {
'else': function(a) { return a === true; }
};
var _max_kept_msgs_count = undefined;
var _maxKeptCount;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
function getMaxKeptCount() {
if (_maxKeptCount === undefined) {
var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
_maxKeptCount = RED.settings[name];
}
else {
_max_kept_msgs_count = 0;
_maxKeptCount = 0;
}
}
return _max_kept_msgs_count;
return _maxKeptCount;
}
function getProperty(node,msg) {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else {
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
}
});
} else {
if (node.propertyType === 'jsonata') {
try {
return RED.util.evaluateJSONataExpression(node.property,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else {
try {
return RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
} catch(err) {
return undefined;
}
}
}
}
function getV1(node,msg,rule,hasParts) {
if (node.useAsyncRules) {
return new Promise( (resolve,reject) => {
if (rule.vt === 'prev') {
resolve(node.previousValue);
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (rule.vt === 'json') {
resolve("json"); // TODO: ?! invalid case
} else if (rule.vt === 'null') {
resolve("null");
} else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
}
});
} else {
if (rule.vt === 'prev') {
return node.previousValue;
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
try {
return RED.util.evaluateJSONataExpression(exp,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else if (rule.vt === 'json') {
return "json"; // TODO: ?! invalid case
} else if (rule.vt === 'null') {
return "null";
} else {
try {
return RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
} catch(err) {
return undefined;
}
}
}
}
function getV2(node,msg,rule) {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
resolve(node.previousValue);
} else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
} else {
resolve(v2);
}
})
} else {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
return node.previousValue;
} else if (rule.v2t === 'jsonata') {
try {
return RED.util.evaluateJSONataExpression(rule.v2,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else if (typeof v2 !== 'undefined') {
try {
return RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
} catch(err) {
return undefined;
}
} else {
return v2;
}
}
}
function applyRule(node, msg, property, state) {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var rule = node.rules[state.currentRule];
var v1,v2;
getV1(node,msg,rule,state.hasParts).then(value => {
v1 = value;
}).then(()=>getV2(node,msg,rule)).then(value => {
v2 = value;
}).then(() => {
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return resolve(false);
}
} else {
state.onward.push(null);
}
resolve(state.currentRule < node.rules.length - 1);
});
})
} else {
var rule = node.rules[state.currentRule];
var v1 = getV1(node,msg,rule,state.hasParts);
var v2 = getV2(node,msg,rule);
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return false;
}
} else {
state.onward.push(null);
}
return state.currentRule < node.rules.length - 1
}
}
function applyRules(node, msg, property,state) {
if (!state) {
state = {
currentRule: 0,
elseflag: true,
onward: [],
hasParts: msg.hasOwnProperty("parts") &&
msg.parts.hasOwnProperty("id") &&
msg.parts.hasOwnProperty("index")
}
}
if (node.useAsyncRules) {
return applyRule(node,msg,property,state).then(hasMore => {
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
} else {
node.previousValue = property;
return state.onward;
}
});
} else {
var hasMore = applyRule(node,msg,property,state);
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
} else {
node.previousValue = property;
return state.onward;
}
}
}
function SwitchNode(n) {
RED.nodes.createNode(this, n);
this.rules = n.rules || [];
@@ -94,10 +344,18 @@ module.exports = function(RED) {
var node = this;
var valid = true;
var repair = n.repair;
var needs_count = repair;
var needsCount = repair;
this.useAsyncRules = (
this.propertyType === 'flow' ||
this.propertyType === 'global' || (
this.propertyType === 'jsonata' &&
/\$(flow|global)Context/.test(this.property)
)
);
for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i];
needs_count = needs_count || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
needsCount = needsCount || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
if (!rule.vt) {
if (!isNaN(Number(rule.v))) {
rule.vt = 'num';
@@ -105,6 +363,13 @@ module.exports = function(RED) {
rule.vt = 'str';
}
}
this.useAsyncRules = this.useAsyncRules || (
rule.vt === 'flow' ||
rule.vt === 'global' || (
rule.vt === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v)
)
);
if (rule.vt === 'num') {
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
@@ -117,6 +382,9 @@ module.exports = function(RED) {
valid = false;
}
}
if (rule.vt === 'flow' || rule.vt === 'global' || rule.vt === 'jsonata') {
this.useAsyncRules = true;
}
if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) {
if (!isNaN(Number(rule.v2))) {
@@ -125,6 +393,13 @@ module.exports = function(RED) {
rule.v2t = 'str';
}
}
this.useAsyncRules = this.useAsyncRules || (
rule.v2t === 'flow' ||
rule.v2t === 'global' || (
rule.v2t === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v2)
)
);
if (rule.v2t === 'num') {
rule.v2 = Number(rule.v2);
} else if (rule.v2t === 'jsonata') {
@@ -137,31 +412,30 @@ module.exports = function(RED) {
}
}
}
if (!valid) {
return;
}
var pending_count = 0;
var pending_id = 0;
var pending_in = {};
var pending_out = {};
var pendingCount = 0;
var pendingId = 0;
var pendingIn = {};
var pendingOut = {};
var received = {};
function add2group_in(id, msg, parts) {
if (!(id in pending_in)) {
pending_in[id] = {
function addMessageToGroup(id, msg, parts) {
if (!(id in pendingIn)) {
pendingIn[id] = {
count: undefined,
msgs: [],
seq_no: pending_id++
seq_no: pendingId++
};
}
var group = pending_in[id];
var group = pendingIn[id];
group.msgs.push(msg);
pending_count++;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
clear_pending();
pendingCount++;
var max_msgs = getMaxKeptCount();
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
clearPending();
node.error(RED._("switch.errors.too-many"), msg);
}
if (parts.hasOwnProperty("count")) {
@@ -170,32 +444,29 @@ module.exports = function(RED) {
return group;
}
function del_group_in(id, group) {
pending_count -= group.msgs.length;
delete pending_in[id];
}
function add2pending_in(msg) {
function addMessageToPending(msg) {
var parts = msg.parts;
if (parts.hasOwnProperty("id") &&
parts.hasOwnProperty("index")) {
var group = add2group_in(parts.id, msg, parts);
var msgs = group.msgs;
var count = group.count;
if (count === msgs.length) {
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
// We've already checked the msg.parts has the require bits
var group = addMessageToGroup(parts.id, msg, parts);
var msgs = group.msgs;
var count = group.count;
if (count === msgs.length) {
// We have a complete group - send the individual parts
return msgs.reduce((promise, msg) => {
return promise.then((result) => {
msg.parts.count = count;
process_msg(msg, false);
}
del_group_in(parts.id, group);
}
return true;
return processMessage(msg, false);
})
}, Promise.resolve()).then( () => {
pendingCount -= group.msgs.length;
delete pendingIn[parts.id];
});
}
return false;
return Promise.resolve();
}
function send_group(onwards, port_count) {
function sendGroup(onwards, port_count) {
var counts = new Array(port_count).fill(0);
for (var i = 0; i < onwards.length; i++) {
var onward = onwards[i];
@@ -230,141 +501,122 @@ module.exports = function(RED) {
}
}
function send2ports(onward, msg) {
function sendGroupMessages(onward, msg) {
var parts = msg.parts;
var gid = parts.id;
received[gid] = ((gid in received) ? received[gid] : 0) +1;
var send_ok = (received[gid] === parts.count);
if (!(gid in pending_out)) {
pending_out[gid] = {
if (!(gid in pendingOut)) {
pendingOut[gid] = {
onwards: []
};
}
var group = pending_out[gid];
var group = pendingOut[gid];
var onwards = group.onwards;
onwards.push(onward);
pending_count++;
pendingCount++;
if (send_ok) {
send_group(onwards, onward.length, msg);
pending_count -= onward.length;
delete pending_out[gid];
sendGroup(onwards, onward.length, msg);
pendingCount -= onward.length;
delete pendingOut[gid];
delete received[gid];
}
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
clear_pending();
var max_msgs = getMaxKeptCount();
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
clearPending();
node.error(RED._("switch.errors.too-many"), msg);
}
}
function msg_has_parts(msg) {
if (msg.hasOwnProperty("parts")) {
var parts = msg.parts;
return (parts.hasOwnProperty("id") &&
parts.hasOwnProperty("index"));
}
return false;
}
function process_msg(msg, check_parts) {
var has_parts = msg_has_parts(msg);
if (needs_count && check_parts && has_parts &&
add2pending_in(msg)) {
return;
function processMessage(msg, checkParts) {
var hasParts = msg.hasOwnProperty("parts") &&
msg.parts.hasOwnProperty("id") &&
msg.parts.hasOwnProperty("index");
if (needsCount && checkParts && hasParts) {
return addMessageToPending(msg);
}
var onward = [];
try {
var prop;
if (node.propertyType === 'jsonata') {
prop = RED.util.evaluateJSONataExpression(node.property,msg);
} else {
prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
}
var elseflag = true;
for (var i=0; i<node.rules.length; i+=1) {
var rule = node.rules[i];
var test = prop;
var v1,v2;
if (rule.vt === 'prev') {
v1 = node.previousValue;
} else if (rule.vt === 'jsonata') {
try {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (has_parts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
if (node.useAsyncRules) {
return getProperty(node,msg)
.then(property => applyRules(node,msg,property))
.then(onward => {
if (!repair || !hasParts) {
node.send(onward);
}
v1 = RED.util.evaluateJSONataExpression(exp,msg);
} catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else if (rule.vt === 'json') {
v1 = "json";
} else if (rule.vt === 'null') {
v1 = "null";
else {
sendGroupMessages(onward, msg);
}
}).catch(err => {
node.warn(err);
});
} else {
try {
var property = getProperty(node,msg);
var onward = applyRules(node,msg,property);
if (!repair || !hasParts) {
node.send(onward);
} else {
try {
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
} catch(err) {
v1 = undefined;
}
}
v2 = rule.v2;
if (rule.v2t === 'prev') {
v2 = node.previousValue;
} else if (rule.v2t === 'jsonata') {
try {
v2 = RED.util.evaluateJSONataExpression(rule.v2,msg);
} catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return;
}
} else if (typeof v2 !== 'undefined') {
try {
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
} catch(err) {
v2 = undefined;
}
}
if (rule.t == "else") { test = elseflag; elseflag = true; }
if (operators[rule.t](test,v1,v2,rule.case,msg.parts)) {
onward.push(msg);
elseflag = false;
if (node.checkall == "false") { break; }
} else {
onward.push(null);
sendGroupMessages(onward, msg);
}
} catch(err) {
node.warn(err);
}
node.previousValue = prop;
if (!repair || !has_parts) {
node.send(onward);
}
else {
send2ports(onward, msg);
}
} catch(err) {
node.warn(err);
}
}
function clear_pending() {
pending_count = 0;
pending_id = 0;
pending_in = {};
pending_out = {};
function clearPending() {
pendingCount = 0;
pendingId = 0;
pendingIn = {};
pendingOut = {};
received = {};
}
var pendingMessages = [];
var activeMessagePromise = null;
var processMessageQueue = function(msg) {
if (msg) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
if (activeMessagePromise !== null) {
// The node is currently processing a message, so do nothing
// more with this message
return;
}
}
if (pendingMessages.length === 0) {
// There are no more messages to process, clear the active flag
// and return
activeMessagePromise = null;
return;
}
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
activeMessagePromise = processMessage(nextMsg,true)
.then(processMessageQueue)
.catch((err) => {
node.error(err,nextMsg);
return processMessageQueue();
});
}
this.on('input', function(msg) {
process_msg(msg, true);
if (node.useAsyncRules) {
processMessageQueue(msg);
} else {
processMessage(msg,true);
}
});
this.on('close', function() {
clear_pending();
clearPending();
});
}

View File

@@ -54,6 +54,10 @@
outputs: 1,
icon: "swap.png",
label: function() {
function prop2name(type, key) {
var result = RED.utils.parseContextKey(key);
return type +"." +result.key;
}
if (this.name) {
return this.name;
}
@@ -70,13 +74,13 @@
} else {
if (this.rules.length == 1) {
if (this.rules[0].t === "set") {
return this._("change.label.set",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
return this._("change.label.set",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
} else if (this.rules[0].t === "change") {
return this._("change.label.change",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
return this._("change.label.change",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
} else if (this.rules[0].t === "move") {
return this._("change.label.move",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
return this._("change.label.move",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
} else {
return this._("change.label.delete",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
return this._("change.label.delete",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
}
} else {
return this._("change.label.changeCount",{count:this.rules.length});
@@ -146,7 +150,7 @@
.appendTo(row2);
var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata']});
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
@@ -154,7 +158,7 @@
.appendTo(row3_1);
var fromValue = $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
.appendTo(row3_1)
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool']});
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']});
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
@@ -162,7 +166,7 @@
.appendTo(row3_2);
var toValue = $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
.appendTo(row3_2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin']});
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to)

View File

@@ -93,47 +93,66 @@ module.exports = function(RED) {
valid = false;
this.error(RED._("change.errors.invalid-expr",{error:e.message}));
}
} else if (rule.tot === 'env') {
rule.to = RED.util.evaluateNodeProperty(rule.to,'env');
}
}
function applyRule(msg,rule) {
try {
var property = rule.p;
var value = rule.to;
if (rule.tot === 'json') {
value = JSON.parse(rule.to);
} else if (rule.tot === 'bin') {
value = Buffer.from(JSON.parse(rule.to))
}
var current;
var fromValue;
var fromType;
var fromRE;
if (rule.tot === "msg") {
value = RED.util.getMessageProperty(msg,rule.to);
} else if (rule.tot === 'flow') {
value = node.context().flow.get(rule.to);
} else if (rule.tot === 'global') {
value = node.context().global.get(rule.to);
} else if (rule.tot === 'date') {
value = Date.now();
} else if (rule.tot === 'jsonata') {
try{
value = RED.util.evaluateJSONataExpression(rule.to,msg);
} catch(err) {
node.error(RED._("change.errors.invalid-expr",{error:err.message}));
return;
}
}
if (rule.t === 'change') {
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
if (rule.fromt === "msg") {
fromValue = RED.util.getMessageProperty(msg,rule.from);
} else if (rule.fromt === 'flow') {
fromValue = node.context().flow.get(rule.from);
} else if (rule.fromt === 'global') {
fromValue = node.context().global.get(rule.from);
function getToValue(msg,rule) {
var value = rule.to;
if (rule.tot === 'json') {
value = JSON.parse(rule.to);
} else if (rule.tot === 'bin') {
value = Buffer.from(JSON.parse(rule.to))
}
if (rule.tot === "msg") {
value = RED.util.getMessageProperty(msg,rule.to);
} else if ((rule.tot === 'flow') ||
(rule.tot === 'global')) {
return new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
});
} else if (rule.tot === 'date') {
value = Date.now();
} else if (rule.tot === 'jsonata') {
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
if (err) {
reject(RED._("change.errors.invalid-expr",{error:err.message}))
} else {
resolve(value);
}
});
});
}
return Promise.resolve(value);
}
function getFromValue(msg,rule) {
var fromValue;
var fromType;
var fromRE;
if (rule.t === 'change') {
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
return new Promise((resolve,reject) => {
if (rule.fromt === "msg") {
resolve(RED.util.getMessageProperty(msg,rule.from));
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
var contextKey = RED.util.parseContextStore(rule.from);
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
if (err) {
reject(err);
} else {
resolve(fromValue);
}
});
}
}).then(fromValue => {
if (typeof fromValue === 'number' || fromValue instanceof Number) {
fromType = 'num';
} else if (typeof fromValue === 'boolean') {
@@ -147,108 +166,163 @@ module.exports = function(RED) {
try {
fromRE = new RegExp(fromRE, "g");
} catch (e) {
valid = false;
node.error(RED._("change.errors.invalid-from",{error:e.message}));
return;
return Promise.reject(new Error(RED._("change.errors.invalid-from",{error:e.message})));
}
} else {
node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}));
return
return Promise.reject(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
}
return {
fromType,
fromValue,
fromRE
}
});
} else {
fromType = rule.fromt;
fromValue = rule.from;
fromRE = rule.fromRE;
}
}
return Promise.resolve({
fromType,
fromValue,
fromRE
});
}
function applyRule(msg,rule) {
var property = rule.p;
var current;
var fromValue;
var fromType;
var fromRE;
try {
return getToValue(msg,rule).then(value => {
return getFromValue(msg,rule).then(fromParts => {
fromValue = fromParts.fromValue;
fromType = fromParts.fromType;
fromRE = fromParts.fromRE;
if (rule.pt === 'msg') {
try {
if (rule.t === 'delete') {
RED.util.setMessageProperty(msg,property,undefined);
} else if (rule.t === 'set') {
RED.util.setMessageProperty(msg,property,value);
} else if (rule.t === 'change') {
current = RED.util.getMessageProperty(msg,property);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
RED.util.setMessageProperty(msg,property,value);
} else {
current = current.replace(fromRE,value);
RED.util.setMessageProperty(msg,property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
RED.util.setMessageProperty(msg,property,value);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
RED.util.setMessageProperty(msg,property,value);
}
}
}
} catch(err) {}
return msg;
} else if (rule.pt === 'flow' || rule.pt === 'global') {
var contextKey = RED.util.parseContextStore(property);
return new Promise((resolve,reject) => {
var target = node.context()[rule.pt];
var callback = err => {
if (err) {
reject(err);
} else {
resolve(msg);
}
}
if (rule.t === 'delete') {
target.set(contextKey.key,undefined,contextKey.store,callback);
} else if (rule.t === 'set') {
target.set(contextKey.key,value,contextKey.store,callback);
} else if (rule.t === 'change') {
target.get(contextKey.key,contextKey.store,(err,current) => {
if (err) {
reject(err);
return;
}
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
target.set(contextKey.key,value,contextKey.store,callback);
} else {
current = current.replace(fromRE,value);
target.set(contextKey.key,current,contextKey.store,callback);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
target.set(contextKey.key,value,contextKey.store,callback);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
target.set(contextKey.key,value,contextKey.store,callback);
}
}
});
}
});
}
});
}).catch(err => {
node.error(err, msg);
return null;
});
} catch(err) {
return Promise.resolve(msg);
}
}
function applyRules(msg, currentRule) {
if (currentRule >= node.rules.length) {
return Promise.resolve(msg);
}
var r = node.rules[currentRule];
var rulePromise;
if (r.t === "move") {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
rulePromise = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt}).then(
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
);
}
else { // 2 step move if we are moving from a child
rulePromise = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt}).then(
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
).then(
msg => applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt})
).then(
msg => applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt})
)
}
} else {
rulePromise = applyRule(msg,r);
}
return rulePromise.then(
msg => {
if (!msg) {
return
} else if (currentRule === node.rules.length - 1) {
return msg;
} else {
fromType = rule.fromt;
fromValue = rule.from;
fromRE = rule.fromRE;
return applyRules(msg, currentRule+1);
}
}
if (rule.pt === 'msg') {
if (rule.t === 'delete') {
RED.util.setMessageProperty(msg,property,undefined);
} else if (rule.t === 'set') {
RED.util.setMessageProperty(msg,property,value);
} else if (rule.t === 'change') {
current = RED.util.getMessageProperty(msg,property);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
RED.util.setMessageProperty(msg,property,value);
} else {
current = current.replace(fromRE,value);
RED.util.setMessageProperty(msg,property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
RED.util.setMessageProperty(msg,property,value);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
RED.util.setMessageProperty(msg,property,value);
}
}
}
}
else {
var target;
if (rule.pt === 'flow') {
target = node.context().flow;
} else if (rule.pt === 'global') {
target = node.context().global;
}
if (target) {
if (rule.t === 'delete') {
target.set(property,undefined);
} else if (rule.t === 'set') {
target.set(property,value);
} else if (rule.t === 'change') {
current = target.get(property);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
target.set(property,value);
} else {
current = current.replace(fromRE,value);
target.set(property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
target.set(property,value);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
target.set(property,value);
}
}
}
}
}
} catch(err) {/*console.log(err.stack)*/}
return msg;
);
}
if (valid) {
this.on('input', function(msg) {
for (var i=0; i<this.rules.length; i++) {
if (this.rules[i].t === "move") {
var r = this.rules[i];
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
msg = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt});
applyRule(msg,{t:"delete", p:r.p, pt:r.pt});
}
else { // 2 step move if we are moving from a child
msg = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt});
applyRule(msg,{t:"delete", p:r.p, pt:r.pt});
msg = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt});
applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt});
}
} else {
msg = applyRule(msg,this.rules[i]);
}
if (msg === null) {
return;
}
}
node.send(msg);
applyRules(msg, 0)
.then( msg => { if (msg) { node.send(msg) }} )
.catch( err => node.error(err, msg))
});
}
}

View File

@@ -295,7 +295,8 @@
For object outputs, once this count has been reached, the node can be configured to send a message for each subsequent message
received.</p>
<p>A <i>timeout</i> can be set to trigger sending the new message using whatever has been received so far.</p>
<p>If a message is received with the <b>msg.complete</b> property set, the output message is sent.</p>
<p>If a message is received with the <b>msg.complete</b> property set, the output message is finalised and sent.
This resets any part counts.</p>
<h4>Reduce Sequence mode</h4>
<p>When configured to join in reduce mode, an expression is applied to each
@@ -415,7 +416,7 @@
$("#node-input-reduceExp").typedInput({types:[jsonata_or_empty]});
$("#node-input-reduceInit").typedInput({
default: 'num',
types:['flow','global','str','num','bool','json','bin','date','jsonata'],
types:['flow','global','str','num','bool','json','bin','date','jsonata','env'],
typeField: $("#node-input-reduceInitType")
});
$("#node-input-reduceFixup").typedInput({types:[jsonata_or_empty]});
@@ -439,10 +440,7 @@
$("#node-input-joiner").typedInput({
default: 'str',
typeField: $("#node-input-joinerType"),
types:[
'str',
'bin'
]
types:['str', 'bin']
});
$("#node-input-property").typedInput({
@@ -451,7 +449,7 @@
});
$("#node-input-key").typedInput({
types:['msg', {value:"merge", label:"", hasValue:false}]
types:['msg']
});
$("#node-input-build").change();

View File

@@ -233,7 +233,7 @@ module.exports = function(RED) {
RED.nodes.registerType("split",SplitNode);
var _max_kept_msgs_count = undefined;
var _max_kept_msgs_count;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
@@ -252,13 +252,29 @@ module.exports = function(RED) {
exp.assign("I", index);
exp.assign("N", count);
exp.assign("A", accum);
return RED.util.evaluateJSONataExpression(exp, msg);
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(exp, msg, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
function apply_f(exp, accum, count) {
exp.assign("N", count);
exp.assign("A", accum);
return RED.util.evaluateJSONataExpression(exp, {});
return new Promise((resolve,reject) => {
return RED.util.evaluateJSONataExpression(exp, {}, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
function exp_or_undefined(exp) {
@@ -269,32 +285,40 @@ module.exports = function(RED) {
return exp
}
function reduce_and_send_group(node, group) {
function reduceAndSendGroup(node, group) {
var is_right = node.reduce_right;
var flag = is_right ? -1 : 1;
var msgs = group.msgs;
var accum = eval_exp(node, node.exp_init, node.exp_init_type);
var reduce_exp = node.reduce_exp;
var reduce_fixup = node.reduce_fixup;
var count = group.count;
msgs.sort(function(x,y) {
var ix = x.parts.index;
var iy = y.parts.index;
if (ix < iy) return -flag;
if (ix > iy) return flag;
return 0;
return getInitialReduceValue(node, node.exp_init, node.exp_init_type).then(accum => {
var reduce_exp = node.reduce_exp;
var reduce_fixup = node.reduce_fixup;
var count = group.count;
msgs.sort(function(x,y) {
var ix = x.parts.index;
var iy = y.parts.index;
if (ix < iy) {return -flag;}
if (ix > iy) {return flag;}
return 0;
});
return msgs.reduce((promise, msg) => promise.then(accum => apply_r(reduce_exp, accum, msg, msg.parts.index, count)), Promise.resolve(accum))
.then(accum => {
if(reduce_fixup !== undefined) {
return apply_f(reduce_fixup, accum, count).then(accum => {
node.send({payload: accum});
});
} else {
node.send({payload: accum});
}
});
}).catch(err => {
throw new Error(RED._("join.errors.invalid-expr",{error:err.message}));
});
for(var msg of msgs) {
accum = apply_r(reduce_exp, accum, msg, msg.parts.index, count);
}
if(reduce_fixup !== undefined) {
accum = apply_f(reduce_fixup, accum, count);
}
node.send({payload: accum});
}
function reduce_msg(node, msg) {
if(msg.hasOwnProperty('parts')) {
var promise;
if (msg.hasOwnProperty('parts')) {
var parts = msg.parts;
var pending = node.pending;
var pending_count = node.pending_count;
@@ -311,66 +335,51 @@ module.exports = function(RED) {
}
var group = pending[gid];
var msgs = group.msgs;
if(parts.hasOwnProperty('count') &&
(group.count === undefined)) {
group.count = count;
if(parts.hasOwnProperty('count') && (group.count === undefined)) {
group.count = parts.count;
}
msgs.push(msg);
pending_count++;
var completeProcess = function() {
node.pending_count = pending_count;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
node.pending = {};
node.pending_count = 0;
var promise = Promise.reject(RED._("join.too-many"));
promise.catch(()=>{});
return promise;
}
return Promise.resolve();
}
if(msgs.length === group.count) {
delete pending[gid];
try {
pending_count -= msgs.length;
reduce_and_send_group(node, group);
} catch(e) {
node.error(RED._("join.errors.invalid-expr",{error:e.message})); }
pending_count -= msgs.length;
promise = reduceAndSendGroup(node, group).then(completeProcess);
} else {
promise = completeProcess();
}
node.pending_count = pending_count;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
node.pending = {};
node.pending_count = 0;
node.error(RED._("join.too-many"), msg);
}
}
else {
} else {
node.send(msg);
}
if (!promise) {
promise = Promise.resolve();
}
return promise;
}
function eval_exp(node, exp, exp_type) {
if(exp_type === "flow") {
return node.context().flow.get(exp);
}
else if(exp_type === "global") {
return node.context().global.get(exp);
}
else if(exp_type === "str") {
return exp;
}
else if(exp_type === "num") {
return Number(exp);
}
else if(exp_type === "bool") {
if (exp === 'true') {
return true;
}
else if (exp === 'false') {
return false;
}
}
else if ((exp_type === "bin") ||
(exp_type === "json")) {
return JSON.parse(exp);
}
else if(exp_type === "date") {
return Date.now();
}
else if(exp_type === "jsonata") {
var jexp = RED.util.prepareJSONataExpression(exp, node);
return RED.util.evaluateJSONataExpression(jexp, {});
}
throw new Error("unexpected initial value type");
function getInitialReduceValue(node, exp, exp_type) {
return new Promise((resolve, reject) => {
RED.util.evaluateNodeProperty(exp, exp_type, node, {},
(err, result) => {
if(err) {
return reject(err);
}
else {
return resolve(result);
}
});
});
}
function JoinNode(n) {
@@ -399,6 +408,7 @@ module.exports = function(RED) {
this.reduce_fixup = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
} catch(e) {
this.error(RED._("join.errors.invalid-expr",{error:e.message}));
return;
}
}
@@ -437,7 +447,8 @@ module.exports = function(RED) {
newArray = newArray.concat(n);
})
group.payload = newArray;
} else if (group.type === 'buffer') {
}
else if (group.type === 'buffer') {
var buffers = [];
var bufferLen = 0;
if (group.joinChar !== undefined) {
@@ -450,7 +461,8 @@ module.exports = function(RED) {
buffers.push(group.payload[i]);
bufferLen += group.payload[i].length;
}
} else {
}
else {
bufferLen = group.bufferLen;
buffers = group.payload;
}
@@ -463,7 +475,8 @@ module.exports = function(RED) {
groupJoinChar = group.joinChar.toString();
}
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
} else {
}
else {
if (node.propertyType === 'full') {
group.msg = RED.util.cloneMessage(group.msg);
}
@@ -471,13 +484,48 @@ module.exports = function(RED) {
}
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
group.msg.parts = group.msg.parts.parts;
} else {
}
else {
delete group.msg.parts;
}
delete group.msg.complete;
node.send(group.msg);
}
var pendingMessages = [];
var activeMessagePromise = null;
// In reduce mode, we must process messages fully in order otherwise
// groups may overlap and cause unexpected results. The use of JSONata
// means some async processing *might* occur if flow/global context is
// accessed.
var processReduceMessageQueue = function(msg) {
if (msg) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
if (activeMessagePromise !== null) {
// The node is currently processing a message, so do nothing
// more with this message
return;
}
}
if (pendingMessages.length === 0) {
// There are no more messages to process, clear the active flag
// and return
activeMessagePromise = null;
return;
}
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
activeMessagePromise = reduce_msg(node, nextMsg)
.then(processReduceMessageQueue)
.catch((err) => {
node.error(err,nextMsg);
return processReduceMessageQueue();
});
}
this.on("input", function(msg) {
try {
var property;
@@ -516,8 +564,7 @@ module.exports = function(RED) {
propertyIndex = msg.parts.index;
}
else if (node.mode === 'reduce') {
reduce_msg(node, msg);
return;
return processReduceMessageQueue(msg);
}
else {
// Use the node configuration to identify all of the group information
@@ -525,7 +572,7 @@ module.exports = function(RED) {
payloadType = node.build;
targetCount = node.count;
joinChar = node.joiner;
if (targetCount === 0 && msg.hasOwnProperty('parts')) {
if (n.count === "" && msg.hasOwnProperty('parts')) {
targetCount = msg.parts.count || 0;
}
if (node.build === 'object') {
@@ -539,7 +586,10 @@ module.exports = function(RED) {
}
else {
if (msg.hasOwnProperty('complete')) {
completeSend(partId);
if (inflight[partId]) {
inflight[partId].msg.complete = msg.complete;
completeSend(partId);
}
}
else {
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
@@ -547,6 +597,7 @@ module.exports = function(RED) {
}
return;
}
if (!inflight.hasOwnProperty(partId)) {
if (payloadType === 'object' || payloadType === 'merged') {
inflight[partId] = {
@@ -554,29 +605,16 @@ module.exports = function(RED) {
payload:{},
targetCount:targetCount,
type:"object",
msg:msg
msg:RED.util.cloneMessage(msg)
};
}
else if (node.accumulate === true) {
if (msg.hasOwnProperty("reset")) { delete inflight[partId]; }
inflight[partId] = inflight[partId] || {
currentCount:0,
payload:{},
targetCount:targetCount,
type:payloadType,
msg:msg
}
if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') {
inflight[partId].payload = [];
}
}
else {
inflight[partId] = {
currentCount:0,
payload:[],
targetCount:targetCount,
type:payloadType,
msg:msg
msg:RED.util.cloneMessage(msg)
};
if (payloadType === 'string') {
inflight[partId].joinChar = joinChar;
@@ -619,19 +657,22 @@ module.exports = function(RED) {
} else {
if (!isNaN(propertyIndex)) {
group.payload[propertyIndex] = property;
group.currentCount++;
} else {
group.payload.push(property);
if (property !== undefined) {
group.payload.push(property);
group.currentCount++;
}
}
group.currentCount++;
}
// TODO: currently reuse the last received - add option to pick first received
group.msg = msg;
group.msg = Object.assign(group.msg, msg);
var tcnt = group.targetCount;
if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; }
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
completeSend(partId);
}
} catch(err) {
}
catch(err) {
console.log(err.stack);
}
});

View File

@@ -17,7 +17,7 @@
module.exports = function(RED) {
"use strict";
var _max_kept_msgs_count = undefined;
var _max_kept_msgs_count;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
@@ -32,30 +32,20 @@ module.exports = function(RED) {
return _max_kept_msgs_count;
}
function eval_jsonata(node, code, val) {
try {
return RED.util.evaluateJSONataExpression(code, val);
}
catch (e) {
node.error(RED._("sort.invalid-exp"));
throw e;
}
}
function get_context_val(node, name, dval) {
var context = node.context();
var val = context.get(name);
if (val === undefined) {
context.set(name, dval);
return dval;
}
return val;
}
// function get_context_val(node, name, dval) {
// var context = node.context();
// var val = context.get(name);
// if (val === undefined) {
// context.set(name, dval);
// return dval;
// }
// return val;
// }
function SortNode(n) {
RED.nodes.createNode(this, n);
var node = this;
var pending = get_context_val(node, 'pending', {})
var pending = {};//get_context_val(node, 'pending', {})
var pending_count = 0;
var pending_id = 0;
var order = n.order || "ascending";
@@ -71,16 +61,15 @@ module.exports = function(RED) {
key_exp = RED.util.prepareJSONataExpression(key_exp, this);
}
catch (e) {
node.error(RED._("sort.invalid-exp"));
node.error(RED._("sort.invalid-exp",{message:e.toString()}));
return;
}
}
var dir = (order === "ascending") ? 1 : -1;
var conv = as_num
? function(x) { return Number(x); }
: function(x) { return x; };
var conv = as_num ? function(x) { return Number(x); }
: function(x) { return x; };
function gen_comp(key) {
function generateComparisonFunction(key) {
return function(x, y) {
var xp = conv(key(x));
var yp = conv(key(y));
@@ -90,74 +79,105 @@ module.exports = function(RED) {
};
}
function send_group(group) {
var key = key_is_exp
? function(msg) {
return eval_jsonata(node, key_exp, msg);
}
: function(msg) {
return RED.util.getMessageProperty(msg, key_prop);
};
var comp = gen_comp(key);
function sortMessageGroup(group) {
var promise;
var msgs = group.msgs;
try {
msgs.sort(comp);
}
catch (e) {
return; // not send when error
}
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
msg.parts.index = i;
node.send(msg);
}
}
function sort_payload(msg) {
var data = RED.util.getMessageProperty(msg, target_prop);
if (Array.isArray(data)) {
var key = key_is_exp
? function(elem) {
return eval_jsonata(node, key_exp, elem);
}
: function(elem) { return elem; };
var comp = gen_comp(key);
if (key_is_exp) {
var evaluatedDataPromises = msgs.map(msg => {
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(key_exp, msg, (err, result) => {
if (err) {
reject(RED._("sort.invalid-exp",{message:err.toString()}));
} else {
resolve({
item: msg,
sortValue: result
})
}
});
})
});
promise = Promise.all(evaluatedDataPromises).then(evaluatedElements => {
// Once all of the sort keys are evaluated, sort by them
var comp = generateComparisonFunction(elem=>elem.sortValue);
return evaluatedElements.sort(comp).map(elem=>elem.item);
});
} else {
var key = function(msg) {
return ;
}
var comp = generateComparisonFunction(msg => RED.util.getMessageProperty(msg, key_prop));
try {
data.sort(comp);
msgs.sort(comp);
}
catch (e) {
return false;
return; // not send when error
}
return true;
promise = Promise.resolve(msgs);
}
return false;
return promise.then(msgs => {
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
msg.parts.index = i;
node.send(msg);
}
});
}
function check_parts(parts) {
if (parts.hasOwnProperty("id") &&
parts.hasOwnProperty("index")) {
return true;
function sortMessageProperty(msg) {
var data = RED.util.getMessageProperty(msg, target_prop);
if (Array.isArray(data)) {
if (key_is_exp) {
// key is an expression. Evaluated the expression for each item
// to get its sort value. As this could be async, need to do
// it first.
var evaluatedDataPromises = data.map(elem => {
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(key_exp, elem, (err, result) => {
if (err) {
reject(RED._("sort.invalid-exp",{message:err.toString()}));
} else {
resolve({
item: elem,
sortValue: result
})
}
});
})
})
return Promise.all(evaluatedDataPromises).then(evaluatedElements => {
// Once all of the sort keys are evaluated, sort by them
// and reconstruct the original message item with the newly
// sorted values.
var comp = generateComparisonFunction(elem=>elem.sortValue);
data = evaluatedElements.sort(comp).map(elem=>elem.item);
RED.util.setMessageProperty(msg, target_prop,data);
return true;
})
} else {
var comp = generateComparisonFunction(elem=>elem);
try {
data.sort(comp);
} catch (e) {
return Promise.resolve(false);
}
return Promise.resolve(true);
}
}
return false;
return Promise.resolve(false);
}
function clear_pending() {
function removeOldestPending() {
var oldest;
var oldest_key;
for(var key in pending) {
node.log(RED._("sort.clear"), pending[key].msgs[0]);
delete pending[key];
}
pending_count = 0;
}
function remove_oldest_pending() {
var oldest = undefined;
var oldest_key = undefined;
for(var key in pending) {
var item = pending[key];
if((oldest === undefined) ||
(oldest.seq_no > item.seq_no)) {
oldest = item;
oldest_key = key;
if (pending.hasOwnProperty(key)) {
var item = pending[key];
if((oldest === undefined) ||
(oldest.seq_no > item.seq_no)) {
oldest = item;
oldest_key = key;
}
}
}
if(oldest !== undefined) {
@@ -166,16 +186,20 @@ module.exports = function(RED) {
}
return 0;
}
function process_msg(msg) {
function processMessage(msg) {
if (target_is_prop) {
if (sort_payload(msg)) {
node.send(msg);
}
sortMessageProperty(msg).then(send => {
if (send) {
node.send(msg);
}
}).catch(err => {
node.error(err,msg);
});
return;
}
var parts = msg.parts;
if (!check_parts(parts)) {
if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
return;
}
var gid = parts.id;
@@ -195,23 +219,32 @@ module.exports = function(RED) {
pending_count++;
if (group.count === msgs.length) {
delete pending[gid]
send_group(group);
sortMessageGroup(group).catch(err => {
node.error(err,msg);
});
pending_count -= msgs.length;
}
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
pending_count -= remove_oldest_pending();
node.error(RED._("sort.too-many"), msg);
} else {
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
pending_count -= removeOldestPending();
node.error(RED._("sort.too-many"), msg);
}
}
}
this.on("input", function(msg) {
process_msg(msg);
processMessage(msg);
});
this.on("close", function() {
clear_pending();
})
for(var key in pending) {
if (pending.hasOwnProperty(key)) {
node.log(RED._("sort.clear"), pending[key].msgs[0]);
delete pending[key];
}
}
pending_count = 0;
});
}
RED.nodes.registerType("sort", SortNode);

View File

@@ -31,6 +31,8 @@
<dl class="message-properties">
<dt>payload<span class="property-type">object | string</span></dt>
<dd>A JavaScript object or JSON string.</dd>
<dt>schema<span class="property-type">object</span></dt>
<dd>An optional JSON Schema object to validate the payload against.</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
@@ -41,6 +43,9 @@
<li>If the input is a JavaScript object it creates a JSON string. The string can optionally be well-formatted.</li>
</ul>
</dd>
<dt>schemaError<span class="property-type">array</span></dt>
<dd>If JSON schema validation fails, the catch node will have a <code>schemaError</code> property
containing an array of errors.</dd>
</dl>
<h3>Details</h3>
<p>By default, the node operates on <code>msg.payload</code>, but can be configured
@@ -53,6 +58,8 @@
receives a String, no further checks will be made of the property. It will
not check the String is valid JSON nor will it reformat it if the format option
is selected.</p>
<p>For more details about JSON Schema you can consult the specification
<a href="http://json-schema.org/latest/json-schema-validation.html">here</a>.</p>
</script>
<script type="text/javascript">

View File

@@ -16,39 +16,102 @@
module.exports = function(RED) {
"use strict";
const Ajv = require('ajv');
const ajv = new Ajv({allErrors: true, schemaId: 'auto'});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
function JSONNode(n) {
RED.nodes.createNode(this,n);
this.indent = n.pretty ? 4 : 0;
this.action = n.action||"";
this.property = n.property||"payload";
this.schema = null;
this.compiledSchema = null;
var node = this;
this.on("input", function(msg) {
var validate = false;
if (msg.schema) {
// If input schema is different, re-compile it
if (JSON.stringify(this.schema) != JSON.stringify(msg.schema)) {
try {
this.compiledSchema = ajv.compile(msg.schema);
this.schema = msg.schema;
} catch(e) {
this.schema = null;
this.compiledSchema = null;
node.error(RED._("json.errors.schema-error-compile"), msg);
return;
}
}
validate = true;
}
var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
if (typeof value === "string") {
if (node.action === "" || node.action === "obj") {
try {
RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
node.send(msg);
if (validate) {
if (this.compiledSchema(msg[node.property])) {
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
}
} else {
node.send(msg);
}
}
catch(e) { node.error(e.message,msg); }
} else {
node.send(msg);
// If node.action is str and value is str
if (validate) {
if (this.compiledSchema(JSON.parse(msg[node.property]))) {
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
}
} else {
node.send(msg);
}
}
}
else if (typeof value === "object") {
if (node.action === "" || node.action === "str") {
if (!Buffer.isBuffer(value)) {
try {
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
node.send(msg);
if (validate) {
if (this.compiledSchema(value)) {
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
}
} else {
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
node.send(msg);
}
}
catch(e) { node.error(RED._("json.errors.dropped-error")); }
}
else { node.warn(RED._("json.errors.dropped-object")); }
} else {
node.send(msg);
// If node.action is obj and value is object
if (validate) {
if (this.compiledSchema(value)) {
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
node.error(`${RED._("json.errors.schema-error")}: ${ajv.errorsText(this.compiledSchema.errors)}`, msg);
}
} else {
node.send(msg);
}
}
}
else { node.warn(RED._("json.errors.dropped")); }

View File

@@ -37,6 +37,8 @@
<dt class="optional">filename <span class="property-type">string</span></dt>
<dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
</dl>
<h3>Output</h3>
<p>On completion of write, input message is sent to output port.</p>
<h3>Details</h3>
<p>Each message payload will be added to the end of the file, optionally appending
a newline (\n) character between each one.</p>
@@ -123,9 +125,8 @@
},
color:"BurlyWood",
inputs:1,
outputs:0,
icon: "file.png",
align: "right",
outputs:1,
icon: "file-out.png",
label: function() {
if (this.overwriteFile === "delete") {
return this.name||this._("file.label.deletelabel",{file:this.filename});
@@ -159,7 +160,7 @@
outputLabels: function(i) {
return (this.format === "utf8") ? "UTF8 string" : "binary buffer";
},
icon: "file.png",
icon: "file-in.png",
label: function() {
return this.name||this.filename||this._("file.label.filelabel");
},

View File

@@ -39,14 +39,20 @@ module.exports = function(RED) {
node.tout = null;
},333);
}
if (filename === "") { node.warn(RED._("file.errors.nofilename")); }
else if (node.overwriteFile === "delete") {
if (filename === "") {
node.warn(RED._("file.errors.nofilename"));
} else if (node.overwriteFile === "delete") {
fs.unlink(filename, function (err) {
if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); }
else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); }
if (err) {
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
} else {
if (RED.settings.verbose) {
node.log(RED._("file.status.deletedfile",{file:filename}));
}
node.send(msg);
}
});
}
else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename);
if (node.createDir) {
try {
@@ -64,15 +70,21 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
node.data.push(Buffer.from(data));
node.data.push({msg:msg,data:Buffer.from(data)});
while (node.data.length > 0) {
if (node.overwriteFile === "true") {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream.on("error", function(err) {
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
});
node.wstream.end(node.data.shift());
(function(packet) {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream.on("error", function(err) {
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
});
node.wstream.on("open", function() {
node.wstream.end(packet.data, function() {
node.send(packet.msg);
});
})
})(node.data.shift());
}
else {
// Append mode
@@ -115,10 +127,17 @@ module.exports = function(RED) {
}
if (node.filename) {
// Static filename - write and reuse the stream next time
node.wstream.write(node.data.shift());
var packet = node.data.shift()
node.wstream.write(packet.data, function() {
node.send(packet.msg);
});
} else {
// Dynamic filename - write and close the stream
node.wstream.end(node.data.shift());
var packet = node.data.shift()
node.wstream.end(packet.data, function() {
node.send(packet.msg);
});
delete node.wstream;
delete node.wstreamIno;
}

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.18.6",
"version": "0.19.4",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -33,35 +33,37 @@
"flow"
],
"dependencies": {
"ajv": "6.5.3",
"basic-auth": "2.0.0",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"cheerio": "0.22.0",
"clone": "2.1.1",
"clone": "2.1.2",
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors": "2.8.4",
"cron": "1.3.0",
"cron": "1.4.1",
"denque": "1.3.0",
"express": "4.16.3",
"express-session": "1.15.6",
"follow-redirects": "1.4.1",
"fs-extra": "5.0.0",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"i18next": "1.10.6",
"https-proxy-agent": "2.2.1",
"i18next": "11.6.0",
"is-utf8": "0.2.1",
"js-yaml": "3.11.0",
"js-yaml": "3.12.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.5.3",
"jsonata": "1.5.4",
"media-typer": "0.3.0",
"memorystore": "1.6.0",
"mqtt": "2.18.0",
"multer": "1.3.0",
"mustache": "2.3.0",
"mqtt": "2.18.8",
"multer": "1.3.1",
"mustache": "2.3.2",
"node-red-node-email": "0.1.*",
"node-red-node-feedparser": "0.1.*",
"node-red-node-feedparser": "^0.1.12",
"node-red-node-rbe": "0.2.*",
"node-red-node-twitter": "0.1.*",
"node-red-node-twitter": "^1.1.0",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.1",
@@ -69,29 +71,30 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.3.3",
"semver": "5.5.0",
"request": "2.88.0",
"semver": "5.5.1",
"sentiment": "2.1.0",
"uglify-js": "3.3.25",
"uglify-js": "3.4.9",
"when": "3.7.8",
"ws": "1.1.5",
"xml2js": "0.4.19"
},
"optionalDependencies": {
"bcrypt": "~1.0.3"
"bcrypt": "~2.0.0"
},
"devDependencies": {
"chromedriver": "^2.33.2",
"grunt": "~1.0.1",
"chromedriver": "^2.41.0",
"grunt": "~1.0.3",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.2.0",
"grunt-cli": "~1.3.1",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~3.3.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-contrib-uglify": "~3.4.0",
"grunt-contrib-watch": "~1.1.0",
"grunt-jsonlint": "~1.1.0",
"grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon": "~0.4.2",
@@ -100,16 +103,16 @@
"grunt-webdriver": "^2.0.3",
"http-proxy": "^1.16.2",
"istanbul": "0.4.5",
"mocha": "^5.1.1",
"mocha": "^5.2.0",
"node-red-node-test-helper": "0.1.7",
"should": "^8.4.0",
"sinon": "1.17.7",
"stoppable": "^1.0.6",
"supertest": "3.0.0",
"wdio-chromedriver-service": "^0.1.1",
"wdio-mocha-framework": "^0.5.11",
"wdio-spec-reporter": "^0.1.3",
"webdriverio": "^4.9.11",
"node-red-node-test-helper": "^0.1.7"
"supertest": "3.1.0",
"wdio-chromedriver-service": "^0.1.3",
"wdio-mocha-framework": "^0.6.2",
"wdio-spec-reporter": "^0.1.5",
"webdriverio": "^4.13.1"
},
"engines": {
"node": ">=4"

133
red/api/admin/context.js Normal file
View File

@@ -0,0 +1,133 @@
/**
* 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 log;
var redNodes;
var util;
var settings;
function exportContextStore(scope,ctx, store, result, callback) {
ctx.keys(store,function(err, keys) {
if (err) {
return callback(err);
}
result[store] = {};
var c = keys.length;
if (c === 0) {
callback(null);
} else {
keys.forEach(function(key) {
ctx.get(key,store,function(err, v) {
if (err) {
return callback(err);
}
if (scope !== 'global' ||
store === redNodes.listContextStores().default ||
!settings.hasOwnProperty("functionGlobalContext") ||
!settings.functionGlobalContext.hasOwnProperty(key) ||
settings.functionGlobalContext[key] !== v) {
result[store][key] = util.encodeObject({msg:v});
}
c--;
if (c === 0) {
callback(null);
}
});
});
}
});
}
module.exports = {
init: function(runtime) {
redNodes = runtime.nodes;
log = runtime.log;
util = runtime.util;
settings = runtime.settings;
},
get: function(req,res) {
var scope = req.params.scope;
var id = req.params.id;
var key = req.params[0];
var availableStores = redNodes.listContextStores();
//{ default: 'default', stores: [ 'default', 'file' ] }
var store = req.query['store'];
if (store && availableStores.stores.indexOf(store) === -1) {
return res.status(404).end();
}
var ctx;
if (scope === 'global') {
ctx = redNodes.getContext('global');
} else if (scope === 'flow') {
ctx = redNodes.getContext(id);
} else if (scope === 'node') {
var node = redNodes.getNode(id);
if (node) {
ctx = node.context();
}
}
if (ctx) {
if (key) {
store = store || availableStores.default;
ctx.get(key,store,function(err, v) {
var encoded = util.encodeObject({msg:v});
if (store !== availableStores.default) {
encoded.store = store;
}
res.json(encoded);
});
return;
} else {
var stores;
if (!store) {
stores = availableStores.stores;
} else {
stores = [store];
}
var result = {};
var c = stores.length;
var errorReported = false;
stores.forEach(function(store) {
exportContextStore(scope,ctx,store,result,function(err) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
res.end(400);
}
return;
}
c--;
if (c === 0) {
if (!errorReported) {
if (stores.length > 1 && scope === 'global') {
}
res.json(result);
}
}
});
})
}
} else {
res.json({});
}
}
}

View File

@@ -19,6 +19,7 @@ var express = require("express");
var nodes = require("./nodes");
var flows = require("./flows");
var flow = require("./flow");
var context = require("./context");
var auth = require("../auth");
var apiUtil = require("../util");
@@ -28,6 +29,7 @@ module.exports = {
flows.init(runtime);
flow.init(runtime);
nodes.init(runtime);
context.init(runtime);
var needsPermission = auth.needsPermission;
@@ -52,6 +54,12 @@ module.exports = {
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
// Context
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(node|flow)/:id",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(node|flow)/:id/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
return adminApp;
}
}

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