Compare commits

..

288 Commits

Author SHA1 Message Date
Dave Conway-Jones
fda95dfc5d csv node: remove xs console.log 2020-04-14 11:02:14 +01:00
Dave Conway-Jones
24eb78d137 add ja translations 2020-04-03 16:55:43 +01:00
Dave Conway-Jones
e969a1c97c Let CSV node only send headers once
(and then reset that on msg.reset)
and also accept msg.columns csv string to set column headers if not specified in node.
And Add tests
2020-04-03 15:54:19 +01:00
Nick O'Leary
fbfc74e5ca [groups] Ensure newly imported nodes have width/height 2020-04-02 11:58:08 +01:00
Nick O'Leary
1b5654001c Merge pull request #2396 from amodelbello/master
Replace pvorb/clone with lodash.clonedeep to prevent errors when cloning complex objects
2020-04-01 20:22:40 +01:00
Nick O'Leary
e0f3e94e2b Merge branch 'dev' into master 2020-04-01 20:22:25 +01:00
Nick O'Leary
5da89892b4 [groups] Draw group selection above all other groups 2020-04-01 14:10:35 +01:00
Nick O'Leary
04da13eaf9 Merge branch 'pr_2523' into dev 2020-03-31 15:59:31 +01:00
Nick O'Leary
7fa4df082e Force sync redraw of view when replacing unknown nodes 2020-03-31 15:58:51 +01:00
Nick O'Leary
ae001c5e82 Merge branch 'master' into dev 2020-03-31 15:35:46 +01:00
Nick O'Leary
e7f942eda7 Update nodeTabMap when replacing unknown nodes 2020-03-31 15:34:48 +01:00
Hiroyasu Nishiyama
fa8236ee2c update for recent change of dev branch 2020-03-31 20:32:07 +09:00
Hiroyasu Nishiyama
08ec04c889 merge upstream/dev 2020-03-31 19:05:22 +09:00
Hiroyasu Nishiyama
e5150ea012 force redraw after node installation 2020-03-31 16:48:20 +09:00
Nick O'Leary
222ece2533 Merge pull request #2493 from node-red/groups
Grouping Nodes
2020-03-30 23:43:27 +01:00
Nick O'Leary
294696daf5 Merge branch 'dev' into groups 2020-03-30 23:42:52 +01:00
Nick O'Leary
d099356207 Merge branch 'master' into dev 2020-03-30 23:42:40 +01:00
Nick O'Leary
5c06761b1a Remove console.log from subflow 2020-03-30 23:42:30 +01:00
Nick O'Leary
05fc3c5eca Merge branch 'master' into dev 2020-03-30 23:41:33 +01:00
Nick O'Leary
9d4e2adde4 Merge pull request #2519 from neohelden/master
German I18n adjustments
2020-03-30 21:44:44 +01:00
Nick O'Leary
a8db3d8dd3 Don't double-sanitize node name in debug sidebar
Fixes #2521
2020-03-29 20:38:05 +01:00
Nick O'Leary
6ae7c51dc5 Check node props when deciding if pasted node can splice links
Fixes #2494
2020-03-29 20:33:15 +01:00
Nick O'Leary
84771f5864 Flows/subflows must preinitialise their context objects
Fixes #2513

If a node inside a subflow accessed its context object in its
constructor, the subflow-instance flow context would not yet
have been created. This would cause a place holder context
to get created on its behalf, but that place holder doesn't
have its parent set properly. This then breaks the usage
of $parent inside such a subflow.

This fix has changed it so flows (and subflows) create their
flow context as part of their initial creation. That ensures
it exists when individual nodes from the subflow are created,
allowing them to safely access their context.

This has also fixed a related issue where any attempt to use
$parent to access beyond the root parent would seemingly hang
as the callback was never being called. This would cause
messages to get stuck in flows. The fix ensures the callback
is used in the root context objects and undefined is returned.
2020-03-27 23:47:12 +00:00
Nick O'Leary
4304d44851 Ensure complete node scope is remapped in subflows
Fixes #2514
2020-03-27 09:44:15 +00:00
Nick O'Leary
1018c0e8a5 Handle false values in $env() properly
Fixes 2517
2020-03-27 09:05:58 +00:00
Nick O'Leary
b1d0013214 [groups] Better ordering of group elements on the DOM 2020-03-26 22:51:06 +00:00
Nick O'Leary
94ef25bbb9 [groups] i18n group messages 2020-03-26 22:50:46 +00:00
Nick O'Leary
13830ffc9c [groups] Tidy up Info sidebar summary of group selection 2020-03-26 21:00:22 +00:00
Nick O'Leary
e0bef941b4 [groups] Include groups when copying whole tabs 2020-03-26 20:26:58 +00:00
Nick O'Leary
03e9522d98 [groups] Include groups when exporting 2020-03-26 18:01:57 +00:00
Nick O'Leary
1bdbd31b96 [groups] Overhaul group drag handling for empty groups 2020-03-26 15:27:34 +00:00
Nick O'Leary
ef9db701f8 [groups] Add default group style to css so can be themed 2020-03-26 15:25:08 +00:00
Nick O'Leary
afb564a4fc [groups] Add copy/paste group style actions 2020-03-26 15:24:02 +00:00
Nick O'Leary
3e7f58dedd [groups] Include group counts in copy/paste notifications 2020-03-26 15:22:59 +00:00
Pascal Stech
e46d8345db German I18n adjustments 2020-03-26 15:39:32 +01:00
Nick O'Leary
2e364b6d9a Merge pull request #2516 from alexk111/patch-1
Remove duplicate
2020-03-25 17:53:12 +00:00
Alex Kaul
b4177836a8 Remove duplicate 2020-03-26 00:42:01 +07:00
Nick O'Leary
5b2ee21204 Merge pull request #2506 from node-red-hitachi/fix-subflow-template-tab
fix tab appearance of subflow template panel
2020-03-24 15:15:21 +00:00
Nick O'Leary
9b6e798eb6 Merge pull request #2502 from kazuhitoyokoi/master-updatemsgcatalog4websocketnode
Add Japanese translation to node property of websocket node
2020-03-24 15:14:17 +00:00
Nick O'Leary
7c91c4ae5a [groups] Prevent subflow port nodes being added group 2020-03-24 14:05:35 +00:00
Nick O'Leary
7bc3b662e4 [groups] Fix up various delete/undo actions with groups 2020-03-24 14:05:09 +00:00
Nick O'Leary
64af1f7e9b [groups] Lasso should select top-most group of selection 2020-03-23 22:04:40 +00:00
Nick O'Leary
f0038e9796 [groups] Use requestAnimationFrame for view redraw
This moves the expensive redraw code out of the event handling phase
and onto the browser's repaint phase. This makes the event handling
more responsive, particularly when dragging a large number of nodes.

It also removes lots of unnecessary anonymous functions in the redraw
code that should also improve performance.
2020-03-23 21:30:52 +00:00
Nick O'Leary
768aa4ac92 [groups] getGroupAt should return top most group 2020-03-23 21:30:09 +00:00
Nick O'Leary
f61c137ea3 [groups] Improve styling of group selection/highlight 2020-03-23 14:51:18 +00:00
Nick O'Leary
20a8059758 [groups] Add style options for group label 2020-03-20 20:00:03 +00:00
Nick O'Leary
58696c6ad4 [groups] Add better colour picker for group fill/stroke 2020-03-19 13:41:54 +00:00
Dave Conway-Jones
b5ed018bae csv node - add tests for blank columns and null values 2020-03-18 16:18:16 +00:00
tmdoit
91b7dd988e [CSV node] Add support for parsing empty strings and null values (#2510)
* [CSV node] Add support for parsing empty strings and null values

* Add new lines at the end and fix script type.

* Last one script type fix

* Naming change
2020-03-18 15:54:10 +00:00
Dave Conway-Jones
b0c3c78899 MQTT out - Add warning if topic contains + or #
still sends msg as it causes connection to bounce which may be useful and dropping it would be a change in behaviour.
2020-03-18 15:47:03 +00:00
Kazuhito Yokoi
282f00e091 Add Japanese translation for join node (#2508) 2020-03-17 21:36:23 +00:00
Nick O'Leary
5cd2791506 [groups] Add groups to flow util unit tests 2020-03-16 23:01:57 +00:00
Nick O'Leary
9b2e9ec41a [groups] Support undo of convert to subflow from inside group 2020-03-16 22:51:54 +00:00
Nick O'Leary
a8bc753720 [groups] Show group info when selected in sidebar 2020-03-16 11:16:18 +00:00
Nick O'Leary
266df86d98 [groups] Add menu options for group actions 2020-03-16 10:20:48 +00:00
Dave Conway-Jones
85a1f59a93 Fix join to not crash on appending invalid tyoes to buffer.
Add extra info to clarify use of complete
to Close #2505
2020-03-15 16:43:32 +00:00
Dave Conway-Jones
43258ee816 Trigger node - reset default timeout value when switcing away from wait for reset 2020-03-15 15:11:19 +00:00
Hiroyasu Nishiyama
c4ca0b6e91 fix tab apperance of subflow template panel 2020-03-15 08:02:26 +09:00
Nick O'Leary
1bf3b3077e [groups] Include groups when converting selection to subflow 2020-03-14 00:17:16 +00:00
Nick O'Leary
c9194c3635 [groups] Fix undo/redo handling of addTo/removeFrom group 2020-03-13 23:09:18 +00:00
Nick O'Leary
27c462fee9 [groups] Support dragging node from palette into group 2020-03-13 23:01:19 +00:00
Nick O'Leary
7886e5d57c [groups] Add undo support for group actions 2020-03-13 23:01:01 +00:00
Nick O'Leary
6912dec166 Merge pull request #2479 from node-red-hitachi/dev-admin-api-auth
Add admin api authentication function
2020-03-13 13:36:53 +00:00
Nick O'Leary
b8e610e1b6 Merge pull request #2500 from tilleul/patch-3
Support for context stores using JSONata and evaluateNodeProperty()
2020-03-13 13:21:26 +00:00
Kazuhito Yokoi
421b5846f2 Add page objects for UI testing (#2501)
* Update page object of change node

* Support multiple node outputs in UI testing

* Add page object of switch node

* Add page objects of trigger and exec nodes

* Remove unnecessary code

* Update page object of trigger node to select time unit

* Add page objects of websocket nodes

* Support boolean as value in selectWithWait()

* Update page object of split node

* Merge page objects of mqtt nodes to make them same as original mqtt node file path
2020-03-13 13:20:16 +00:00
Nick O'Leary
6a30f2cbc8 Merge pull request #2503 from kazuhitoyokoi/master-jpn4filenode
Remove old leagcy wording from file node info (Japanese and Chinese)
2020-03-13 13:19:53 +00:00
Nick O'Leary
a8b1e91843 Merge pull request #2504 from bonanitech/patch-3
Fix paletteCategories order
2020-03-13 13:11:41 +00:00
Nick O'Leary
20f97d0d13 Add better handling of host-key-verify error with projects 2020-03-13 13:09:47 +00:00
Mauricio Bonani
4c78f06c2b Fix paletteCategories order 2020-03-13 08:44:56 -04:00
Kazuhito Yokoi
c700d5c922 Remove old leagcy wording from file node info (Chinese) 2020-03-13 21:38:23 +09:00
Kazuhito Yokoi
a9508a2c04 Remove old leagcy wording from file node info (Japanese) 2020-03-13 21:31:16 +09:00
Dave Conway-Jones
09d55a0cbd remove unneeded title line from file info text 2020-03-13 11:33:37 +00:00
Dave Conway-Jones
b165129388 Remove old leagcy wording from file node info to stop confusing users. 2020-03-13 11:28:19 +00:00
Nick O'Leary
0ef3471f8f [groups] Add undo of group import 2020-03-13 11:27:13 +00:00
Dave Conway-Jones
9ba9998bd6 make exec node logging consistent with itself. (only be verbose when in verbose mode) 2020-03-13 11:26:49 +00:00
Kazuhito Yokoi
72126730ef Remove unnecessary code for node property of websocket node in the German language 2020-03-13 16:32:00 +09:00
Kazuhito Yokoi
fd2213232c Update message catalogs for other languages 2020-03-13 16:29:16 +09:00
Kazuhito Yokoi
369c5754f2 Add Japanese translation to node property of websocket node 2020-03-13 16:26:48 +09:00
Nick O'Leary
fc3d0ab053 [groups] Keep groups ordered by depth in DOM 2020-03-09 15:10:54 +00:00
Nick O'Leary
1c63d7ff31 Merge pull request #2489 from kazuhitoyokoi/master-fixuitest
Fix XPath in page objects for UI tests
2020-03-09 11:17:49 +00:00
Nick O'Leary
de971fa53f Merge pull request #2487 from bonanitech/patch-1
Fix workspace CSS properties syntax
2020-03-09 11:17:14 +00:00
Nick O'Leary
d005eb46cf Merge pull request #2488 from bonanitech/patch-2
Consolidate duplicate selectors
2020-03-09 11:16:49 +00:00
Nick O'Leary
d1dd7d1d51 [groups] Support copy/paste/import/export of groups 2020-03-09 11:14:18 +00:00
tilleul
b78ef006ec Support for context stores using JSONata and evaluateNodeProperty()
The function prepareJSONataExpression() does not take context store into account.
This causes problems when using typedInput fields and getting the value of a property based on its type using evaluateNodeProperty().
2020-03-09 11:22:20 +01:00
Nick O'Leary
9a0c843f29 [groups] Support deleting groups as part of selection 2020-03-05 22:49:31 +00:00
Nick O'Leary
4d96d95370 [groups] Add merge-selection-to-group and remove-selection-from-group 2020-03-05 15:52:26 +00:00
Nick O'Leary
51ea5dc342 [groups] Add ungroup-selection action 2020-03-05 10:43:28 +00:00
Nick O'Leary
97d58e34f2 [groups] Support nested groups in editor 2020-03-04 21:48:38 +00:00
Nick O'Leary
86ce5c591b [groups] add basic group functionality to editor 2020-03-03 20:55:01 +00:00
Dave Conway-Jones
5090b01b8e Ensure join node handles missing buffer joiner when not in string mode
and add tests
to close #2491
2020-03-02 19:50:39 +00:00
Kazuhito Yokoi
491812fac5 Fix XPath in UI tests 2020-03-02 05:07:48 +00:00
Mauricio Bonani
8a82552bdc Consolidate duplicates 2020-02-29 15:14:57 -05:00
Mauricio Bonani
bd4fc2e5cc Fix workspace CSS properties syntax 2020-02-29 09:15:42 -05:00
Nick O'Leary
32aa4c41ce Bump for 1.0.4 2020-02-27 14:37:25 +00:00
Nick O'Leary
2a6bedbd8d update changelog 2020-02-27 11:38:44 +00:00
KAZUHIRO ITO
83942c2551 Fix plugin only receives the actual token 2020-02-27 19:55:21 +09:00
KAZUHIRO ITO
458d794f52 Fix tokensStrategy order 2020-02-27 19:41:59 +09:00
KAZUHIRO ITO
95982ad464 Update adminAuth tokensStrategy test spec 2020-02-27 19:20:08 +09:00
Nick O'Leary
cc5fdd9844 Avoid adding extra divs to edit form to avoid size miscalculation 2020-02-26 13:17:03 +00:00
Nick O'Leary
d09ee6611f Update dependencies 2020-02-26 11:37:37 +00:00
KAZUHIRO ITO
bba6855872 Add admin api authentication function 2020-02-26 12:59:40 +09:00
Nick O'Leary
43970b404e Update github templates 2020-02-25 23:06:14 +00:00
Dave Conway-Jones
1868289b71 Better fix for trigegr 2nd message in last payload mode
Now works correctly in multiple topics mode.
And update tests
2020-02-25 22:15:53 +00:00
Nick O'Leary
c9ad5bea93 Merge branch 'dev' of github.com:node-red/node-red into dev 2020-02-25 15:09:26 +00:00
Nick O'Leary
a09b3bb6c7 Merge branch 'dev' into pr_2242 2020-02-25 14:32:33 +00:00
Nick O'Leary
3d6170be5e Merge pull request #2475 from kazuhitoyokoi/master-adduitest
Add UI test case for error handling
2020-02-25 14:10:38 +00:00
Kazuhito Yokoi
00477fd67a Add UI test case for error handling 2020-02-25 19:56:48 +09:00
Kazuhito Yokoi
21c57f968a Add page object code for nodes 2020-02-25 19:02:46 +09:00
Kazuhito Yokoi
f7d2314d64 Add page object code for split node and remove duplicated code 2020-02-25 19:01:17 +09:00
Kazuhito Yokoi
5ecf8c83db Support to input JSON path in debug node property 2020-02-25 18:46:02 +09:00
Nick O'Leary
608834eafb Ensure IPv6 broker names are wrapped in brackets
Fixes #2462
2020-02-24 21:52:07 +00:00
Dave Conway-Jones
1fd4b2b9fc join node - check existance before clearing timeout 2020-02-24 21:31:01 +00:00
Dave Conway-Jones
01a143cd5a Emsure trigger complete 2nd msg when set to send latest
and add test
to close #2474
2020-02-24 21:28:40 +00:00
Dave Conway-Jones
6321b21a1a Merge branch 'master' of https://github.com/node-red/node-red 2020-02-24 21:19:54 +00:00
Dave Conway-Jones
8405826fab Ensure trigger sends complete 2nd msg if set to send latest msg
and add test
to close #2474
2020-02-24 21:17:54 +00:00
Nick O'Leary
22de8855c1 Handle httpAdminRoot missing ending slash with login strategy
Fixes #2473
2020-02-24 21:08:29 +00:00
Nick O'Leary
1830478ec3 Merge pull request #2461 from node-red/set-flow-with-creds
Allow credentials to be provided as part of /flows api
2020-02-24 16:22:57 +00:00
Nick O'Leary
6d98b93135 Merge pull request #2464 from kazuhitoyokoi/master-adduitests
Add UI test cases for data formats
2020-02-24 16:20:13 +00:00
Nick O'Leary
54978e4d64 Merge pull request #2466 from node-red-hitachi/update-message-catalogue
Update message catalogue for SUBFLOW UI
2020-02-24 16:19:59 +00:00
Nick O'Leary
9d567d61fe Merge pull request #2470 from mknj/master
bump https-proxy-agent
2020-02-24 16:19:17 +00:00
Nick O'Leary
79feb691bd Add regex awareness to jsonata formatter 2020-02-24 16:08:58 +00:00
Nick O'Leary
e16fe1e6a5 Add better regex highlighting in jsonata edit mode
Fixes #2465
2020-02-24 13:27:42 +00:00
Nick O'Leary
04d3981921 Bump to jsonata 1.8.1 2020-02-24 11:42:56 +00:00
Nick O'Leary
40c3099e4e Avoid adding extra newlines when formating jsonata
Fixes #2472
2020-02-24 11:41:27 +00:00
Nick O'Leary
3f86fd7176 Upgrade to latest marked and dompurify libs 2020-02-24 11:22:47 +00:00
mknj
9e6bc46540 bump https-proxy-agent
fixes #2469

this is a major version bump of https-proxy-agent, because they set engine to >6 and did some refactoring, which is ok for node-red.

all tests pass.
2020-02-21 07:33:05 +01:00
Dave Conway-Jones
5e892f222b clarify tcp node text re blank parameters. 2020-02-19 16:40:07 +00:00
Hiroyasu Nishiyama
2da1554caa update message catalogue for subflow UI 2020-02-18 21:38:32 +09:00
Kazuhito Yokoi
a53d0c091e Merge split and join node objects 2020-02-17 19:03:45 +09:00
Kazuhito Yokoi
f88bfa059d Make scenario structures same as cookbook 2020-02-17 14:17:13 +09:00
Kazuhito Yokoi
2e38999506 Add UI test cases for data formats 2020-02-17 13:57:01 +09:00
Kazuhito Yokoi
42b841cb78 Update XPath to the latest 2020-02-17 13:29:06 +09:00
Nick O'Leary
e3dab3cf20 Ensure catalog load errors are logged to the console 2020-02-14 16:14:52 +00:00
Nick O'Leary
569b9f3d06 Track context sidebar element paths to track formatting changes
Fixes #2460
2020-02-13 22:39:59 +00:00
Nick O'Leary
d6b5494625 Allow credentials to be provided as part of /flows api 2020-02-13 16:44:48 +00:00
Nick O'Leary
f76edf74f9 Merge pull request #2454 from node-red/send-metric-fix
Move receive metric position to better reflect async changes
2020-02-13 10:08:24 +00:00
Kazuhito Yokoi
5c199d3bb4 Fix garbled characters in library (#2457)
* update getFileBody

* add suitable unit tests

Co-authored-by: Hiroyuki Okada <ok.okada.hiroyuki@gmail.com>
2020-02-12 16:35:33 +00:00
Nick O'Leary
634a51635c Battling Chrome Autocomplete, part 31: Wrap search input with form 2020-02-10 18:56:03 +00:00
Nick O'Leary
4f9395e881 Merge pull request #2455 from node-red-hitachi/core-node-example
Add support of example flows for core node
2020-02-10 15:17:14 +00:00
Nick O'Leary
8035531a27 Merge pull request #2368 from node-red/subflow-cred-props
Add credential-type to subflow env properties
2020-02-10 11:33:42 +00:00
Nick O'Leary
cc177533e8 Dont export subflow template creds by default 2020-02-10 11:28:56 +00:00
Nick O'Leary
cd210d9fbf Add support for credential-stored env var in subflow 2020-02-10 11:28:56 +00:00
Nick O'Leary
87b9b56b65 Merge pull request #2367 from node-red/cred-typedInput
Add credential type to TypedInput
2020-02-10 11:17:10 +00:00
Nick O'Leary
bffcaa1c17 Refocus credential typedInput when hide/show button clicked 2020-02-10 11:16:19 +00:00
Nick O'Leary
33cbb2ada8 Fixup typedInput cred css 2020-02-10 11:09:49 +00:00
Nick O'Leary
d08e77cf36 Add credential type to TypedInput 2020-02-10 11:09:49 +00:00
Nick O'Leary
1f8ed9dcb9 Merge branch 'master' into dev 2020-02-10 11:07:50 +00:00
Nick O'Leary
53b127902c Merge pull request #2458 from node-red-hitachi/master-zhtw
complete traditional chinese translation
2020-02-10 10:46:50 +00:00
JIYE YU
389cbf4900 complete traditional chinese translation 2020-02-10 11:31:37 +09:00
Nick O'Leary
80d100f3f9 Move receive metric position to better reflect async changes
Fixes #2444
2020-02-07 16:49:41 +00:00
Nick O'Leary
a05589c5a6 Filter palette using raw label not html formatted label
Fixes #2409
2020-02-07 16:31:59 +00:00
Nick O'Leary
7d32636133 Improve file store error when cache disabled and sync api used
Closes #2406
2020-02-07 14:26:30 +00:00
Nick O'Leary
3db5f928ee Wrap long context values when displaying in sidebar
Fixes #2400
2020-02-07 13:59:08 +00:00
Nick O'Leary
797da3bc8e Fix duplicating array item in visual json editor 2020-02-07 10:46:04 +00:00
Nick O'Leary
1e8d695311 Merge pull request #2453 from node-red-hitachi/master-zhcn
Complete zh-CN translation for Editor-client/Nodes/Runtime
2020-02-07 10:15:55 +00:00
JIYE YU
00eb474e02 new zh-CN translation for runtime 2020-02-07 18:27:06 +09:00
JIYE YU
ad6104baeb wrap up ch-ZN translation for nodes message.json 2020-02-07 17:58:20 +09:00
JIYE YU
cd552ab202 wrap up ch-ZN translation for editor-client files 2020-02-07 17:57:37 +09:00
Nick O'Leary
bbd471ad93 Trick chrome into autofilling dummy username/password inputs
Fixes #2445

Continuing the arms race against Chrome's war on developers getting
to choose if a form should be autocompleted or not.

The honey-pot username/password fields we already had were being
ignored. This is because they were hidden.

This fix does three things:

 - unhides the honey-pot inputs, but moves them offscreen so they won't be seen
 - gives them dummy id's so Chrome thinks they are username/password fields
 - updates our autocomplete setting to be the standards-compliant 'off' for all
  the other browsers who adhere to the standard
2020-02-06 15:36:23 +00:00
Nick O'Leary
0f1ca1c7cf cloneMessage should handle undefined without throwing err
Fixes #2399
2020-02-06 10:05:32 +00:00
Nick O'Leary
62fc554d25 Merge pull request #2431 from kazuhitoyokoi/master-removenamespace
Remove unnecessary namespaces for i18n
2020-02-06 09:25:38 +00:00
Nick O'Leary
84dc34e68f Merge pull request #2451 from kazuhitoyokoi/translations4japanese
Add Japanese translations for i18n
2020-02-06 09:22:14 +00:00
Kazuhito Yokoi
0bb77bfa7f Add Japanese translations for i18n 2020-02-06 16:14:09 +09:00
Nick O'Leary
b6702a0c3b Modify history sidebar button positioning to handle long labels
Fixes #2338
2020-02-05 19:48:24 +00:00
Nick O'Leary
a781a1dd4d Merge pull request #2440 from kazuhitoyokoi/master-supportbrowserstack
Support BrowserStack in UI testing
2020-02-05 15:40:03 +00:00
Nick O'Leary
d771527f77 Add some auto-complete snippets to the nrjavascript mode
Close #2438
2020-02-05 15:11:18 +00:00
Nick O'Leary
3d9945b60c Update to JSONata 1.8 2020-02-05 14:44:39 +00:00
Nick O'Leary
5897045f24 Ignore disabled nodes when checking for invalid configs on deploy
Closes #2430
2020-02-05 14:26:55 +00:00
Nick O'Leary
be3dd63360 Merge pull request #2442 from node-red-hitachi/master-zhcn
Add zn-CN translation for all nodes
2020-02-05 10:59:05 +00:00
Nick O'Leary
f951fe6939 Merge pull request #2449 from Tscherno/patch-1
Add HEAD as Method
2020-02-04 20:23:39 +00:00
Hiroyasu Nishiyama
272fbc0cb0 add examples of batch node 2020-02-04 09:45:23 +09:00
Hiroyasu Nishiyama
36bf2a3c38 add support for examples of core nodes 2020-02-03 12:59:12 +09:00
Tscherno
663ed9833a Add HEAD as Method 2020-02-01 17:21:33 +01:00
JIYE YU
c03abdb5e7 add zn-CN translation for nodes: network, parsers, sequence 2020-01-31 11:32:23 +09:00
JIYE YU
6d3eb7bb4b fix translated doc according to comments 2020-01-31 11:32:23 +09:00
JIYE YU
7ffd37d9cb add zn-CN translation for nodes:common,function,storage 2020-01-31 11:32:23 +09:00
Jiye Yu
504d13943d wrap up the zh-tw UI translation (#2443)
* zh-tw UI translation: complement the rest part

* fix translated sentence according to comments
2020-01-30 12:40:44 +00:00
Nick O'Leary
59b1466e5d Merge pull request #2235 from 3anology/master
UI Translated to Traditional Chinese
2020-01-28 21:45:41 +00:00
Nick O'Leary
6b52206186 Merge branch 'master' into dev 2020-01-22 11:54:24 +00:00
Nick O'Leary
9d4238e5cc Re-enable jshint on editor and fixup issues 2020-01-22 11:53:41 +00:00
Kazuhito Yokoi
c16c119a7d Remove unnecessary namespaces for i18n 2020-01-21 20:13:34 +09:00
Kazuhito Yokoi
b49835c72f Support BrowserStack in UI testing 2020-01-21 09:49:19 +09:00
Nick O'Leary
ee6f6ae391 Fixup debug tests 2020-01-17 20:57:13 +00:00
Nick O'Leary
95a51aafdc Add path property to debug messages
Fixes #2358

This property can be used to identify the full path to the node that
logged a given message. If the node is inside a subflow (and maybe
nested many levels deep), this path can be used to help find the
node, rather than just the top-level subflow instance node.

A side-effect of this change is the Debug sidebar is now able to
show the message tools for a message coming from a deeply nested
subflow
2020-01-17 16:56:30 +00:00
Nick O'Leary
5e7cd79ed9 Merge pull request #2392 from kazuhitoyokoi/master-fixjsoneditor
Add icons and support i18n in typedInput of JSON editor
2020-01-17 10:38:23 +00:00
Nick O'Leary
aba6173e23 Merge pull request #2411 from kazuhitoyokoi/master-fixeditor
Fix IME bug in text editor
2020-01-17 10:36:43 +00:00
Nick O'Leary
468beee045 Merge pull request #2381 from node-red/keyboard-nav
Scroll the view with WASD/Cursor keys when nothing selected
2020-01-17 10:36:05 +00:00
Nick O'Leary
70ad66bcff Merge pull request #2425 from kazuhitoyokoi/dev-fixnodehtml
Change types from text/x-red to text/html in node html files
2020-01-17 10:35:28 +00:00
Nick O'Leary
e2c3b35391 Merge pull request #2408 from onozuka/remove-runtime-deploy-event
UI-test: fix memory leak. runtime-deploy event in deploy()
2020-01-17 10:21:34 +00:00
Nick O'Leary
448de23f59 Merge pull request #2426 from kazuhitoyokoi/master-fixuitest
Improvements of UI testing
2020-01-17 10:18:05 +00:00
Kazuhito Yokoi
74a015c329 Change types from text/x-red to text/html in node html files 2020-01-15 11:40:48 +09:00
Kazuhito Yokoi
44a07c74fd Click tab instead of workspace 2020-01-15 10:42:59 +09:00
Kazuhito Yokoi
0f8af4ba1c Use boolean value as flag instead of string value 2020-01-14 20:36:10 +09:00
Kazuhito Yokoi
214d788029 Use enter key to open node property instead of double click 2020-01-14 19:24:50 +09:00
Kazuhito Yokoi
530bf22bd5 Use palette search when selecting nodes 2020-01-14 18:02:27 +09:00
Kazuhito Yokoi
ccc98370eb Use ephemeral port number for MQTT broker 2020-01-14 17:44:56 +09:00
Kazuhito Yokoi
7640bc029c Add handling to remove unused file and directories in UI testing 2020-01-14 17:24:16 +09:00
Kazuhito Yokoi
3f72eb51a0 Fix shortcut key handling 2020-01-14 17:18:46 +09:00
Kazuhito Yokoi
8801ace247 Remain .payload in debug node property 2020-01-14 16:56:39 +09:00
Kazuhito Yokoi
faf46e4447 Improve node files for UI testing 2020-01-14 16:37:43 +09:00
Kazuhito Yokoi
63978e226b Move node files for UI testing to v1.0 directory structures 2020-01-14 15:42:56 +09:00
Nick O'Leary
b96164d4f5 Fix false change detection when no config node selected 2020-01-10 14:52:20 +00:00
Kazuhito Yokoi
944070dfb1 Move CSS definition to ace.scss 2020-01-09 13:06:39 +09:00
Kazuhito Yokoi
f0584df1d0 Revert "Fix IME bug in text editor"
This reverts commit 9cbd0fceea.
2020-01-09 12:53:39 +09:00
Nick O'Leary
ba209c2bdd Merge pull request #2417 from node-red-hitachi/fix-error-on-install
Remove undefined loadFlowLibrary call
2020-01-08 14:43:19 +00:00
Nick O'Leary
c6e2f28b97 Merge pull request #2419 from boisei0/fix-2418-regex-routes
Fixes the routes described in #2418
2020-01-08 14:43:00 +00:00
Arlena Derksen
2436bb0128 Fixes the routes described in #2418 regarding too broad matching of regex URLs. 2020-01-03 21:27:35 +01:00
Dave Conway-Jones
9c4640e010 Make min-height for change, switch, batch and mqtt consistent
to allow fit to smaller screens before scrolling
(to fix comment from forum)
2020-01-01 21:32:56 +00:00
Hiroyasu Nishiyama
1ee43113b1 remove undefined loadFlowLibrary call 2019-12-31 20:22:13 +09:00
Dave Conway-Jones
902f477ee3 Bump included nodes versions to mathc latest
rbe, tail (and sentiment).
To close #2415
2019-12-30 15:46:08 +00:00
Dave Conway-Jones
9c1d46ff92 Make node highlighting a bit more obvious for busy flows 2019-12-26 16:53:13 +00:00
Kazuhito Yokoi
9cbd0fceea Fix IME bug in text editor 2019-12-17 21:00:50 +09:00
Yukio Onozuka
b22a4f94ab when call event. remove runtime event. 2019-12-13 07:52:34 +09:00
Amo DelBello
14c2005bbc Merge pull request #1 from node-red/master
node-red/node-red changes
2019-12-09 14:54:53 -07:00
Nick O'Leary
a4c351fd4f Merge pull request #2397 from kazuhitoyokoi/master-fixuitest
Fix test cases for UI
2019-12-03 10:33:05 +00:00
Kazuhito Yokoi
a364d4950d Fix test cases for UI 2019-12-02 12:43:13 +09:00
Amo DelBello
d017dd75cd Remove 'clone' from util 2019-11-29 20:15:56 -07:00
Amo DelBello
021df83c3f Replace 'clone' with 'lodash.clonedeep' 2019-11-29 18:50:30 -07:00
Kazuhito Yokoi
7805974736 Add icons and support i18n in typedInput of JSON editor 2019-11-25 16:40:42 +09:00
Nick O'Leary
c1dae95f71 Merge pull request #2390 from node-red-hitachi/fix-mqtt-jp
Update Japanese info text of mqtt out node
2019-11-22 22:29:35 +00:00
Hiroyasu Nishiyama
e7c2ff3bd2 fix typos in Japanese info text of mqtt out 2019-11-22 13:01:50 +09:00
Hiroyasu Nishiyama
25459b52a1 udate Japanese info text of mqtt 2019-11-22 12:47:36 +09:00
Nick O'Leary
d45274494d Merge branch 'master' into dev 2019-11-21 21:57:58 +00:00
Nick O'Leary
b81be8f358 Update for 1.0.3 again 2019-11-21 17:28:28 +00:00
Nick O'Leary
aa6c0b9d6e Add clarification that MQTT Out requires payload to send msg
Fixes #2370
2019-11-21 16:56:01 +00:00
Nick O'Leary
64580237d5 Remove rogue debug 2019-11-21 11:08:15 +00:00
Nick O'Leary
b93165592e Merge pull request #2372 from btsimonh/proxy-support
noproxy support in mqtt, Proxy & noproxy support in websocket
2019-11-21 10:45:23 +00:00
Nick O'Leary
83c1e44925 Merge pull request #2371 from btsimonh/projects-flowsFile-absolute
flows file absolute path plus non-existent folder creation.
2019-11-21 10:44:20 +00:00
Nick O'Leary
3088115aba Avoid unnecessary i18n catalog load if base lng provided 2019-11-21 10:42:40 +00:00
Nick O'Leary
fc93e502b8 Merge pull request #2376 from tt92/master
I18n - language fallback behaviour
2019-11-21 10:38:21 +00:00
Nick O'Leary
e90e6eaac3 Merge pull request #2388 from node-red/subflow-status-i18n
let subflow add node-red context to status so i18n works
2019-11-21 09:59:40 +00:00
Dave Conway-Jones
2f4dcba54d let subflow add node-red context to status
For all those nodes thaht don't specify it.
So that subflow status then works ok
2019-11-20 06:12:33 -05:00
tot92
683c6a748e switched loading order 2019-11-18 20:45:48 +01:00
Simon Hailes
175a871ee0 repalce for-in with 'normal' for loop in mqtt and http 2019-11-18 17:14:38 +00:00
btsimonh
b4e2061e85 Update packages/node_modules/@node-red/nodes/core/network/21-httprequest.js
Co-Authored-By: Nick O'Leary <nick.oleary@gmail.com>
2019-11-18 17:03:59 +00:00
Nick O'Leary
2aef99c440 Merge pull request #2362 from node-red-hitachi/i18n-json-editor
Update support of i18n of visual JSON editor
2019-11-18 11:32:19 +00:00
Nick O'Leary
6c125e125f Merge pull request #2383 from kazuhitoyokoi/master-fixpermission
Revert permission of script file
2019-11-18 11:29:43 +00:00
Nick O'Leary
88cbc32abc Fix inverse of 'replace' editor event 2019-11-14 19:13:23 +00:00
Kazuhito Yokoi
8f45e8f84a Revert permission of script file 2019-11-14 12:38:54 +09:00
Hiroyasu Nishiyama
21635aadfe use type name from common.type 2019-11-13 22:50:44 +09:00
Hiroyasu Nishiyama
d5234888b3 Merge branch 'master' into i18n-json-editor 2019-11-13 22:42:53 +09:00
Nick O'Leary
f478afb58a Merge pull request #2378 from node-red-hitachi/dev-node-installation
Add node installation from other than public site
2019-11-13 12:06:33 +00:00
Nick O'Leary
a54ca699b5 Scroll the view with WASD/Cursor keys when nothing selected 2019-11-13 10:06:25 +00:00
Nick O'Leary
1f5ff0c6d3 Merge pull request #2366 from btsimonh/fix-encodeObjecterror
Catches bad objects being encoded, returning instead the error.
2019-11-13 09:33:20 +00:00
Nick O'Leary
2a2541df59 Merge pull request #2361 from kazuhitoyokoi/master-fixtypedInput
Support i18n for typedInput in join and switch nodes
2019-11-13 09:29:08 +00:00
Nick O'Leary
cd629c1699 Merge pull request #2373 from kazuhitoyokoi/master-fixuitest
Fix UI testing on the latest Google Chrome
2019-11-13 09:25:59 +00:00
KAZUHIRO ITO
ff96773295 Add node installation from other than public site 2019-11-11 18:25:36 +09:00
tot92
4d6828ec14 typo 2019-11-09 22:09:02 +01:00
tot92
dae1d6057e fixed i18n language fallback behaviour 2019-11-09 22:07:06 +01:00
Kazuhito Yokoi
6726c42cc8 Remove @wdio/cli module 2019-11-05 21:59:37 +09:00
Kazuhito Yokoi
4f6023e44c Support UI testing on the latest Google Chrome 2019-11-05 11:40:20 +09:00
Kazuhito Yokoi
9e16d7f433 Move messages to editor.json 2019-11-05 11:29:43 +09:00
Simon Hailes
aa86cfc55f Add fix for HttpsProxyAgent using an incorrect default port for http: 2019-11-03 11:08:24 +00:00
Simon Hailes
6931cb9895 Added test for both UNC path and flow in non-existent subfolder. 2019-11-03 10:42:50 +00:00
Simon Hailes
d32d04bd4e websocket - add proxy support. 2019-11-03 09:55:11 +00:00
Simon Hailes
0b3e9bf5e2 mqtt & httprequest: change proxy comparison from null to falsy.
mqtt - add no_proxy support.
2019-11-03 09:54:23 +00:00
Simon Hailes
a4af7b8e21 localfilesystem - ensure folder is present before write (e.g. flows file not in user folder) 2019-11-03 09:10:32 +00:00
Simon Hailes
72deee5d74 Detect windows UNC '\\' as well as 'X:' as an absolute flow path - use the path.isAbsolute function instead of rolling our own. 2019-11-03 08:24:47 +00:00
Simon Hailes
5e9e523d4c add one line of coverage in log tests. 2019-11-02 13:21:58 +00:00
Simon Hailes
c54509df3d additional test to cover last line in encodeObject 2019-11-02 13:03:37 +00:00
Simon Hailes
63cc9adeaa small update to log - if toString() causes exception, then note and use util.inspect instead - prevents log from causing processes to abort through exception.
Add tests.
2019-11-02 12:38:40 +00:00
Simon Hailes
74d760a46d add util.inspect data to 'type not printable'. Add test to cover these lines (no existing test). 2019-11-02 12:37:07 +00:00
Simon Hailes
d46531def8 add unit tests for encode Object changes. 2019-11-02 11:27:08 +00:00
Simon Hailes
eb09ec6834 add utils.inspect result and be more explicit about the error. 2019-11-02 10:39:22 +00:00
Nick O'Leary
9bd9c6a400 Merge pull request #2364 from kazuhitoyokoi/master-addtooltip
Add tooltip to the expand button in markdown editor
2019-11-01 23:36:31 +00:00
Nick O'Leary
7321e206c5 Merge pull request #2363 from kazuhitoyokoi/master-fixtabselectkey
Support ctrl key to select tabs for Windows
2019-11-01 23:35:18 +00:00
Nick O'Leary
2c7917f0ca Merge pull request #2355 from node-red-hitachi/fix-typedInput-line-break
Prohibit line break in type menu of typedInput
2019-11-01 23:27:36 +00:00
Nick O'Leary
d94b20a908 Merge pull request #2356 from node-red-hitachi/fix-jsonata-help-display
Make JSONata help initially shown
2019-11-01 23:27:01 +00:00
Nick O'Leary
b1b1fe21dd Merge pull request #2357 from kazuhitoyokoi/addplaceholder
Add placeholders to name fields
2019-11-01 23:26:40 +00:00
Nick O'Leary
1db3af7c8e Merge pull request #2354 from node-red-hitachi/update-jsonata-message-jp
Update Japanese JSONata message catalogue
2019-11-01 23:25:46 +00:00
piyonakajima
397fe31f97 Update 17-split.html (#2365) 2019-11-01 13:04:43 +00:00
Simon Hailes
bc283aa025 Catches bad objects being encoded, returning instead the error.
Symptom- Observed that global context would not display in front end, the call returning 400.
Traced to an object in global which cause encodeObject to except.
This push catches that, and now global will display, but the object in question display as an error.
2019-11-01 11:38:26 +00:00
Kazuhito Yokoi
9dbdf0947b Add tooltip to expand button in markdown editor 2019-10-31 17:21:26 +09:00
Kazuhito Yokoi
7c21bf4555 Add icons for typedInput 2019-10-31 16:03:27 +09:00
Kazuhito Yokoi
361dc194ee Support ctrl key to select tabs for Windows 2019-10-31 14:46:29 +09:00
Hiroyasu Nishiyama
8c1aa83d12 update support of i18n of JSON editor 2019-10-31 12:26:04 +09:00
Kazuhito Yokoi
d2755a8049 Support i18n for typedInput in split and switch nodes 2019-10-30 19:58:29 +09:00
Kazuhito Yokoi
1b78bd1684 Add placeholder to topic field 2019-10-30 19:39:38 +09:00
Kazuhito Yokoi
5f67f1f078 Add placeholders to name fields 2019-10-29 20:45:57 +09:00
Hiroyasu Nishiyama
07061928df make JSONata help initially shown 2019-10-29 10:24:15 +09:00
Hiroyasu Nishiyama
18ff2df65c prohibit line break in type menu of typedInput 2019-10-28 23:49:14 +09:00
Hiroyasu Nishiyama
7b1411d171 update Japanese JSONata message catalogue 2019-10-28 22:45:53 +09:00
Yuma Matsuura
7957ec4369 Modify id 2019-09-27 19:17:17 +09:00
Yuma Matsuura
17653761b9 Update a line break function 2019-09-03 18:43:46 +09:00
3Anology
bca9b5d8c0 基本Node繁體中文化 2019-08-23 00:12:41 +08:00
3Anology
bc789c7f9f Update infotips.json 2019-08-22 23:50:44 +08:00
Yuma Matsuura
a42d7d867e Add a libe break function 2019-07-26 11:36:22 +09:00
3Anology
d517697564 zh-TW
繁體中文(台灣)
2019-07-15 19:15:29 +08:00
416 changed files with 14842 additions and 2991 deletions

View File

@@ -1,6 +1,6 @@
---
name: Bug report
about: Reproducable software issues in the core of Node-RED
about: Reproducible software issues in the core of Node-RED
title: ''
labels: ''
assignees: ''

View File

@@ -29,6 +29,6 @@ the [forum](https://discourse.nodered.org) or
<!-- Put an `x` in the boxes that apply -->
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team.
- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [ ] I have run `grunt` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality

1
.gitignore vendored
View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -26,9 +26,13 @@ module.exports = function(grunt) {
nodemonArgs.push(flowFile);
}
var browserstack = grunt.option('browserstack');
if (browserstack) {
process.env.BROWSERSTACK = true;
}
var nonHeadless = grunt.option('non-headless');
if (nonHeadless) {
process.env.NODE_RED_NON_HEADLESS = 'true';
process.env.NODE_RED_NON_HEADLESS = true;
}
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
@@ -64,8 +68,7 @@ module.exports = function(grunt) {
},
all: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js","test/nodes/**/*_spec.js"] },
core: { src: ["test/unit/_spec.js","test/unit/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]},
trouble: {src: ["test/nodes/core/function/*_spec.js"]}
nodes: { src: ["test/nodes/**/*_spec.js"]}
},
jshint: {
options: {
@@ -81,20 +84,20 @@ module.exports = function(grunt) {
//"loopfunc": true, // allow functions to be defined in loops
//"sub": true // don't warn that foo['bar'] should be written as foo.bar
},
all: [
'Gruntfile.js',
'red.js',
'packages/**/*.js'
],
core: {
files: {
src: [
'Gruntfile.js',
'red.js',
'packages/**/*.js',
]
}
},
// all: [
// 'Gruntfile.js',
// 'red.js',
// 'packages/**/*.js'
// ],
// core: {
// files: {
// src: [
// 'Gruntfile.js',
// 'red.js',
// 'packages/**/*.js',
// ]
// }
// },
nodes: {
files: {
src: [ 'nodes/core/*/*.js' ]
@@ -102,7 +105,7 @@ module.exports = function(grunt) {
},
editor: {
files: {
src: [ 'editor/js/**/*.js' ]
src: [ 'packages/node_modules/@node-red/editor-client/src/js/**/*.js' ]
}
},
tests: {
@@ -148,6 +151,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
@@ -174,6 +178,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/group.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
@@ -190,7 +195,8 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js",
"node_modules/marked/marked.min.js",
"node_modules/dompurify/dist/purify.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js",
"node_modules/jsonata/jsonata-es5.min.js",
@@ -587,7 +593,7 @@ module.exports = function(grunt) {
grunt.registerTask('default',
'Builds editor content then runs code style checks and unit tests on all components',
['build','verifyPackageDependencies','jshint:editor','mocha_istanbul:trouble']);
['build','verifyPackageDependencies','jshint:editor','mocha_istanbul:all']);
grunt.registerTask('test-core',
'Runs code style check and unit tests on core runtime code',

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "1.0.3",
"version": "1.0.4",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -24,7 +24,7 @@
}
],
"dependencies": {
"ajv": "6.10.2",
"ajv": "6.12.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
@@ -34,47 +34,50 @@
"cookie": "0.4.0",
"cookie-parser": "1.4.4",
"cors": "2.8.5",
"cron": "1.7.1",
"cron": "1.8.2",
"denque": "1.4.1",
"express": "4.17.1",
"express-session": "1.16.2",
"express-session": "1.17.0",
"fs-extra": "8.1.0",
"fs.notify": "0.0.4",
"hash-sum": "2.0.0",
"https-proxy-agent": "2.2.3",
"https-proxy-agent": "5.0.0",
"i18next": "15.1.2",
"iconv-lite": "0.5.0",
"iconv-lite": "0.5.1",
"is-utf8": "0.2.1",
"js-yaml": "3.13.1",
"json-stringify-safe": "5.0.1",
"jsonata": "1.7.0",
"jsonata": "1.8.1",
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.1",
"memorystore": "1.6.2",
"mime": "2.4.4",
"mqtt": "2.18.8",
"multer": "1.4.2",
"mustache": "3.0.2",
"node-red-node-rbe": "^0.2.5",
"node-red-node-sentiment": "^0.1.4",
"node-red-node-tail": "^0.0.3",
"mustache": "4.0.0",
"node-red-node-rbe": "^0.2.6",
"node-red-node-sentiment": "^0.1.6",
"node-red-node-tail": "^0.1.0",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.2",
"passport": "0.4.0",
"passport": "0.4.1",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.1",
"request": "2.88.0",
"semver": "6.3.0",
"uglify-js": "3.6.0",
"uglify-js": "3.8.0",
"when": "3.7.8",
"ws": "6.2.1",
"xml2js": "0.4.19"
"xml2js": "0.4.23"
},
"optionalDependencies": {
"bcrypt": "3.0.6"
},
"devDependencies": {
"marked": "0.8.0",
"dompurify": "2.0.8",
"grunt": "~1.0.4",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.2",
@@ -95,14 +98,14 @@
"grunt-npm-command": "~0.1.2",
"grunt-sass": "~3.1.0",
"grunt-simple-mocha": "~0.4.1",
"http-proxy": "^1.16.2",
"http-proxy": "1.18.0",
"istanbul": "0.4.5",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"minami": "1.2.3",
"mocha": "^5.2.0",
"mosca": "^2.8.3",
"node-red-node-test-helper": "^0.2.3",
"node-sass": "^4.13.0",
"node-sass": "^4.13.1",
"should": "^8.4.0",
"sinon": "1.17.7",
"stoppable": "^1.1.0",

View File

@@ -44,6 +44,7 @@ module.exports = {
user: req.user,
module: req.body.module,
version: req.body.version,
url: req.body.url,
req: apiUtils.getRequestLogObject(req)
}
runtimeAPI.nodes.addModule(opts).then(function(info) {

View File

@@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module
passport.use(strategies.bearerStrategy.BearerStrategy);
passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy);
passport.use(strategies.anonymousStrategy);
passport.use(strategies.tokensStrategy);
var server = oauth2orize.createServer();
@@ -60,7 +61,7 @@ function init(_settings,storage) {
function needsPermission(permission) {
return function(req,res,next) {
if (settings && settings.adminAuth) {
return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() {
return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() {
if (!req.user) {
return next();
}
@@ -100,7 +101,10 @@ function login(req,res) {
}
} else if (mergedAdminAuth.type === "strategy") {
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot;
var urlPrefix = (settings.httpAdminRoot||"").replace(/\/$/,"");
if (urlPrefix.length > 0) {
urlPrefix += "/";
}
response = {
"type":"strategy",
"prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]

View File

@@ -123,9 +123,38 @@ AnonymousStrategy.prototype.authenticate = function(req) {
});
}
function TokensStrategy() {
passport.Strategy.call(this);
this.name = 'tokens';
}
util.inherits(TokensStrategy, passport.Strategy);
TokensStrategy.prototype.authenticate = function(req) {
var self = this;
var token = null;
if (Users.tokenHeader() === 'authorization') {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
}
} else {
token = req.headers[Users.tokenHeader()];
}
if (token) {
Users.tokens(token).then(function(admin) {
if (admin) {
self.success(admin,{scope:admin.permissions});
} else {
self.fail(401);
}
});
} else {
self.fail(401);
}
}
module.exports = {
bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange,
anonymousStrategy: new AnonymousStrategy()
anonymousStrategy: new AnonymousStrategy(),
tokensStrategy: new TokensStrategy()
}

View File

@@ -59,7 +59,9 @@ function getDefaultUser() {
var api = {
get: get,
authenticate: authenticate,
default: getDefaultUser
default: getDefaultUser,
tokens: getDefaultUser,
tokenHeader: "authorization"
}
function init(config) {
@@ -105,6 +107,12 @@ function init(config) {
} else {
api.default = getDefaultUser;
}
if (config.tokens && typeof config.tokens === "function") {
api.tokens = config.tokens;
if (config.tokenHeader && typeof config.tokenHeader === "string") {
api.tokenHeader = config.tokenHeader.toLowerCase();
}
}
}
function cleanUser(user) {
if (user && user.hasOwnProperty('password')) {
@@ -118,5 +126,7 @@ module.exports = {
init: init,
get: function(username) { return api.get(username).then(cleanUser)},
authenticate: function() { return api.authenticate.apply(null, arguments) },
default: function() { return api.default(); }
default: function() { return api.default(); },
tokens: function(token) { return api.tokens(token); },
tokenHeader: function() { return api.tokenHeader }
};

View File

@@ -88,13 +88,13 @@ module.exports = {
// Locales
var locales = require("./locales");
locales.init(runtimeAPI);
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
editorApp.get(/^\/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
// Library
var library = require("./library");
library.init(runtimeAPI);
editorApp.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
editorApp.get(/^\/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/^\/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
// Credentials

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "1.0.3",
"version": "1.0.4",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,21 +16,21 @@
}
],
"dependencies": {
"@node-red/util": "1.0.3",
"@node-red/editor-client": "1.0.3",
"@node-red/util": "1.0.4",
"@node-red/editor-client": "1.0.4",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"clone": "2.1.2",
"cors": "2.8.5",
"express-session": "1.16.2",
"express-session": "1.17.0",
"express": "4.17.1",
"memorystore": "1.6.1",
"memorystore": "1.6.2",
"mime": "2.4.4",
"mustache": "3.0.2",
"mustache": "4.0.0",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0",
"passport": "0.4.1",
"when": "3.7.8",
"ws": "6.2.1"
},

View File

@@ -34,11 +34,11 @@
"view" : "Ansicht",
"grid" : "Gitter",
"showGrid" : "Raster anzeigen",
"snapGrid" : "Einrasten am Raster",
"snapGrid" : "Am Raster ausrichten",
"gridSize" : "Rastergröße",
"textDir" : "Textrichtung",
"defaultDir" : "Standard",
"ltr" : "Links-nach-rechts",
"ltr" : "Von links nach rechts",
"rtl" : "Von rechts nach links",
"auto" : "Kontextuell"
},
@@ -53,15 +53,15 @@
"import" : "Import",
"export" : "Exportieren",
"search" : "Flows durchsuchen",
"searchInput" : "durchsuchen Sie Ihre Flows",
"searchInput" : "Flows durchsuchen",
"subflows" : "Subflow",
"createSubflow" : "Subflow erstellen",
"selectionToSubflow" : "Auswahl für Subflow",
"selectionToSubflow" : "Auswahl zu Subflow",
"flows" : "Flows",
"add" : "Hinzufügen",
"rename" : "Umbenennen",
"delete" : "Löschen",
"keyboardShortcuts" : "Tastaturkurzbefehle",
"keyboardShortcuts" : "Tastenkürzel",
"login" : "Anmelden",
"logout" : "Abmelden",
"editPalette" : "Palette verwalten",
@@ -217,7 +217,7 @@
"remote" : "Ferne Änderungen",
"reviewChanges" : "Änderungen prüfen",
"noBinaryFileShowed" : "Der Inhalt der Binärdatei kann nicht angezeigt",
"viewCommitDiff" : "Änderungen festschreiben",
"viewCommitDiff" : "Änderungen committen",
"compareChanges" : "Änderungen vergleichen",
"saveConflict" : "Konfliktlösung speichern",
"conflictHeader" : "<span> __resolved__ </span> von <span> __unresolved__ </span> -Konflikten behoben",
@@ -226,8 +226,8 @@
"newVersionError" : "Neue Version enthält keine gültige JSON-Datei:"
},
"subflow" : {
"editSubflow" : "Flowschablone bearbeiten: __name__",
"edit" : "Flowsschablone bearbeiten",
"editSubflow" : "Subflow bearbeiten: __name__",
"edit" : "Subflow bearbeiten",
"subflowInstances" : "Es ist __count__ Instanz dieser Subflow-Vorlage vorhanden.",
"subflowInstances_plural" : "Es gibt __count__ Instanzen dieser Subflow-Vorlage.",
"editSubflowProperties" : "Eigenschaften bearbeiten",
@@ -266,7 +266,7 @@
}
},
"keyboard" : {
"title" : "Tastaturkurzbefehle",
"title" : "Tastenkürzel",
"keyboard" : "Tastatur",
"filterActions" : "Filteraktionen",
"shortcut" : "Direktaufruf",
@@ -283,7 +283,7 @@
"exportNode" : "Node exportieren",
"nudgeNode" : "Ausgewählte Nodes verschieben (1px)",
"moveNode" : "Ausgewählte Nodes verschieben (20px)",
"toggleSidebar" : "Seitenleiste ein-/ausschalten",
"toggleSidebar" : "Seitenleiste ein-/ausblenden",
"copyNode" : "Ausgewählte Nodes kopieren",
"cutNode" : "Ausgewählte Nodes ausschneiden",
"pasteNode" : "Node einfügen",
@@ -308,7 +308,7 @@
},
"palette" : {
"noInfo" : "Keine Informationen verfügbar",
"filter" : "Filter Nodes",
"filter" : "Nodes filtern",
"search" : "Suchmodule",
"addCategory" : "Neu hinzufügen ...",
"label" : {
@@ -366,11 +366,11 @@
"remove" : "entfernen",
"update" : "Update auf __version__",
"updated" : "aktualisiert",
"install" : "installieren",
"installed" : "installiert",
"install" : "Installieren",
"installed" : "Installiert",
"loading" : "Kataloge werden geladen ...",
"tab-nodes" : "Nodes",
"tab-install" : "installieren",
"tab-install" : "Installieren",
"sort" : "Sortierung:",
"sortAZ" : "a-z",
"sortRecent" : "kürzlich",
@@ -452,7 +452,7 @@
"name" : "Kontextdaten",
"label" : "Kontext",
"none" : "keine ausgewählt",
"refresh" : "Aktualisierung zum Laden",
"refresh" : "Zum Aktualisieren neu laden",
"empty" : "leer",
"node" : "Node",
"flow" : "Flow",
@@ -477,7 +477,7 @@
"none" : "Keine",
"install" : "installieren",
"removeFromProject" : "Aus Projekt entfernen",
"addToProject" : "zu Projekt hinzufügen",
"addToProject" : "Zu Projekt hinzufügen",
"files" : "Dateien",
"flow" : "Flow",
"credentials" : "Berechtigungsnachweis",
@@ -510,7 +510,7 @@
},
"userSettings" : {
"committerDetail" : "Committer-Details",
"committerTip" : "Leer Wert für Systemstandardwert belassen",
"committerTip" : "Leer lassen für Systemstandard",
"userName" : "Benutzername",
"email" : "E-Mail",
"sshKeys" : "SSH-Schlüssel",
@@ -544,7 +544,7 @@
"revertChanges" : "Änderungen zurücksetzen",
"localChanges" : "Lokale Änderungen",
"none" : "Keine",
"conflictResolve" : "Alle Konflikte wurden aufgelöst. Festschreiben der Änderungen, um den Mischvorgang abzuschließen.",
"conflictResolve" : "Alle Konflikte wurden aufgelöst. Committe die Änderungen, um den Merge Request abzuschließen.",
"localFiles" : "Lokale Dateien",
"all" : "alle",
"unmergedChanges" : "Nicht zusammengeführte Änderungen",

View File

@@ -14,7 +14,22 @@
"back": "Back",
"next": "Next",
"clone": "Clone project",
"cont": "Continue"
"cont": "Continue",
"line": "Outline",
"fill": "Fill",
"color": "Color",
"position": "Position"
},
"type": {
"string": "string",
"number": "number",
"boolean": "boolean",
"array": "array",
"buffer": "buffer",
"object": "object",
"jsonString": "JSON string",
"undefined": "undefined",
"null": "null"
}
},
"workspace": {
@@ -80,7 +95,12 @@
"projects-new": "New",
"projects-open": "Open",
"projects-settings": "Project Settings",
"showNodeLabelDefault": "Show label of newly added nodes"
"showNodeLabelDefault": "Show label of newly added nodes",
"groups": "Groups",
"groupSelection": "Group selection",
"ungroupSelection": "Ungroup selection",
"groupMergeSelection": "Merge selection",
"groupRemoveSelection": "Remove from group"
}
},
"actions": {
@@ -160,6 +180,8 @@
"node_plural": "__count__ nodes",
"configNode": "__count__ configuration node",
"configNode_plural": "__count__ configuration nodes",
"group": "__count__ group",
"group_plural": "__count__ groups",
"flow": "__count__ flow",
"flow_plural": "__count__ flows",
"subflow": "__count__ subflow",
@@ -175,6 +197,9 @@
"nodesImported": "Imported:",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"groupCopied": "__count__ group copied",
"groupCopied_plural": "__count__ groups copied",
"groupStyleCopied": "Group style copied",
"invalidFlow": "Invalid flow: __message__",
"export": {
"selected":"selected nodes",
@@ -297,6 +322,13 @@
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
}
},
"group": {
"editGroup": "Edit group: __name__",
"errors": {
"cannotCreateDiffGroups": "Cannot create group using nodes from different groups",
"cannotAddSubflowPorts": "Cannot add subflow ports to a group"
}
},
"editor": {
"configEdit": "Edit",
"configAdd": "Add",
@@ -340,7 +372,8 @@
"bool": "bool",
"json": "JSON",
"bin": "buffer",
"env": "env variable"
"env": "env variable",
"cred": "credential"
},
"menu": {
"input": "input",
@@ -527,6 +560,7 @@
"label": "info",
"node": "Node",
"type": "Type",
"group": "Group",
"module": "Module",
"id": "ID",
"status": "Status",
@@ -602,7 +636,6 @@
"removeFromProject": "remove from project",
"addToProject": "add to project",
"files": "Files",
"package": "Package",
"flow": "Flow",
"credentials": "Credentials",
"package":"Package",
@@ -746,7 +779,8 @@
"bin": "buffer",
"date": "timestamp",
"jsonata": "expression",
"env": "env variable"
"env": "env variable",
"cred": "credential"
}
},
"editableList": {
@@ -792,10 +826,14 @@
"copyPath": "Copy path to item",
"expandItems": "Expand items",
"collapseItems": "Collapse items",
"duplicate": "Duplicate"
"duplicate": "Duplicate",
"error": {
"invalidJSON": "Invalid JSON: "
}
},
"markdownEditor": {
"title": "Markdown editor",
"expand": "Expand",
"format": "Formatted with markdown",
"heading1": "Heading 1",
"heading2": "Heading 2",
@@ -962,7 +1000,8 @@
"passphrase": "Passphrase",
"retry": "Retry",
"update-failed": "Failed to update auth",
"unhandled": "Unhandled error response"
"unhandled": "Unhandled error response",
"host-key-verify-failed": "<p>Host key verification failed.</p><p>The repository host key could not be verified. Please update your <code>known_hosts</code> file and try again."
},
"create-branch-list": {
"invalid": "Invalid branch",
@@ -993,6 +1032,7 @@
"en-US": "English",
"ja": "Japanese",
"ko": "Korean",
"zh-CN": "Chinese(Simplified)"
"zh-CN": "Chinese(Simplified)",
"zh-TW": "Chinese(Traditional)"
}
}

View File

@@ -262,5 +262,9 @@
"$distinct": {
"args": "array",
"desc": "Returns an array with duplicate values removed from `array`"
},
"$type": {
"args": "value",
"desc": "Returns the type of `value` as a string. If `value` is undefined, this will return `undefined`"
}
}

View File

@@ -15,6 +15,17 @@
"next": "進む",
"clone": "プロジェクトをクローン",
"cont": "続ける"
},
"type": {
"string": "文字列",
"number": "数値",
"boolean": "真偽値",
"array": "配列",
"buffer": "バッファ",
"object": "オブジェクト",
"jsonString": "JSON文字列",
"undefined": "undefined",
"null": "null"
}
},
"workspace": {
@@ -340,7 +351,8 @@
"bool": "真偽",
"json": "JSON",
"bin": "バッファ",
"env": "環境変数"
"env": "環境変数",
"cred": "認証情報"
},
"menu": {
"input": "入力",
@@ -791,10 +803,14 @@
"copyPath": "要素のパスをコピー",
"expandItems": "要素を展開",
"collapseItems": "要素を折り畳む",
"duplicate": "複製"
"duplicate": "複製",
"error": {
"invalidJSON": "不正なJSON: "
}
},
"markdownEditor": {
"title": "マークダウンエディタ",
"expand": "拡大",
"format": "マークダウン形式で記述",
"heading1": "見出しレベル1",
"heading2": "見出しレベル2",
@@ -992,6 +1008,7 @@
"en-US": "英語",
"ja": "日本語",
"ko": "韓国語",
"zh-CN": "中国語(簡体)"
"zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"
}
}

View File

@@ -1,7 +1,7 @@
{
"$string": {
"args": "arg",
"desc": "以下の型変換ルールを用いて、引数 *arg* を文字列へ型変換します。:\n\n - 文字列は変換しません。\n - 関数は空の文字列に変換します。\n - JSONの数値として表現できないため、無限大やNaNはエラーになります。\n - 他の値は `JSON.stringify` 関数を用いて、JSONの文字列へ変換します。"
"args": "arg[, prettify]",
"desc": "以下の型変換ルールを用いて、引数 *arg* を文字列へ型変換します。:\n\n - 文字列は変換しません。\n - 関数は空の文字列に変換します。\n - JSONの数値として表現できないため、無限大やNaNはエラーになります。\n - 他の値は `JSON.stringify` 関数を用いて、JSONの文字列へ変換します。`prettify`が真の場合、JSONを整形出力します。フィールドを1行毎に出力。フィールドのネスト深さによってインデントを行います。"
},
"$length": {
"args": "str",
@@ -185,7 +185,7 @@
},
"$reduce": {
"args": "array, function [, init]",
"desc": "配列の各要素値に関数 `function` を連続的に適用して得られる集約値を返します。 `function` の適用の際には、直前の `function` の適用結果と要素値が引数として与えられます。\n\n関数 `function` は引数を2つ取り、配列の各要素の間に配置する中置演算子のように作用しなくてはなりません。\n\n任意の引数 `init` には、集約時の初期値を設定します。"
"desc": "配列の各要素値に関数 `function` を連続的に適用して得られる集約値を返します。 `function` の適用の際には、直前の `function` の適用結果と要素値が引数として与えられます。\n\n関数 `function` は引数を2つ取り、配列の各要素の間に配置する中置演算子のように作用しなくてはなりません。関数`function`のシグネチャは`myfunc($accumulator, $value[, $index[, $array]])`という形式でなければなりません。\n\n任意の引数 `init` には、集約時の初期値を設定します。"
},
"$flowContext": {
"args": "string",
@@ -230,5 +230,41 @@
"$parseInteger": {
"args": "string, picture",
"desc": "`picture`文字列の指定に従って、`string`パラメータを整数(JSON数値)に変換します。`picture`文字列は`$formatInteger`と同じ形式です。"
},
"$error": {
"args": "[str]",
"desc": "メッセージを指定して例外を送出します。メッセージ`str`を省略した場合は`$error() function evaluated`をメッセージとします。"
},
"$assert": {
"args": "arg, str",
"desc": "`arg`が真の場合、undefinedを返します。偽の場合、`str`をメッセージとする例外を送出します。"
},
"$single": {
"args": "array, function",
"desc": "`array`の要素のうち、条件判定関数`function`を満たす(`function`に与えた場合に真偽値`true`を返す)要素が1つのみである場合、それを返します。マッチする要素が1つのみでない場合、例外を送出します。\n\n指定する関数は`function(value [, index [, array]])`というシグネチャでなければなりません。ここで、`value`は`array`の要素値、`index`は要素の添字、第三引数には配列全体を渡します。"
},
"$encodeUrl": {
"args": "str",
"desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "encodeUrlComponentで置換したUniform Resource Locator (URL)をデコードします。\n\n例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "encodeUrlで置換したUniform Resource Locator (URL)要素をデコードします。 \n\n例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "配列`array`から重複要素を削除した配列を返します。"
},
"$type": {
"args": "value",
"desc": "`value` の型を文字列として返します。もし `value` が未定義の場合、 `undefined` が返されます。"
}
}

View File

@@ -10,7 +10,22 @@
"load": "读取",
"save": "保存",
"import": "导入",
"export": "导出"
"export": "导出",
"back": "后退",
"next": "下一个",
"clone": "克隆项目",
"cont": "继续"
},
"type": {
"string": "字符串",
"number": "数字",
"boolean": "布尔值",
"array": "数组",
"buffer": "buffer",
"object": "对象",
"jsonString": "JSON字符串",
"undefined": "未定义",
"null": "空"
}
},
"workspace": {
@@ -19,10 +34,13 @@
"confirmDelete": "确认删除",
"delete": "你确定想删除 '__label__'?",
"dropFlowHere": "把流程放到这里",
"addFlow": "添加流程",
"listFlows": "流程一览",
"status": "状态",
"enabled": "有效",
"disabled": "无效",
"info": "详细描述"
"info": "详细描述",
"selectNodes": "点击节点来选择"
},
"menu": {
"label": {
@@ -36,11 +54,16 @@
"defaultDir": "默认方向",
"ltr": "从左到右",
"rtl": "从右到左",
"auto": "上下文"
"auto": "上下文",
"language": "语言",
"browserDefault": "浏览器默认"
},
"sidebar": {
"show": "显示侧边栏"
},
"palette": {
"show": "显示控制板"
},
"settings": "设置",
"userSettings": "用户设置",
"nodes": "节点",
@@ -60,11 +83,23 @@
"keyboardShortcuts": "键盘快捷方式",
"login": "登陆",
"logout": "退出",
"editPalette":"节点管理",
"editPalette": "节点管理",
"other": "其他",
"showTips": "显示小提示"
"showTips": "显示小提示",
"help": "Node-RED网页",
"projects": "项目",
"projects-new": "新建",
"projects-open": "打开",
"projects-settings": "项目设定",
"showNodeLabelDefault": "显示新添加的节点的标签"
}
},
"actions": {
"toggle-navigator": "切换导航器",
"zoom-out": "缩小",
"zoom-reset": "重设缩放",
"zoom-in": "放大"
},
"user": {
"loggedInAs": "作为__name__登陆",
"username": "账号",
@@ -82,29 +117,73 @@
"warning": "<strong>警告</strong>: __message__",
"warnings": {
"undeployedChanges": "节点中存在未部署的更改",
"nodeActionDisabled": "节点操作已禁用",
"nodeActionDisabledSubflow": "节点动作在子流程中被禁用",
"missing-types": "流程由于缺少节点类型而停止。请检查日志的详细信息",
"restartRequired": "Node-RED必须重新启动以启用升级的模块"
"safe-mode": "<p>流程以安全模式停止。</p><p>您可以修改流程并部署更改以重新启动。</p>",
"restartRequired": "Node-RED必须重新启动以启用升级的模块",
"credentials_load_failed": "<p>由于无法解密凭据,因此流程停止。</p><p>流程凭据文件已加密,但是项目的加密密钥丢失或无效。</p>",
"credentials_load_failed_reset": "<p>凭据无法解密</p><p>流凭据文件已加密,但是项目的加密密钥丢失或无效。</p><p>流凭据文件将在下一次部署时重置。任何现有的流凭证将被清除。</p>",
"missing_flow_file": "<p>找不到项目流程文件。</p><p>该项目未配置流程文件。</p>",
"missing_package_file": "<p>找不到项目包文件。</p><p>项目缺少package.json文件。</p>",
"project_empty": "<p>该项目为空。</p><p>是否要创建一组默认的项目文件?<br/>否则,您将必须在编辑器外部手动将文件添加到项目中。</p>",
"project_not_found": "<p>未找到项目'__project__'。</p>",
"git_merge_conflict": "<p>自动合并更改失败。</p><p>修复未合并的冲突,然后提交结果。</p>"
},
"error": "<strong>Error</strong>: __message__",
"error": "<strong>错误</strong>: __message__",
"errors": {
"lostConnection": "丢失与服务器的连接,重新连接...",
"lostConnectionReconnect": "丢失与服务器的连接__time__秒后重新连接",
"lostConnectionTry": "现在尝试",
"cannotAddSubflowToItself": "无法向其自身添加子流程",
"cannotAddCircularReference": "无法添加子流程 - 循环引用",
"unsupportedVersion": "您正在使用不受支持的Node.js版本<br/>请升级到最新版本的Node.js LTS"
"unsupportedVersion": "您正在使用不受支持的Node.js版本<br/>请升级到最新版本的Node.js LTS",
"failedToAppendNode": "<p>'__module__'加载失败</p><p>__error__</p>"
},
"project": {
"change-branch": "转到本地分支'__project__'",
"merge-abort": "Git合并中止",
"loaded": "项目'__project__'已加载",
"updated": "项目'__project__'已更新",
"pull": "项目'__project__'已重新加载",
"revert": "项目 '__project__'已还原",
"merge-complete": "Git合并完成",
"setupCredentials": "设定证书",
"setupProjectFiles": "设置项目文件",
"no": "不了,谢谢",
"createDefault": "创建默认项目文件",
"mergeConflict": "显示合并冲突"
},
"label": {
"manage-project-dep": "管理项目依赖性",
"setup-cred": "设定证书",
"setup-project": "设置项目文件",
"create-default-package": "创建默认的包文件",
"no-thanks": "不了,谢谢",
"create-default-project": "创建默认项目文件",
"show-merge-conflicts": "显示合并冲突"
}
},
"clipboard": {
"clipboard": "剪贴板",
"nodes": "节点",
"node": "__count__节点",
"node_plural": "__count__节点",
"configNode": "__count__配置节点",
"configNode_plural": "__count__配置节点",
"flow": "__count__流程",
"flow_plural": "__count__流程",
"subflow": "__count__子流程",
"subflow_plural": "__count__子流程",
"pasteNodes": "在这里粘贴节点",
"selectFile": "选择要导入的文件",
"importNodes": "导入节点",
"exportNodes": "导出节点至剪贴板",
"download": "下载",
"importUnrecognised": "导入了无法识别的类型:",
"importUnrecognised_plural": "导入了无法识别的类型:",
"nodesExported": "节点导出到了剪贴板",
"nodesImported": "导入:",
"nodeCopied": "已复制__count__个节点",
"nodeCopied_plural": "已复制__count__个节点",
"invalidFlow": "无效的流程: __message__",
@@ -114,11 +193,21 @@
"all": "所有流程",
"compact": "紧凑",
"formatted": "已格式化",
"copy": "导出到剪贴板"
"copy": "导出到剪贴板",
"export": "到处到库",
"exportAs": "导出为",
"overwrite": "替换",
"exists": "<p><b>\"__file__\"</b>已存在</p><p>是否要替换它?</p>"
},
"import": {
"import": "导入到",
"newFlow": "新流程"
"newFlow": "新流程",
"errors": {
"notArray": "输入的不是JSON数组",
"itemNotObject": "输入的流无效 - 项目__index__不是节点对象",
"missingId": "输入的流无效-项 __index__ 缺少'id'属性",
"missingType": "输入的流程无效-项__index__缺少'类型'属性"
}
},
"copyMessagePath": "已复制路径",
"copyMessageValue": "已复制数值",
@@ -132,7 +221,10 @@
"modifiedFlowsDesc": "只部署包含已更改节点的流",
"modifiedNodes": "已更改的节点",
"modifiedNodesDesc": "只部署已经更改的节点",
"restartFlows": "重启流程",
"restartFlowsDesc": "重新启动当前部署的流程",
"successfulDeploy": "部署成功",
"successfulRestart": "成功重启流程",
"deployFailed": "部署失败: __message__",
"unusedConfigNodes": "您有一些未使用的配置节点",
"unusedConfigNodesLink": "点击此处查看它们",
@@ -152,16 +244,24 @@
"improperlyConfigured": "工作区包含一些未正确配置的节点:",
"unknown": "工作区包含一些未知的节点类型:",
"confirm": "你确定要部署吗?",
"doNotWarn": "不要再对此发出警告",
"conflict": "服务器正在运行较新的一组流程。",
"backgroundUpdate": "服务器上的流程已更新。",
"conflictChecking": "检查是否可以自动合并更改",
"conflictAutoMerge": "此更改不包括冲突,可以自动合并",
"conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。"
"conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。",
"plusNMore": "+ __count__更多"
}
},
"eventLog": {
"title": "事件记录日志",
"view": "查看日志"
},
"diff": {
"unresolvedCount": "__count__个未解决的冲突",
"unresolvedCount_plural": "__count__个未解决的冲突",
"globalNodes": "全局节点",
"flowProperties": "流程属性",
"type": {
"added": "已添加",
"changed": "已更改",
@@ -175,9 +275,19 @@
"nodeCount": "__count__个节点",
"nodeCount_plural": "__count__个节点",
"local": "本地",
"remote": "远程"
"remote": "远程",
"reviewChanges": "查看变更",
"noBinaryFileShowed": "无法显示二进制文件内容",
"viewCommitDiff": "查看提交更改",
"compareChanges": "比较变更",
"saveConflict": "保存冲突解决",
"conflictHeader": "已解决<span>__unresolved__</span>中的<span>__resolved__</span>个冲突",
"commonVersionError": "通用版本不包含有效的JSON",
"oldVersionError": "旧版本不包含有效的JSON",
"newVersionError": "新版本不包含有效的JSON"
},
"subflow": {
"editSubflowInstance": "编辑子流实例__name__",
"editSubflow": "编辑流程模板: __name__",
"edit": "编辑流程模板",
"subflowInstances": "这个子流程模板有__count__个实例",
@@ -185,8 +295,14 @@
"editSubflowProperties": "编辑属性",
"input": "输入:",
"output": "输出:",
"status": "状态节点",
"deleteSubflow": "删除子流程",
"info": "详细描述",
"category": "类别",
"env": {
"restore": "恢复为默认子流",
"remove": "删除环境变量"
},
"errors": {
"noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点",
"multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择"
@@ -204,18 +320,68 @@
"editConfig": "编辑__type__配置",
"addNewType": "添加新的__type__节点",
"nodeProperties": "节点属性",
"label": "标签",
"color": "颜色",
"portLabels": "端口标签",
"labelInputs": "输入",
"labelOutputs": "输出",
"settingIcon": "图标",
"default": "默认",
"noDefaultLabel": "无",
"defaultLabel": "使用默认标签",
"searchIcons": "搜索图标",
"useDefault": "使用默认",
"description": "描述",
"show": "显示",
"hide": "隐藏",
"locale": "选择界面语言",
"icon": "图标",
"inputType": "输入类型",
"inputs": {
"input": "输入",
"select": "选择",
"checkbox": "复选框",
"spinner": "微调器",
"none": "空",
"hidden": "隐藏属性"
},
"types": {
"str": "字符串",
"num": "数字",
"bool": "布尔",
"json": "JSON",
"bin": "buffer",
"env": "环境变量"
},
"menu": {
"input": "输入",
"select": "选择",
"checkbox": "复选框",
"spinner": "微调器",
"hidden": "仅标签"
},
"select": {
"label": "标签",
"value": "值"
},
"spinner": {
"min": "最小值",
"max": "最大值"
},
"errors": {
"scopeChange": "更改范围将使其他流中的节点无法使用"
"scopeChange": "更改范围将使其他流中的节点无法使用",
"invalidProperties": "无效的属性:"
}
},
"keyboard": {
"title": "键盘快捷键",
"keyboard": "键盘",
"filterActions": "筛选动作",
"shortcut": "快捷键",
"scope": "范围",
"unassigned": "未分配",
"global": "全局",
"workspace": "工作组",
"selectAll": "选择所有节点",
"selectAllConnected": "选择所有连接的节点",
"addRemoveNode": "从选择中添加/删除节点",
@@ -226,12 +392,14 @@
"nudgeNode": "移动所选节点(1px)",
"moveNode": "移动所选节点(20px)",
"toggleSidebar": "切换侧边栏",
"togglePalette": "切换控制板",
"copyNode": "复制所选节点",
"cutNode": "剪切所选节点",
"pasteNode": "粘贴节点",
"undoChange": "撤消上次执行的更改",
"searchBox": "打开搜索框",
"managePalette": "管理面板"
"managePalette": "管理面板",
"actionList": "动作列表"
},
"library": {
"library": "库",
@@ -239,30 +407,42 @@
"saveToLibrary": "保存到库...",
"typeLibrary": "__type__类型库",
"unnamedType": "无名__type__",
"exportToLibrary": "节点导出到库",
"exportedToLibrary": "节点导出到库",
"dialogSaveOverwrite": "一个叫做__libraryName__的__libraryType__已经存在您需要覆盖么",
"invalidFilename": "无效的文件名",
"savedNodes": "保存的节点",
"savedType": "已保存__type__",
"saveFailed": "保存失败: __message__",
"newFolder": "新文件夹",
"types": {
"local": "本地的",
"examples": "例子"
}
},
"exportToLibrary": "将节点导出到库"
},
"palette": {
"noInfo": "无可用信息",
"filter": "过滤节点",
"search": "搜索模块",
"addCategory": "添加新的...",
"label": {
"subflows": "子流程",
"network": "网络",
"common": "共通",
"input": "输入",
"output": "输出",
"function": "功能",
"sequence": "序列",
"parser": "解析",
"social": "社交",
"storage": "存储",
"analysis": "分析",
"advanced": "高级"
},
"actions": {
"collapse-all": "收起所有类别",
"expand-all": "展开所有类别"
},
"event": {
"nodeAdded": "添加到面板中的节点:",
"nodeAdded_plural": "添加到面板中的多个节点",
@@ -276,6 +456,7 @@
},
"editor": {
"title": "面板管理",
"palette": "控制板",
"times": {
"seconds": "秒前",
"minutes": "分前",
@@ -309,6 +490,8 @@
"updated": "已更新",
"install": "安装",
"installed": "已安装",
"conflict": "冲突",
"conflictTip": "<p>无法安装此模块,因为它包含已安装的<br/>节点类型</p><p>与<code>__module__</code>冲突</p>",
"loading": "加载目录...",
"tab-nodes": "节点",
"tab-install": "安装",
@@ -356,6 +539,7 @@
"label": "信息",
"node": "节点",
"type": "类型",
"module": "模组",
"id": "ID",
"status": "状态",
"enabled": "启用",
@@ -364,17 +548,18 @@
"instances": "实例",
"properties": "属性",
"info": "信息",
"desc": "描述",
"blank": "空白",
"null": "空",
"showMore": "展开",
"showLess": "收起",
"flow": "流程",
"selection":"选择",
"nodes":"__count__ 个节点",
"selection": "选择",
"nodes": "__count__ 个节点",
"flowDesc": "流程描述",
"subflowDesc": "子流程描述",
"nodeHelp": "节点帮助",
"none":"无",
"none": "无",
"arrayItems": "__count__个项目",
"showTips": "您可以从设置面板启用提示信息"
},
@@ -386,9 +571,25 @@
"subflows": "子流程",
"flows": "流程",
"filterAll": "所有",
"showAllConfigNodes": "显示所有配置节点",
"filterUnused": "未使用",
"showAllUnusedConfigNodes": "显示所有未使用的配置节点",
"filtered": "__count__ 个隐藏"
},
"context": {
"name": "上下文数据",
"label": "上下午",
"none": "未选择",
"refresh": "刷新以加载",
"empty": "空",
"node": "节点",
"flow": "流程",
"global": "全局",
"deleteConfirm": "你确定要删除这个项目吗?",
"autoRefresh": "刷新选择更改",
"refrsh": "刷新",
"delete": "删除"
},
"palette": {
"name": "节点管理",
"label": "节点"
@@ -399,8 +600,151 @@
"description": "描述",
"dependencies": "依赖",
"settings": "设置",
"noSummaryAvailable": "无可用摘要",
"editDescription": "编辑项目描述",
"editDependencies": "编辑项目依赖"
"editDependencies": "编辑项目依赖",
"noDescriptionAvailable": "没有可用的描述",
"editReadme": "编辑README.md",
"showProjectSettings": "显示项目设置",
"projectSettings": {
"title": "项目设置",
"edit": "编辑",
"none": "空",
"install": "安装",
"removeFromProject": "从项目中删除",
"addToProject": "添加到项目",
"files": "文件",
"package": "包",
"flow": "流程",
"credentials": "证书",
"packageCreate": "保存更改后将创建文件",
"fileNotExist": "文件不存在",
"selectFile": "选择文件",
"invalidEncryptionKey": "无效的加密密钥",
"encryptionEnabled": "启用加密",
"encryptionDisabled": "加密已禁用",
"setTheEncryptionKey": "设置加密密钥",
"resetTheEncryptionKey": "重置加密密钥",
"changeTheEncryptionKey": "更改加密密钥",
"currentKey": "当前密钥",
"newKey": "新密钥",
"credentialsAlert": "这将删除所有现有凭证",
"versionControl": "版本控制",
"branches": "分支",
"noBranches": "没有分支",
"deleteConfirm": "您确定要删除本地分支'__name__'吗? 这不能被撤消。",
"unmergedConfirm": "本地分支'__name__'具有未合并的更改,这些更改将丢失。你确定要删除吗?",
"deleteUnmergedBranch": "删除未合并的分支",
"gitRemotes": "Git远程仓库",
"addRemote": "添加远程仓库",
"addRemote2": "添加远程仓库",
"remoteName": "远程仓库名",
"nameRule": "只能包含A-Z 0-9 _ -",
"url": "URL",
"urlRule": "https://, ssh:// or file://",
"urlRule2": "网址中不能包含用户名/密码",
"noRemotes": "没有远程仓库",
"deleteRemoteConfrim": "您确定要删除远程仓库'__name__'吗?",
"deleteRemote": "删除远程仓库"
},
"userSettings": {
"committerDetail": "提交者详细信息",
"committerTip": "保留空白以使用系统默认值",
"userName": "用户名",
"email": "电子邮件",
"sshKeys": "SSH密钥",
"sshKeysTip": "允许您创建到远程git存储库的安全连接。",
"add": "添加密钥",
"addSshKey": "添加SSH密钥",
"addSshKeyTip": "生成新的公钥/私钥对",
"name": "名字",
"nameRule": "只能包含A-Z 0-9 _ -",
"passphrase": "密码短语",
"passphraseShort": "密码短语过短",
"optional": "可选的",
"cancel": "取消",
"generate": "生成密钥",
"noSshKeys": "没有SSH密钥",
"copyPublicKey": "将公钥复制到剪贴板",
"delete": "删除密钥",
"gitConfig": "Git配置",
"deleteConfirm": "您确定要删除SSH密钥__name__吗这不能被撤消。"
},
"versionControl": {
"unstagedChanges": "未暂存的变更",
"stagedChanges": "暂存的变更",
"unstageChange": "取消变更的暂存",
"stageChange": "暂存变更",
"unstageAllChange": "取消所有变更的暂存",
"stageAllChange": "暂存所有变更",
"commitChanges": "提交变更",
"resolveConflicts": "解决冲突",
"head": "HEAD",
"staged": "暂存的",
"unstaged": "未暂存的",
"local": "本地的",
"remote": "远程的",
"revert": "您确定要将更改恢复为'__file__'吗?这不能被撤消。",
"revertChanges": "还原变更",
"localChanges": "本地变更",
"none": "None",
"conflictResolve": "解决所有冲突。提交更改以完成合并。",
"localFiles": "本地文件",
"all": "所有的",
"unmergedChanges": "未合并的更改",
"abortMerge": "中止合并",
"commit": "提交",
"changeToCommit": "提交变更",
"commitPlaceholder": "输入您的提交信息",
"cancelCapital": "取消",
"commitCapital": "提交",
"commitHistory": "提交历史",
"branch": "分支:",
"moreCommits": "更多提交",
"changeLocalBranch": "变更本地分支",
"createBranchPlaceholder": "查找或创建分支",
"upstream": "上游",
"localOverwrite": "切换分支会覆盖您现有的本地更改。您必须先提交或撤消那些更改。",
"manageRemoteBranch": "管理远程分支",
"unableToAccess": "无法访问远程存储库",
"retry": "重试",
"setUpstreamBranch": "设置为上游分支",
"createRemoteBranchPlaceholder": "查找或创建远程分支",
"trackedUpstreamBranch": "创建的分支将被设置为跟踪的上游分支。",
"selectUpstreamBranch": "分支将被创建。 在下面选择以将其设置为被跟踪的上游分支。",
"pushFailed": "推送失败,因为远程具有更多的最新提交。请先拉取并合并,然后再尝试推送。",
"push": "推送",
"pull": "拉取",
"unablePull": "<p>无法提取远程更改;您未暂存的本地更改将被覆盖。</p><p>请先提交更改,然后重试。</p>",
"showUnstagedChanges": "显示未暂存的更改",
"connectionFailed": "无法连接到远程存储库:",
"pullUnrelatedHistory": "<p>远程有无关的提交历史</p><p>您确定要将这些更改拉入本地仓库吗?</p>",
"pullChanges": "拉取更改",
"history": "历史",
"projectHistory": "项目历史",
"daysAgo": "__count__天前",
"daysAgo_plural": "__count__天前",
"hoursAgo": "__count__小时前",
"hoursAgo_plural": "__count__小时前",
"minsAgo": "__count__分钟前",
"minsAgo_plural": "__count__分钟前",
"secondsAgo": "秒前",
"notTracking": "您的本地分支当前未跟踪一个远程分支。",
"statusUnmergedChanged": "您的仓库中有未合并的更改。您需要解决冲突并提交结果。",
"repositoryUpToDate": "您的仓库是最新的。",
"commitsAhead": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。",
"commitsAhead_plural": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。",
"commitsBehind": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。",
"commitsBehind_plural": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。",
"commitsAheadAndBehind1": "您的存储库落后远程仓库__count__次提交",
"commitsAheadAndBehind1_plural": "您的存储库落后远程仓库__count__次提交",
"commitsAheadAndBehind2": "领先远程仓库__count__次提交。",
"commitsAheadAndBehind2_plural": "领先远程仓库__count__次提交。",
"commitsAheadAndBehind3": "您必须先拉取远程提交,然后才能进行推送。",
"commitsAheadAndBehind3_plural": "您必须先拉取远程提交,然后才能进行推送。",
"refreshCommitHistory": "刷新提交历史",
"refreshChanges": "刷新更改"
}
}
},
"typedInput": {
@@ -408,10 +752,12 @@
"str": "文字列",
"num": "数字",
"re": "正则表达式",
"bool": "布尔",
"bool": "布尔",
"json": "JSON",
"bin": "二进制流",
"date": "时间戳"
"date": "时间戳",
"jsonata": "表达式",
"env": "环境变量"
}
},
"editableList": {
@@ -423,8 +769,10 @@
},
"expressionEditor": {
"functions": "功能",
"functionReference": "功能reference",
"insert": "插入",
"title": "JSONata表达式编辑器",
"test": "测试",
"data": "示例消息",
"result": "结果",
"format": "格式表达方法",
@@ -438,14 +786,229 @@
"eval": "评估表达式错误:\n __message__"
}
},
"jsEditor": {
"title": "JavaScript编辑器"
},
"textEditor": {
"title": "文本编辑器"
},
"jsonEditor": {
"title": "JSON编辑器",
"format": "格式化JSON"
"format": "格式化JSON",
"rawMode": "编辑 JSON",
"uiMode": "Visual编辑器",
"insertAbove": "在上方插入",
"insertBelow": "在下方插入",
"addItem": "添加项目",
"copyPath": "复制路径到项目",
"expandItems": "展开项目",
"collapseItems": "收合项目",
"duplicate": "重复",
"error": {
"invalidJSON": "无效的JSON: "
}
},
"markdownEditor": {
"title": "Markdown编辑器",
"expand": "展开",
"format": "格式化为markdown",
"heading1": "标题 1",
"heading2": "标题 2",
"heading3": "标题 3",
"bold": "粗体",
"italic": "斜体",
"code": "代码",
"ordered-list": "排序的列表",
"unordered-list": "非排序的列表",
"quote": "引用",
"link": "链接",
"horizontal-rule": "水平线",
"toggle-preview": "切换预览"
},
"bufferEditor": {
"title": "缓冲区编辑器",
"modeString": "作为UTF-8字符串处理",
"modeArray": "作为JSON数组处理",
"modeDesc": "<h3>缓冲区编辑器</h3><p>缓冲区类型被存储为字节值的JSON数组。编辑器将尝试将输入的数值解析为JSON数组。如果它不是有效的JSON它将被视为UTF-8字符串并被转换为单个字符代码点的数组。</p><p>例如,<code>Hello World</code>的值会被转换为JSON数组<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "配置Git客户端",
"welcome": {
"hello": "你好! 我们已经将“项目”引入了Node-RED。",
"desc0": "这是一种用于管理流程文件的新方法,并且包括对流程的版本控制。",
"desc1": "首先您可以创建您的第一个项目或从git存储库克隆现有项目。",
"desc2": "如果不确定,可以暂时跳过此步骤。您仍然可以随时通过“项目”菜单创建第一个项目。",
"create": "建立专案",
"clone": "克隆仓库",
"openExistingProject": "打开现有项目",
"not-right-now": "不是现在"
},
"git-config": {
"setup": "设置您的版本控制客户端",
"desc0": "Node-RED使用开源工具Git进行版本控制。它跟踪对项目文件的更改并允许您将其推送到远程存储库。",
"desc1": "提交一组更改时Git会使用用户名和电子邮件地址记录谁进行了更改。用户名可以是您想要的任何名称-不必是您的真实姓名。",
"desc2": "您的Git客户端已经配置了以下详细信息。",
"desc3": "您可以稍后在设置对话框的'Git config'标签下更改这些设置。",
"username": "用户名",
"email": "电子邮件"
},
"project-details": {
"create": "创建你的项目",
"desc0": "项目被维护为Git仓库。与他人一起共享您的流程",
"desc1": "您可以创建多个项目,并通过编辑器在它们之间快速切换。",
"desc2": "首先,您的项目需要一个名称和一个可选的描述。",
"already-exists": "项目已存在",
"must-contain": "只能包含A-Z 0-9 _ -",
"project-name": "项目名",
"desc": "描述",
"opt": "可选的"
},
"clone-project": {
"clone": "克隆一个项目",
"desc0": "如果您已经有一个包含项目的git仓库则可以对其进行克隆以开始使用。",
"already-exists": "项目已存在",
"must-contain": "只能包含A-Z 0-9 _ -",
"project-name": "项目名",
"no-info-in-url": "网址中不要包含用户名/密码",
"git-url": "Git仓库的url",
"protocols": "https://, ssh:// or file://",
"auth-failed": "认证失败",
"username": "用户名",
"passwd": "秘密啊",
"ssh-key": "SSH密钥",
"passphrase": "密码短语",
"ssh-key-desc": "在通过ssh克隆仓库之前必须添加SSH密钥才能访问它。",
"ssh-key-add": "添加一个ssh密钥",
"credential-key": "证书加密密钥",
"cant-get-ssh-key": "错误! 无法获取所选的SSH密钥路径。",
"already-exists2": "已存在",
"git-error": "git错误",
"connection-failed": "连接失败",
"not-git-repo": "不是一个git仓库",
"repo-not-found": "未发现仓库"
},
"default-files": {
"create": "创建您的项目文件",
"desc0": "一个包含您的流程文件Readme文件和package.json文件的项目。",
"desc1": "它可以包含您要在Git仓库中维护的任何其他文件。",
"desc2": "您现有的流程和凭证文件将被复制到项目中。",
"flow-file": "流程文件",
"credentials-file": "证书文件"
},
"encryption-config": {
"setup": "设置证书文件的加密",
"desc0": "您的流程证书文件可以被加密以确保其内容安全。",
"desc1": "如果要将这些证书存储在公共Git存储库中则必须通过提供密钥短语来对它们进行加密。",
"desc2": "您的流程证书文件当前未加密。",
"desc3": "这意味着任何有权访问该文件的人都可以读取其内容,例如密码和访问令牌。",
"desc4": "如果要将这些证书存储在公共Git仓库中则必须通过提供密钥短语来对它们进行加密。",
"desc5": "当前使用设置文件中的credentialSecret属性作为密钥来加密流程证书文件。",
"desc6": "您的流程证书文件当前使用系统生成的密钥加密。您应该为此项目提供一个新的密钥。",
"desc7": "密钥将与项目文件分开存储。您将需要提供在另一个Node-RED实例中使用该项目的密钥。",
"credentials": "证书",
"enable": "启用加密",
"disable": "禁用加密",
"disabled": "禁用的",
"copy": "复制现有密钥",
"use-custom": "使用自定义密钥",
"desc8": "证书文件不会被加密,其内容很容易阅读",
"create-project-files": "创建项目文件",
"create-project": "创建项目",
"already-exists": "已存在",
"git-error": "git错误",
"git-auth-error": "git认证错误"
},
"create-success": {
"success": "您已经成功创建了第一个项目!",
"desc0": "现在您可以像往常一样继续使用Node-RED。",
"desc1": "侧栏中的“信息”标签显示了您当前的活动项目。名称旁边的按钮可用于访问项目设置视图。",
"desc2": "侧栏中的“历史记录”标签可用于查看项目中已更改的文件并提交。它向您显示了提交的完整历史记录,并允许您将更改推送到远程存储库。"
},
"create": {
"projects": "项目",
"already-exists": "项目已存在",
"must-contain": "只能包含A-Z 0-9 _ -",
"no-info-in-url": "网址中不要包含用户名/密码",
"open": "打开项目",
"create": "创建项目",
"clone": "克隆仓库",
"project-name": "项目名",
"desc": "描述",
"opt": "可选的",
"flow-file": "流程文件",
"credentials": "证书",
"enable-encryption": "启用加密",
"disable-encryption": "禁用加密",
"encryption-key": "加密密钥",
"desc0": "用来保护您的凭证的短语",
"desc1": "凭证文件不会被加密,其内容很容易阅读",
"git-url": "Git存储库URL",
"protocols": "https://, ssh:// or file://",
"auth-failed": "验证失败",
"username": "用户名",
"password": "密码",
"ssh-key": "SSH密钥",
"passphrase": "密码短语",
"desc2": "在通过ssh克隆存储库之前必须添加SSH密钥才能访问它。",
"add-ssh-key": "添加一个ssh密钥",
"credentials-encryption-key": "证书加密密钥",
"already-exists-2": "已存在",
"git-error": "git错误",
"con-failed": "连接失败",
"not-git": "不是git仓库",
"no-resource": "找不到存储库",
"cant-get-ssh-key-path": "错误无法获取所选的SSH密钥路径。",
"unexpected_error": "意外的错误"
},
"delete": {
"confirm": "您确定要删除此项目吗?"
},
"create-project-list": {
"search": "搜索您的项目",
"current": "当前的"
},
"require-clean": {
"confirm": "<p>您有未部署的更改,这些更改将丢失。</p><p>您要继续吗?</p>"
},
"send-req": {
"auth-req": "存储库需要认证",
"username": "用户名",
"password": "秘密",
"passphrase": "密码短语",
"retry": "重试",
"update-failed": "无法更新身份验证",
"unhandled": "未处理的错误响应"
},
"create-branch-list": {
"invalid": "无效的分支",
"create": "创建分支",
"current": "当前的"
},
"create-default-file-set": {
"no-active": "没有活动项目就无法创建默认文件集",
"no-empty": "无法在非空项目上创建默认文件集",
"git-error": "git错误"
},
"errors": {
"no-username-email": "您的Git客户端未配置用户名/电子邮件。",
"unexpected": "发生了一个意料之外的问题",
"code": "代码"
}
},
"editor-tab": {
"properties": "属性",
"envProperties": "环境变量",
"description": "描述",
"appearance": "外观",
"preview": "UI预览",
"defaultValue": "默认值"
},
"languages": {
"de": "德语",
"en-US": "英文",
"ja": "日语",
"ko": "韩文",
"zh-CN": "简体中文",
"zh-TW": "繁体中文"
}
}

View File

@@ -214,5 +214,57 @@
"$toMillis": {
"args": "timestamp",
"desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。如果该字符串的格式不正确则抛出错误。"
},
"$env": {
"args": "arg",
"desc": "返回环境变量的值。\n\n这是Node-RED定义的函数。"
},
"$eval": {
"args": "expr [, context]",
"desc": "使用当前上下文来作为评估依据,分析并评估字符串`expr`其中包含文字JSON或JSONata表达式。"
},
"$formatInteger": {
"args": "number, picture",
"desc": "将“数字”转换为字符串并将其格式化为“图片”字符串指定的整数表示形式。图片字符串参数定义了数字的格式并具有与XPath F&O 3.1 规范中的fnformat-integer相同的语法。"
},
"$parseInteger": {
"args": "string, picture",
"desc": "使用“图片”字符串指定的格式将“字符串”参数的内容解析为整数作为JSON数字。图片字符串参数与$formatInteger格式相同。."
},
"$error": {
"args": "[str]",
"desc": "引发错误并显示一条消息。 可选的`str`将替代$error()函数评估的默认消息。"
},
"$assert": {
"args": "arg, str",
"desc": "如果`arg`为真,则该函数返回。 如果arg为假则抛出带有str的异常作为异常消息。"
},
"$single": {
"args": "array, function",
"desc": "返回满足参数function谓语的array参数中的唯一值 (比如传递值时函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数`functionvalue [index [array []]]`其中value是数组的每个输入index是该值的位置整个数组作为第三个参数传递。"
},
"$encodeUrl": {
"args": "str",
"desc": "通过用表示字符的UTF-8编码的一个两个三个或四个转义序列替换某些字符的每个实例对统一资源定位符URL组件进行编码。\n\n示例`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "通过用表示字符的UTF-8编码的一个两个三个或四个转义序列替换某些字符的每个实例对统一资源定位符URL进行编码。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "解码以前由encodeUrlComponent创建的统一资源定位器URL组件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "解码先前由encodeUrl创建的统一资源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "返回一个数组,其中重复的值已从`数组`中删除"
},
"$type": {
"args": "value",
"desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0" : "您可以用 {{core:delete-selection}} 刪除選擇的節點或連結。",
"tip1" : "{{core:search}} 可以在流程內搜索節點。",
"tip2": "{{core:toggle-sidebar}} 可以顯示或隱藏邊側欄。",
"tip3": "您可以在 {{core:manage-palette}} 中管理節點的控制台。",
"tip4": "側邊欄中會列出流程中所有的配置節點。您可以通過功能表或者 {{core:show-config-tab}} 來訪問這些節點。",
"tip5": "您可以在設定中選擇顯示或隱藏這些提示。",
"tip6": "您可以用[left] [up] [down] [right]鍵來移動被選中的節點。按住[shift]可以更快地移動節點。",
"tip7": "把節點拖到連接上可以向連接中插入節點。",
"tip8": "您可以用 {{core:show-export-dialog}} 來匯出被選中的節點或標籤頁中的流程。",
"tip9": "您可以將流程的json文字檔拖入編輯方塊或 {{core:show-import-dialog}} 來導入流程。",
"tip10": "按住[shift]後按一下並拖動節點可以將該節點的多個連接一併移動到其他節點的埠。",
"tip11": "{{core:show-info-tab}} 可以顯示「資訊」標籤頁。 {{core:show-debug-tab}} 可以顯示「調試」標籤頁。",
"tip12": "按住[ctrl]的同時點擊工作介面可以在節點的對話欄中快速添加節點。",
"tip13": "按住[ctrl]的同時點擊節點的埠或後續節點可以快速連接多個節點。",
"tip14": "按住[shift]的同時點擊節點會選中所有被連接的節點。",
"tip15": "按住[ctrl]的同時點擊節點可以在選中或取消選中節點。",
"tip16": "{{core:show-previous-tab}} 和 {{core:show-next-tab}} 可以切換標籤頁。",
"tip17": "您可以在節點的屬性配置畫面中通過 {{core:confirm-edit-tray}} 來更改設置,或者用 {{core:cancel-edit-tray}} 來取消更改。",
"tip18": "您可以通過點擊 {{core:edit-selected-node}} 來顯示被選中節點的屬性設置畫面。"
}
}

View File

@@ -0,0 +1,270 @@
{
"$string": {
"args": "arg",
"desc": "通過以下的類型轉換規則將參數*arg*轉換成字串:\n\n - 字串不轉換。\n -函數轉換成空的字串。\n - JSON的值無法用數字表示所以用無限大或者NaN非數表示。\n - 用JSON.stringify函數將其他值轉換成JSON字串。"
},
"$length": {
"args": "str",
"desc": "輸出字串str的字數。如果str不是字串拋出錯誤。"
},
"$substring": {
"args": "str, start[, length]",
"desc": "輸出`start`位置後的的首次出現的包括`str`的子字串。 如果`length`被指定,那麼的字串中將只包括前`length`個文字。如果`start`是負數則輸出從`str`末尾開始的`length`個文字"
},
"$substringBefore": {
"args": "str, chars",
"desc": "輸出str中首次出現的chars之前的子字串如果str中不包括chars則輸出str。"
},
"$substringAfter": {
"args": "str, chars",
"desc": "輸出str中首次出現的chars之後的子字串如果str中不包括chars則輸出str。"
},
"$uppercase": {
"args": "str",
"desc": "`將str中的所有字母變為大寫後輸出。"
},
"$lowercase": {
"args": "str",
"desc": "將str中的所有字母變為小寫後輸出。"
},
"$trim": {
"args": "str",
"desc": "將以下步驟應用於`str`來去除所有空白文字並實現標準化。\n\n 將全部tab定位字元、Enter鍵、換行字元用空白代替。\n- 將連續的空白文字變成一個空白文字。\n- 消除開頭和末尾的空白文字。\n\n如果`str`沒有被指定(即在無輸入參數的情況下調用本函數),將上下文的值作為`str`來使用。 如果`str` 不是字串則拋出錯誤。"
},
"$contains": {
"args": "str, pattern",
"desc": "字串`str` 和 `pattern`匹配的話輸出`true`,不匹配的情況下輸出 `false`。 不指定`str`的情況下(比如用一個參數調用本函數時)、將上下文的值作為`str`來使用。參數 `pattern`可以為字串或正則表達。"
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "將參數`str`分解成由子字串組成的陣列。 如果`str`不是字串拋出錯誤。可以省略的參數 `separator`中指定字串`str`的分隔符號。分隔符號可以是文字或規則運算式。在不指定`separator`的情況下、將分隔符號看作空的字串並把`str`拆分成由單個字母組成的陣列。如果`separator`不是字串則拋出錯誤。在可省略的參數`limit`中指定分割後的子字串的最大個數。超出個數的子字串將被捨棄。如果`limit`沒有被指定,`str` 將不考慮子字串的個數而將字串完全分隔。如果`limit`是負數則拋出錯誤。"
},
"$join": {
"args": "array[, separator]",
"desc": "用可以省略的參數 `separator`來把多個字元串連接。如果`array`不是字串則拋出錯誤。 如果沒有指定`separator`,則用空字串來連接字元(即字串之間沒有`separator`)。 如果`separator`不是字元則拋出錯誤。"
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "對字串`str`使用規則運算式`pattern`並輸出與`str`相匹配的部分資訊。"
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "在字串`str`中搜索`pattern`並用`replacement`來替換。\n\n可選參數`limit`用來指定替換次數的上限。"
},
"$now": {
"args": "",
"desc": "生成ISO 8601互換格式的時刻並作為字串輸出。"
},
"$base64encode": {
"args": "string",
"desc": "將ASCII格式的字串轉換為Base 64格式。將字串中的文字視作二進位形式的資料處理。包含URI編碼在內的字串文字必須在0x00到0xFF的範圍內否則不會被支持。"
},
"$base64decode": {
"args": "string",
"desc": "用UTF-8內碼表將Base 64形式二進位值轉換為字串。"
},
"$number": {
"args": "arg",
"desc": "用下述的規則將參數 `arg`轉換為數值。:\n\n 數值不做轉換。\n 將字串中合法的JSON數値表示轉換成數値。\n 其他形式的值則拋出錯誤。"
},
"$abs": {
"args": "number",
"desc": "輸出參數`number`的絕對值。"
},
"$floor": {
"args": "number",
"desc": "輸出比`number`的值小的最大整數。"
},
"$ceil": {
"args": "number",
"desc": "輸出比`number`的值大的最小整數。"
},
"$round": {
"args": "number [, precision]",
"desc": "輸出四捨五入後的參數`number`。可省略的參數 `precision`指定四捨五入後小數點下的位數。"
},
"$power": {
"args": "base, exponent",
"desc": "輸出底數`base`的`exponent`次冪。"
},
"$sqrt": {
"args": "number",
"desc": "輸出參數 `number`的平方根。"
},
"$random": {
"args": "",
"desc": "輸出比0大比1小的偽亂數。"
},
"$millis": {
"args": "",
"desc": "返回從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數。在同一個運算式的測試中所有對`$millis()`的調用將會返回相同的值。"
},
"$sum": {
"args": "array",
"desc": "輸出陣列`array`的總和。如果`array`不是數值則拋出錯誤。"
},
"$max": {
"args": "array",
"desc": "輸出陣列`array`的最大值。如果`array`不是數值則拋出錯誤。"
},
"$min": {
"args": "array",
"desc": "輸出陣列`array`的最小值。如果`array`不是數值則拋出錯誤。。"
},
"$average": {
"args": "array",
"desc": "輸出陣列`array`的平均數。如果`array`不是數值則拋出錯誤。。"
},
"$boolean": {
"args": "arg",
"desc": "用下述規則將資料轉換成布林值。:\n\n - 不轉換布林值`Boolean`。\n 將空的字串`string`轉換為`false`\n 將不為空的字串`string`轉換為`true`\n 將為0的數位`number`轉換成`false`\n 將不為0的數位`number`轉換成`true`\n –將`null`轉換成`false`\n –將空的陣列`array`轉換成`false`\n –如果陣列`array`中含有可以轉換成`true`的要素則轉換成`true`\n –如果`array`中沒有可轉換成`true`的要素則轉換成`false`\n 空的物件`object`轉換成`false`\n 非空的物件`object`轉換成`true`\n –將函數`function`轉換成`false`"
},
"$not": {
"args": "arg",
"desc": "輸出做反轉運算後的布林值。首先將`arg`轉換為布林值。"
},
"$exists": {
"args": "arg",
"desc": "如果算式`arg`的值存在則輸出`true`。如果算式的值不存在(比如指向不存在區域的引用)則輸出`false`。"
},
"$count": {
"args": "array",
"desc": "輸出陣列中的元素數。"
},
"$append": {
"args": "array, array",
"desc": "將兩個陣列連接。"
},
"$sort": {
"args": "array [, function]",
"desc": "輸出排序後的陣列`array`。\n\n如果使用了比較函數`function`,則下述兩個參數需要被指定。\n\n`function(left, right)`\n\n該比較函數是為了比較left和right兩個值而被排序演算法調用的。如果使用者希望left的值被置於right的值之後那麼該函數必須輸出布林值`true`來表示位置交換。而在不需要位置交換時函數必須輸出`false`。"
},
"$reverse": {
"args": "array",
"desc": "輸出倒序後的陣列`array`。"
},
"$shuffle": {
"args": "array",
"desc": "輸出隨機排序後的陣列 `array`。"
},
"$zip": {
"args": "array, ...",
"desc": "將陣列中的值按索引順序打包後輸出。"
},
"$keys": {
"args": "object",
"desc": "輸出由物件內的鍵組成的陣列。如果參數是物件的陣列則輸出由所有物件中的鍵去重後組成的佇列。"
},
"$lookup": {
"args": "object, key",
"desc": "輸出對象中與參數`key`對應的值。如果第一個參數`object`是陣列,那麼陣列中所有的物件都將被搜索並輸出這些物件中與參數`key`對應的值。"
},
"$spread": {
"args": "object",
"desc": "將物件中的鍵值對分隔成每個要素中只含有一個鍵值對的陣列。如果參數`object`是陣列,那麼返回值的陣列中包含所有物件中的鍵值對。"
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "將輸入陣列`objects`中所有的鍵值對合併到一個`object`中並返回。如果輸入陣列的要素中含有重複的鍵,則返回的`object`中將只包含陣列中最後出現要素的值。如果輸入陣列中包括物件以外的元素,則拋出錯誤。"
},
"$sift": {
"args": "object, function",
"desc": "輸出參數`object`中符合`function`的鍵值對。\n\n`function`必須含有下述參數。\n\n`function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "將函數`function`應用於`object`中的所有鍵值對並輸出由所有返回值組成的陣列。"
},
"$map": {
"args": "array, function",
"desc": "將函數`function`應用於陣列`array`中所有的值並輸出由返回值組成的陣列。\n\n`function`中必須含有下述參數。\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "輸出陣列`array`中符合函數`function`條件的值組成的陣列。\n\n`function`必須包括下述參數。\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "將`function`依次應用於陣列中的各要素值。 其中,前一個要素值的計算結果將參與到下一次的函數運算中。。\n\n函數`function`接受兩個參數並作為中綴標記法中的操作符。\n\n可省略的參數`init`將作為運算的初始值。"
},
"$flowContext": {
"args": "string",
"desc": "獲取流上下文(流等級的上下文,可以讓所有節點共用)的屬性。"
},
"$globalContext": {
"args": "string",
"desc": "獲取全域上下文的屬性。"
},
"$pad": {
"args": "string, width [, char]",
"desc": "根據需要,向字串`string`的副本中填充文字使該字串的字數達到`width`的絕對值並返回填充文字後的字串。\n\n如果`width`的值為正,則向字串`string`的右側填充文字,如果`width`為負,則向字串`string`的左側填充文字。\n\n可選參數`char`用來指定填充的文字。如果未指定該參數,則填充空白文字。"
},
"$fromMillis": {
"args": "number",
"desc": "將表示從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數的數值轉換成ISO 8601形式時間戳記的字串。"
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "將`number`轉換成具有`picture`所指定的數值格式的字串。\n\n此函數的功能與XPath F&O 3.1規格中定義的XPath/XQuery函數的fn:format-number功能相一致。參數`picture`用於指定數值的轉換格式其語法與fn:format-number中的定義一致。\n\n可選的第三參數`options`用來覆蓋預設的局部環境格式如小數點分隔符號。如果指定該參數那麼該參數必須是包含name/value對的物件並且name/value對必須符合XPath F&O 3.1規格中記述的數值格式。"
},
"$formatBase": {
"args": "number [, radix]",
"desc": "將`number`變換為以參數`radix`的值為基數形式的字串。如果不指定`radix`的值則默認基數為10。指定的`radix`值必須在236之間否則拋出錯誤。"
},
"$toMillis": {
"args": "timestamp",
"desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數。如果該字串的格式不正確則拋出錯誤。"
},
"$env": {
"args": "arg",
"desc": "返回環境變量的值。\n\n這是Node-RED定義的函數。"
},
"$eval": {
"args": "expr [, context]",
"desc": "使用當前上下文來作為評估依據,分析並評估字符串`expr`其中包含文字JSON或JSONata表達式。"
},
"$formatInteger": {
"args": "number, picture",
"desc": "將“數字”轉換為字符串並將其格式化為“圖片”字符串指定的整數表示形式。圖片字符串參數定義了數字的格式並具有與XPath F&O 3.1 規範中的fnformat-integer相同的語法。"
},
"$parseInteger": {
"args": "string, picture",
"desc": "使用“圖片”字符串指定的格式將“字符串”參數的內容解析為整數作為JSON數字。圖片字符串參數與$formatInteger格式相同。."
},
"$error": {
"args": "[str]",
"desc": "引發錯誤並顯示一條消息。 可選的`str`將替代$error()函數評估的默認消息。"
},
"$assert": {
"args": "arg, str",
"desc": "如果`arg`為真,則該函數返回。 如果arg為假則拋出帶有str的異常作為異常消息。"
},
"$single": {
"args": "array, function",
"desc": "返回滿足參數function謂語的array參數中的唯一值 (比如傳遞值時函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數`functionvalue [index [array []]]`其中value是數組的每個輸入index是該值的位置整個數組作為第三個參數傳遞。"
},
"$encodeUrl": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL組件進行編碼。\n\n示例`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL進行編碼。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器URL組件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "解碼先前由encodeUrl創建的統一資源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "返回一個數組,其中重復的值已從`數組`中刪除"
},
"$type": {
"args": "value",
"desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`"
}
}

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ RED.history = (function() {
var i;
var len;
var node;
var group;
var subflow;
var modifiedTabs = {};
var inverseEv;
@@ -39,7 +40,7 @@ RED.history = (function() {
inverseEv = {
t: 'replace',
config: RED.nodes.createCompleteNodeSet(),
changed: [],
changed: {},
rev: RED.nodes.version()
};
RED.nodes.clear();
@@ -74,6 +75,15 @@ RED.history = (function() {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.groups) {
inverseEv.groups = [];
for (i=0;i<ev.groups.length;i++) {
group = ev.groups[i];
modifiedTabs[group.z] = true;
inverseEv.groups.push(group);
RED.nodes.removeGroup(group);
}
}
if (ev.workspaces) {
inverseEv.workspaces = [];
for (i=0;i<ev.workspaces.length;i++) {
@@ -193,12 +203,35 @@ RED.history = (function() {
n.dirty = true;
});
}
if (ev.groups) {
inverseEv.groups = [];
var groupsToAdd = new Set(ev.groups.map(function(g) { return g.id }));
for (i=0;i<ev.groups.length;i++) {
RED.nodes.addGroup(ev.groups[i])
modifiedTabs[ev.groups[i].z] = true;
inverseEv.groups.push(ev.groups[i]);
if (ev.groups[i].g && !groupsToAdd.has(ev.groups[i].g)) {
group = RED.nodes.group(ev.groups[i].g);
if (group.nodes.indexOf(ev.groups[i]) === -1) {
group.nodes.push(ev.groups[i]);
}
RED.group.markDirty(ev.groups[i])
}
}
}
if (ev.nodes) {
inverseEv.nodes = [];
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]);
modifiedTabs[ev.nodes[i].z] = true;
inverseEv.nodes.push(ev.nodes[i].id);
if (ev.nodes[i].g) {
group = RED.nodes.group(ev.nodes[i].g);
if (group.nodes.indexOf(ev.nodes[i]) === -1) {
group.nodes.push(ev.nodes[i]);
}
RED.group.markDirty(group)
}
}
}
if (ev.links) {
@@ -260,6 +293,13 @@ RED.history = (function() {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
if (ev.addToGroup) {
RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),true);
inverseEv.removeFromGroup = ev.addToGroup;
} else if (ev.removeFromGroup) {
RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
inverseEv.addToGroup = ev.removeFromGroup;
}
} else if (ev.t == "edit") {
inverseEv = {
t: "edit",
@@ -370,7 +410,9 @@ RED.history = (function() {
if (ev.nodes) {
inverseEv.movedNodes = [];
var z = ev.activeWorkspace;
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) {
var fullNodeList = RED.nodes.filterNodes({z:ev.subflow.subflow.id});
fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id))
fullNodeList.forEach(function(n) {
n.x += ev.subflow.offsetX;
n.y += ev.subflow.offsetY;
n.dirty = true;
@@ -411,6 +453,9 @@ RED.history = (function() {
if (ev.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow);
inverseEv.subflow = ev.subflow;
if (ev.subflow.subflow.g) {
RED.group.addToGroup(RED.nodes.group(ev.subflow.subflow.g),ev.subflow.subflow);
}
}
if (ev.subflows) {
inverseEv.nodes = [];
@@ -422,6 +467,9 @@ RED.history = (function() {
if (ev.movedNodes) {
ev.movedNodes.forEach(function(nid) {
nn = RED.nodes.node(nid);
if (!nn) {
nn = RED.nodes.group(nid);
}
nn.x -= ev.subflow.offsetX;
nn.y -= ev.subflow.offsetY;
nn.dirty = true;
@@ -435,7 +483,7 @@ RED.history = (function() {
RED.nodes.addLink(ev.links[i]);
}
}
if (ev.createdLinks) {
if (ev.createdLinks) {
inverseEv.removedLinks = [];
for (i=0;i<ev.createdLinks.length;i++) {
inverseEv.removedLinks.push(ev.createdLinks[i]);
@@ -450,6 +498,55 @@ RED.history = (function() {
if (ev.order) {
RED.workspaces.order(ev.order);
}
} else if (ev.t == "createGroup") {
inverseEv = {
t: "ungroup",
dirty: RED.nodes.dirty(),
groups: []
}
if (ev.groups) {
for (i=0;i<ev.groups.length;i++) {
inverseEv.groups.push(ev.groups[i]);
RED.group.ungroup(ev.groups[i]);
}
}
} else if (ev.t == "ungroup") {
inverseEv = {
t: "createGroup",
dirty: RED.nodes.dirty(),
groups: []
}
if (ev.groups) {
for (i=0;i<ev.groups.length;i++) {
inverseEv.groups.push(ev.groups[i]);
var nodes = ev.groups[i].nodes.slice();
ev.groups[i].nodes = [];
RED.nodes.addGroup(ev.groups[i]);
RED.group.addToGroup(ev.groups[i],nodes);
}
}
} else if (ev.t == "addToGroup") {
inverseEv = {
t: "removeFromGroup",
dirty: RED.nodes.dirty(),
group: ev.group,
nodes: ev.nodes,
reparent: ev.reparent
}
if (ev.nodes) {
RED.group.removeFromGroup(ev.group,ev.nodes,(ev.hasOwnProperty('reparent')&&ev.hasOwnProperty('reparent')!==undefined)?ev.reparent:true);
}
} else if (ev.t == "removeFromGroup") {
inverseEv = {
t: "addToGroup",
dirty: RED.nodes.dirty(),
group: ev.group,
nodes: ev.nodes,
reparent: ev.reparent
}
if (ev.nodes) {
RED.group.addToGroup(ev.group,ev.nodes);
}
}
Object.keys(modifiedTabs).forEach(function(id) {
@@ -460,8 +557,8 @@ RED.history = (function() {
});
RED.nodes.dirty(ev.dirty);
RED.view.updateActive();
RED.view.select(null);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
@@ -482,6 +579,9 @@ RED.history = (function() {
list: function() {
return undoHistory;
},
listRedo: function() {
return redoHistory;
},
depth: function() {
return undoHistory.length;
},

View File

@@ -44,6 +44,14 @@
"ctrl-y": "core:redo",
"ctrl-a": "core:select-all-nodes",
"shift-?": "core:show-help",
"w": "core:scroll-view-up",
"d": "core:scroll-view-right",
"s": "core:scroll-view-down",
"a": "core:scroll-view-left",
"shift-w": "core:step-view-up",
"shift-d": "core:step-view-right",
"shift-s": "core:step-view-down",
"shift-a": "core:step-view-left",
"up": "core:move-selection-up",
"right": "core:move-selection-right",
"down": "core:move-selection-down",
@@ -53,6 +61,10 @@
"shift-down": "core:step-selection-down",
"shift-left": "core:step-selection-left",
"ctrl-shift-j": "core:show-previous-tab",
"ctrl-shift-k": "core:show-next-tab"
"ctrl-shift-k": "core:show-next-tab",
"ctrl-shift-g": "core:group-selection",
"ctrl-shift-u": "core:ungroup-selection",
"ctrl-shift-c": "core:copy-group-style",
"ctrl-shift-v": "core:paste-group-style"
}
}

View File

@@ -27,6 +27,9 @@ RED.nodes = (function() {
var subflows = {};
var loadedFlowVersion = null;
var groups = {};
var groupsByZ = {};
var initialLoad;
var dirty = false;
@@ -302,6 +305,10 @@ RED.nodes = (function() {
}
function moveNodeToTab(node, z) {
if (node.type === "group") {
moveGroupToTab(node,z);
return;
}
if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id];
}
@@ -311,6 +318,13 @@ RED.nodes = (function() {
nodeTabMap[z][node.id] = node;
node.z = z;
}
function moveGroupToTab(group, z) {
var index = groupsByZ[group.z].indexOf(group);
groupsByZ[group.z].splice(index,1);
groupsByZ[z] = groupsByZ[z] || [];
groupsByZ[z].push(group);
group.z = z;
}
function removeLink(l) {
var index = links.indexOf(l);
@@ -340,6 +354,7 @@ RED.nodes = (function() {
var removedNodes = [];
var removedLinks = [];
var removedGroups = [];
var n;
var node;
for (n=0;n<nodes.length;n++) {
@@ -356,11 +371,17 @@ RED.nodes = (function() {
}
}
}
removedGroups = groupsByZ[id] || [];
removedGroups.forEach(function(g) {
delete groups[g.id]
})
delete groupsByZ[id];
for (n=0;n<removedNodes.length;n++) {
var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links);
}
return {nodes:removedNodes,links:removedLinks};
return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
}
function addSubflow(sf, createNewIds) {
@@ -398,6 +419,10 @@ RED.nodes = (function() {
paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null },
outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null },
oneditprepare: function() {
RED.subflow.buildEditForm("subflow",this);
RED.subflow.buildPropertiesForm(this);
},
oneditresize: function(size) {
// var rows = $(".dialog-form>div:not(.node-input-env-container-row)");
var height = size.height;
@@ -493,6 +518,9 @@ RED.nodes = (function() {
if (n.d === true) {
node.d = true;
}
if (n.g) {
node.g = n.g;
}
if (node.type == "unknown") {
for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) {
@@ -505,19 +533,33 @@ RED.nodes = (function() {
node[d] = n[d];
}
}
if(exportCreds && n.credentials) {
if (exportCreds) {
var credentialSet = {};
node.credentials = {};
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
if (n._def.credentials[cred].type == 'password') {
if (/^subflow:/.test(node.type) && n.credentials) {
// A subflow instance node can have arbitrary creds
for (var sfCred in n.credentials) {
if (n.credentials.hasOwnProperty(sfCred)) {
if (!n.credentials._ ||
n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
(n.credentials["has_"+cred] && n.credentials[cred])) {
n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] ||
(n.credentials["has_"+sfCred] && n.credentials[sfCred])) {
credentialSet[sfCred] = n.credentials[sfCred];
}
}
}
} else if (n.credentials) {
node.credentials = {};
// All other nodes have a well-defined list of possible credentials
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
if (n._def.credentials[cred].type == 'password') {
if (!n.credentials._ ||
n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
(n.credentials["has_"+cred] && n.credentials[cred])) {
credentialSet[cred] = n.credentials[cred];
}
} else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) {
credentialSet[cred] = n.credentials[cred];
}
} else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) {
credentialSet[cred] = n.credentials[cred];
}
}
}
@@ -526,6 +568,13 @@ RED.nodes = (function() {
}
}
}
if (n.type === "group") {
node.x = n.x;
node.y = n.y;
node.w = n.w;
node.h = n.h;
node.nodes = node.nodes.map(function(n) { return n.id });
}
if (n._def.category != "config") {
node.x = n.x;
node.y = n.y;
@@ -568,7 +617,7 @@ RED.nodes = (function() {
return node;
}
function convertSubflow(n) {
function convertSubflow(n, exportCreds) {
var node = {};
node.id = n.id;
node.type = n.type;
@@ -578,6 +627,24 @@ RED.nodes = (function() {
node.in = [];
node.out = [];
node.env = n.env;
if (exportCreds) {
var credentialSet = {};
// A subflow node can have arbitrary creds
for (var sfCred in n.credentials) {
if (n.credentials.hasOwnProperty(sfCred)) {
if (!n.credentials._ ||
n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] ||
(n.credentials["has_"+sfCred] && n.credentials[sfCred])) {
credentialSet[sfCred] = n.credentials[sfCred];
}
}
}
if (Object.keys(credentialSet).length > 0) {
node.credentials = credentialSet;
}
}
node.color = n.color;
n.in.forEach(function(p) {
@@ -633,8 +700,18 @@ RED.nodes = (function() {
/**
* Converts the current node selection to an exportable JSON Object
**/
function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) {
function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) {
var nns = [];
exportedIds = exportedIds || {};
set = set.filter(function(n) {
if (exportedIds[n.id]) {
return false;
}
exportedIds[n.id] = true;
return true;
})
exportedConfigNodes = exportedConfigNodes || {};
exportedSubflows = exportedSubflows || {};
for (var n=0;n<set.length;n++) {
@@ -650,11 +727,11 @@ RED.nodes = (function() {
subflowSet.push(n);
}
});
var exportableSubflow = createExportableNodeSet(subflowSet, exportedSubflows, exportedConfigNodes);
var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
nns = exportableSubflow.concat(nns);
}
}
if (node.type != "subflow") {
if (node.type !== "subflow") {
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) {
@@ -671,6 +748,9 @@ RED.nodes = (function() {
}
}
nns.push(convertedNode);
if (node.type === "group") {
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
}
} else {
var convertedSubflow = convertSubflow(node);
nns.push(convertedSubflow);
@@ -693,7 +773,12 @@ RED.nodes = (function() {
}
for (i in subflows) {
if (subflows.hasOwnProperty(i)) {
nns.push(convertSubflow(subflows[i]));
nns.push(convertSubflow(subflows[i], exportCredentials));
}
}
for (i in groups) {
if (groups.hasOwnProperty(i)) {
nns.push(convertNode(groups[i]));
}
}
for (i in configNodes) {
@@ -822,6 +907,7 @@ RED.nodes = (function() {
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
n.type != "group" &&
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) {
@@ -871,6 +957,7 @@ RED.nodes = (function() {
var node_map = {};
var new_nodes = [];
var new_links = [];
var new_groups = [];
var nid;
var def;
var configNode;
@@ -1038,20 +1125,25 @@ RED.nodes = (function() {
y:parseFloat(n.y || 0),
z:n.z,
type:0,
wires:n.wires||[],
inputLabels: n.inputLabels,
outputLabels: n.outputLabels,
icon: n.icon,
info: n.info,
changed:false,
_config:{}
};
}
if (n.type !== "group") {
node.wires = n.wires||[];
node.inputLabels = n.inputLabels;
node.outputLabels = n.outputLabels;
node.icon = n.icon;
}
if (n.hasOwnProperty('l')) {
node.l = n.l;
}
if (n.hasOwnProperty('d')) {
node.d = n.d;
}
if (n.hasOwnProperty('g')) {
node.g = n.g;
}
if (createNewIds) {
if (subflow_blacklist[n.z]) {
continue;
@@ -1088,7 +1180,17 @@ RED.nodes = (function() {
}
node.type = n.type;
node._def = def;
if (n.type.substring(0,7) === "subflow") {
if (node.type === "group") {
node._def = RED.group.def;
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
node[d] = n[d];
node._config[d] = JSON.stringify(n[d]);
}
}
node._config.x = node.x;
node._config.y = node.y;
} else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) {
@@ -1178,13 +1280,19 @@ RED.nodes = (function() {
}
}
}
addNode(node);
RED.editor.validateNode(node);
if (node.type !== "group") {
addNode(node);
RED.editor.validateNode(node);
} else {
addGroup(node);
}
node_map[n.id] = node;
// If an 'unknown' config node, it will not have been caught by the
// proper config node handling, so needs adding to new_nodes here
if (node.type === "unknown" || node._def.category !== "config") {
new_nodes.push(node);
} else if (node.type === "group") {
new_groups.push(node);
}
}
}
@@ -1219,6 +1327,11 @@ RED.nodes = (function() {
}
delete n.wires;
}
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g
}
for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) {
@@ -1287,9 +1400,20 @@ RED.nodes = (function() {
delete n.status.wires;
}
}
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g;
}
n.nodes = n.nodes.map(function(id) {
return node_map[id];
})
}
RED.workspaces.refresh();
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace];
return [new_nodes,new_links,new_groups,new_workspaces,new_subflows,missingWorkspace];
}
// TODO: supports filter.z|type
@@ -1380,6 +1504,9 @@ RED.nodes = (function() {
nodeTabMap = {};
configNodes = {};
workspacesOrder = [];
groups = {};
groupsByZ = {};
var subflowIds = Object.keys(subflows);
subflowIds.forEach(function(id) {
RED.subflow.removeSubflow(id)
@@ -1408,6 +1535,27 @@ RED.nodes = (function() {
// var loadedFlowVersion = null;
}
function addGroup(group) {
groupsByZ[group.z] = groupsByZ[group.z] || [];
groupsByZ[group.z].push(group);
groups[group.id] = group;
}
function removeGroup(group) {
var i = groupsByZ[group.z].indexOf(group);
groupsByZ[group.z].splice(i,1);
if (group.g) {
if (groups[group.g]) {
var index = groups[group.g].nodes.indexOf(group);
groups[group.g].nodes.splice(index,1);
}
}
RED.group.markDirty(group);
delete groups[group.id];
}
return {
init: function() {
RED.events.on("registry:node-type-added",function(type) {
@@ -1434,6 +1582,9 @@ RED.nodes = (function() {
delete configNodes[n.id];
} else {
nodes.splice(nodes.indexOf(n),1);
if (nodeTabMap[n.z]) {
delete nodeTabMap[n.z][n.id];
}
}
reimportList.push(convertNode(n));
});
@@ -1448,8 +1599,9 @@ RED.nodes = (function() {
});
removeLinks.forEach(removeLink);
RED.view.redraw(true);
// Force the redraw to be synchronous so the view updates
// *now* and removes the unknown node
RED.view.redraw(true, true);
var result = importNodes(reimportList,false);
var newNodeMap = {};
result[0].forEach(function(n) {
@@ -1503,6 +1655,11 @@ RED.nodes = (function() {
subflow: getSubflow,
subflowContains: subflowContains,
addGroup: addGroup,
removeGroup: removeGroup,
group: function(id) { return groups[id] },
groups: function(z) { return groupsByZ[z]||[] },
eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) {
if (cb(nodes[n]) === false) {

View File

@@ -418,8 +418,6 @@ var RED = (function() {
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
}
// Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary();
});
RED.comms.subscribe("event-log/#", function(topic,payload) {
var id = topic.substring(9);
@@ -433,7 +431,7 @@ var RED = (function() {
'<img width="50px" src="red/images/node-red-icon.svg" />'+
'</div>';
RED.sidebar.info.set(aboutHeader+marked(data));
RED.sidebar.info.set(aboutHeader+RED.utils.renderMarkdown(data));
RED.sidebar.info.show();
});
}
@@ -474,6 +472,14 @@ var RED = (function() {
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
]});
menuOptions.push({id:"menu-item-group",label:RED._("menu.label.groups"), options: [
{id:"menu-item-group-group",label:RED._("menu.label.groupSelection"),disabled:true,onselect:"core:group-selection"},
{id:"menu-item-group-ungroup",label:RED._("menu.label.ungroupSelection"),disabled:true,onselect:"core:ungroup-selection"},
null,
{id:"menu-item-group-merge",label:RED._("menu.label.groupMergeSelection"),disabled:true,onselect:"core:merge-selection-to-group"},
{id:"menu-item-group-remove",label:RED._("menu.label.groupRemoveSelection"),disabled:true,onselect:"core:remove-selection-from-group"}
]});
menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) {
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
@@ -526,6 +532,7 @@ var RED = (function() {
}
RED.subflow.init();
RED.group.init();
RED.clipboard.init();
RED.search.init();
RED.actionList.init();

View File

@@ -56,8 +56,9 @@ RED.settings = (function () {
if (key === "auth-tokens") {
return JSON.parse(localStorage.getItem(key));
} else {
var v;
try {
var v = RED.utils.getMessageProperty(userSettings,key);
v = RED.utils.getMessageProperty(userSettings,key);
if (v === undefined) {
v = defaultIfUndefined;
}

View File

@@ -583,6 +583,7 @@ RED.clipboard = (function() {
nodes = [];
selection.forEach(function(n) {
nodes.push(n);
nodes = nodes.concat(RED.nodes.groups(n.id));
nodes = nodes.concat(RED.nodes.filterNodes({z:n.id}));
});
} else {
@@ -592,7 +593,8 @@ RED.clipboard = (function() {
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'red-ui-clipboard-dialog-export-rng-flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace});
nodes = RED.nodes.groups(activeWorkspace);
nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace}));
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);

View File

@@ -0,0 +1,223 @@
RED.colorPicker = (function() {
function getDarkerColor(c) {
var r,g,b;
if (/^#[a-f0-9]{6}$/i.test(c)) {
r = parseInt(c.substring(1, 3), 16);
g = parseInt(c.substring(3, 5), 16);
b = parseInt(c.substring(5, 7), 16);
} else if (/^#[a-f0-9]{3}$/i.test(c)) {
r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16);
g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16);
b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16);
} else {
return c;
}
var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ;
r = Math.max(0,r-50);
g = Math.max(0,g-50);
b = Math.max(0,b-50);
return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0')
}
function create(options) {
var color = options.value;
var id = options.id;
var colorPalette = options.palette || [];
var width = options.cellWidth || 30;
var height = options.cellHeight || 30;
var margin = options.cellMargin || 2;
var perRow = options.cellPerRow || 6;
var container = $("<div>",{style:"display:inline-block"});
var colorHiddenInput = $("<input/>", { id: id, type: "hidden", value: color }).appendTo(container);
var opacityHiddenInput = $("<input/>", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container);
var colorButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
$('<i class="fa fa-caret-down"></i>').appendTo(colorButton);
var colorDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(colorButton);
$('<div>',{class:"red-ui-color-picker-cell-none"}).appendTo(colorDispContainer);
var colorDisp = $('<div>',{class:"red-ui-color-picker-swatch"}).appendTo(colorDispContainer);
var refreshDisplay = function(color) {
if (color === "none") {
colorDisp.addClass('red-ui-color-picker-cell-none').css({
"background-color": "",
opacity: 1
});
colorDispContainer.css({
"border-color":""
})
} else {
var opacity = parseFloat(opacityHiddenInput.val())
colorDisp.removeClass('red-ui-color-picker-cell-none').css({
"background-color": color,
"opacity": opacity
});
var border = getDarkerColor(color);
if (border[0] === '#') {
border += Math.round(255*Math.floor(opacity*100)/100).toString(16);
} else {
border = "";
}
colorDispContainer.css({
"border-color": border
})
}
if (options.hasOwnProperty('opacity')) {
$(".red-ui-color-picker-opacity-slider-overlay").css({
"background-image": "linear-gradient(90deg, transparent 0%, "+color+" 100%)"
})
}
}
colorButton.on("click", function (e) {
var numColors = colorPalette.length;
var picker = $("<div/>", {
class: "red-ui-color-picker"
}).css({
width: ((width+margin+margin)*perRow)+"px",
height: Math.ceil(numColors/perRow)*(height+margin+margin)+"+px"
});
var count = 0;
var row = null;
row = $("<div/>").appendTo(picker);
var colorInput = $('<input>',{
type:"text",
value:colorHiddenInput.val()
}).appendTo(row);
colorInput.on("change", function (e) {
var color = colorInput.val();
colorHiddenInput.val(color).trigger('change');
refreshDisplay(color);
});
// if (options.hasOwnProperty('opacity')) {
// var sliderContainer = $("<div>",{class:"red-ui-color-picker-opacity-slider"
// }
if (options.none) {
row = $("<div/>").appendTo(picker);
var button = $("<button/>", {
class:"red-ui-color-picker-cell red-ui-color-picker-cell-none"
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px"
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
colorInput.val("none");
colorInput.trigger("change");
});
}
colorPalette.forEach(function (col) {
if ((count % perRow) == 0) {
row = $("<div/>").appendTo(picker);
}
var button = $("<button/>", {
class:"red-ui-color-picker-cell"
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px",
backgroundColor: col,
"border-color": getDarkerColor(col)
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
// colorPanel.hide();
colorInput.val(col);
colorInput.trigger("change");
});
count++;
});
if (options.none || options.hasOwnProperty('opacity')) {
row = $("<div/>").appendTo(picker);
// if (options.none) {
// var button = $("<button/>", {
// class:"red-ui-color-picker-cell red-ui-color-picker-cell-none"
// }).css({
// width: width+"px",
// height: height+"px",
// margin: margin+"px"
// }).appendTo(row);
// button.on("click", function (e) {
// e.preventDefault();
// colorPanel.hide();
// selector.val("none");
// selector.trigger("change");
// });
// }
if (options.hasOwnProperty('opacity')) {
var sliderContainer = $("<div>",{class:"red-ui-color-picker-opacity-slider"}).appendTo(row);
sliderContainer.on("mousedown", function(evt) {
if (evt.target === sliderHandle[0]) {
return;
}
var v = evt.offsetX/sliderContainer.width();
sliderHandle.css({
left: ( v*(sliderContainer.width() - sliderHandle.outerWidth()))+"px"
});
v = Math.floor(100*v)
opacityHiddenInput.val(v/100)
opacityLabel.text(v+"%");
refreshDisplay(colorHiddenInput.val());
})
$("<div>",{class:"red-ui-color-picker-opacity-slider-overlay"}).appendTo(sliderContainer);
var sliderHandle = $("<div>",{class:"red-ui-color-picker-opacity-slider-handle red-ui-button red-ui-button-small"}).appendTo(sliderContainer).draggable({
containment: "parent",
axis: "x",
drag: function( event, ui ) {
var v = Math.max(0,ui.position.left/($(this).parent().width()-$(this).outerWidth()));
// Odd bug that if it is loaded with a non-0 value, the first time
// it is dragged it ranges -1 to 99. But every other time, its 0 to 100.
// The Math.max above makes the -1 disappear. The follow hack ensures
// it always maxes out at a 100, at the cost of not allowing 99% exactly.
v = Math.floor(100*v)
if ( v === 99 ) {
v = 100;
}
// console.log("uip",ui.position.left);
opacityHiddenInput.val(v/100)
opacityLabel.text(v+"%");
refreshDisplay(colorHiddenInput.val());
}
});
var opacityLabel = $('<small></small>').appendTo(row);
setTimeout(function() {
sliderHandle.css({
left: (parseFloat(opacityHiddenInput.val())*(sliderContainer.width() - sliderHandle.outerWidth()))+"px"
})
opacityLabel.text(Math.floor(opacityHiddenInput.val()*100)+"%");
},50);
}
}
var colorPanel = RED.popover.panel(picker);
setTimeout(function() {
refreshDisplay(colorHiddenInput.val())
},50);
colorPanel.show({
target: colorButton
})
});
setTimeout(function() {
refreshDisplay(colorHiddenInput.val())
},50);
return container;
}
return {
create: create
}
})();

View File

@@ -38,7 +38,10 @@
this.element.addClass("red-ui-searchBox-input");
this.uiContainer = this.element.wrap("<div>").parent();
this.uiContainer.addClass("red-ui-searchBox-container");
if (this.element.parents("form").length === 0) {
var form = this.element.wrap("<form>").parent();
form.addClass("red-ui-searchBox-form");
}
$('<i class="fa fa-search"></i>').prependTo(this.uiContainer);
this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer);
this.clearButton.on("click",function(e) {

View File

@@ -218,7 +218,7 @@ RED.tabs = (function() {
var thisTab = $(this).parent();
var fireSelectionChanged = false;
if (options.onselect) {
if (evt.metaKey) {
if (evt.metaKey || evt.ctrlKey) {
if (thisTab.hasClass("selected")) {
thisTab.removeClass("selected");
if (thisTab[0] !== currentTab[0]) {

View File

@@ -410,7 +410,7 @@
return;
}
if (container.hasClass("expanded")) {
done && done();
if (done) { done() }
return;
}
if (!container.hasClass("built") && (item.deferBuild || typeof item.children === 'function')) {
@@ -435,7 +435,7 @@
spinner.remove();
}
}
done && done();
if (done) { done() }
that._trigger("childrenloaded",null,item)
}
if (typeof item.children === 'function') {
@@ -457,7 +457,7 @@
} else {
item.treeList.childList.slideDown('fast');
}
done && done();
if (done) { done() }
}
container.addClass("expanded");
}

View File

@@ -164,6 +164,84 @@
}
})
}
},
cred:{
value:"cred",
label:"credential",
icon:"fa fa-lock",
inputType: "password",
valueLabel: function(container,value) {
var that = this;
container.css("pointer-events","none");
this.elementDiv.hide();
var buttons = $('<div>').css({
position: "absolute",
right:"6px",
top: "6px",
"pointer-events":"all"
}).appendTo(container);
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
width:"20px"
}).appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
var currentType = that.input.attr("type");
if (currentType === "text") {
that.input.attr("type","password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
setTimeout(function() {
that.input.focus();
},50);
} else {
that.input.attr("type","text");
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
setTimeout(function() {
that.input.focus();
},50);
}
}).hide();
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-1px").appendTo(eyeButton);
if (value === "__PWRD__") {
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
padding:"6px 6px",
borderRadius:"4px"
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
innerContainer.hide();
container.css("background","none");
container.css("pointer-events","none");
that.input.val("");
that.element.val("");
that.elementDiv.show();
editButton.hide();
cancelButton.show();
eyeButton.show();
setTimeout(function() {
that.input.focus();
},50);
});
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left","3px").appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
innerContainer.show();
container.css("background","");
that.input.val("__PWRD__");
that.element.val("__PWRD__");
that.elementDiv.hide();
editButton.show();
cancelButton.hide();
eyeButton.hide();
that.input.attr("type","password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
}).hide();
} else {
container.css("background","none");
container.css("pointer-events","none");
this.elementDiv.show();
eyeButton.show();
}
}
}
};
var nlsd = false;
@@ -220,6 +298,8 @@
that.input.attr(d,m);
});
this.defaultInputType = this.input.attr('type');
this.uiSelect.addClass("red-ui-typedInput-container");
this.element.attr('type','hidden');
@@ -687,7 +767,7 @@
$('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
}
else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel);
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel);
}
}
if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
@@ -822,6 +902,11 @@
if (this.optionSelectTrigger) {
this.optionSelectTrigger.hide();
}
if (opt.inputType) {
this.input.attr('type',opt.inputType)
} else {
this.input.attr('type',this.defaultInputType)
}
if (opt.hasValue === false) {
this.oldValue = this.input.val();
this.input.val("");
@@ -830,8 +915,8 @@
} else if (opt.valueLabel) {
this.valueLabelContainer.show();
this.valueLabelContainer.empty();
opt.valueLabel.call(this,this.valueLabelContainer,this.input.val());
this.elementDiv.hide();
opt.valueLabel.call(this,this.valueLabelContainer,this.input.val());
} else {
if (this.oldValue !== undefined) {
this.input.val(this.oldValue);

View File

@@ -334,8 +334,7 @@ RED.deploy = (function() {
var invalidNodes = [];
RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid;
if (!node.valid) {
if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
@@ -345,6 +344,7 @@ RED.deploy = (function() {
}
});
hasUnknown = unknownNodes.length > 0;
hasInvalid = invalidNodes.length > 0;
var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) {

View File

@@ -1029,9 +1029,9 @@ RED.diff = (function() {
}
var localSelectDiv = $('<label>',{class:"red-ui-diff-selectbox",for:safeNodeId+"-local"}).on("click", function(e) { e.stopPropagation();}).appendTo(localDiv);
var localRadio = $('<input>',{class:"red-ui-diff-selectbox-input",id:safeNodeId+"-local",type:'radio',value:"local",name:safeNodeId,class:className+"-local"}).data('node-id',node.id).on("change", changeHandler).appendTo(localSelectDiv);
var localRadio = $('<input>',{class:"red-ui-diff-selectbox-input "+className+"-local",id:safeNodeId+"-local",type:'radio',value:"local",name:safeNodeId}).data('node-id',node.id).on("change", changeHandler).appendTo(localSelectDiv);
var remoteSelectDiv = $('<label>',{class:"red-ui-diff-selectbox",for:safeNodeId+"-remote"}).on("click", function(e) { e.stopPropagation();}).appendTo(remoteDiv);
var remoteRadio = $('<input>',{class:"red-ui-diff-selectbox-input",id:safeNodeId+"-remote",type:'radio',value:"remote",name:safeNodeId,class:className+"-remote"}).data('node-id',node.id).on("change", changeHandler).appendTo(remoteSelectDiv);
var remoteRadio = $('<input>',{class:"red-ui-diff-selectbox-input "+className+"-remote",id:safeNodeId+"-remote",type:'radio',value:"remote",name:safeNodeId}).data('node-id',node.id).on("change", changeHandler).appendTo(remoteSelectDiv);
if (state === 'local') {
localRadio.prop('checked',true);
} else if (state === 'remote') {

View File

@@ -490,8 +490,7 @@ RED.editor = (function() {
done();
}
}
if (definition.credentials) {
if (definition.credentials || /^subflow:/.test(definition.type)) {
if (node.credentials) {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
completePrepare();
@@ -499,7 +498,9 @@ RED.editor = (function() {
$.getJSON(getCredentialsURL(node.type, node.id), function (data) {
node.credentials = data;
node.credentials._ = $.extend(true,{},data);
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
if (!/^subflow:/.test(definition.type)) {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
}
completePrepare();
});
}
@@ -513,7 +514,9 @@ RED.editor = (function() {
for (var i=editStack.length-1;i<editStack.length;i++) {
var node = editStack[i];
label = node.type;
if (node.type === '_expression') {
if (node.type === 'group') {
label = RED._("group.editGroup",{name:RED.utils.sanitize(node.name||node.id)});
} else if (node.type === '_expression') {
label = RED._("expressionEditor.title");
} else if (node.type === '_js') {
label = RED._("jsEditor.title");
@@ -576,17 +579,21 @@ RED.editor = (function() {
$(this).attr("data-i18n",keys.join(";"));
});
if (type === "subflow-template" || type === "subflow") {
RED.subflow.buildEditForm(dialogForm,type,node);
if (type === "subflow-template") {
// This is the 'edit properties' dialog for a subflow template
// TODO: this needs to happen later in the dialog open sequence
// so that credentials can be loaded prior to building the form
RED.subflow.buildEditForm(type,node);
}
// Add dummy fields to prevent 'Enter' submitting the form in some
// cases, and also prevent browser auto-fill of password
// Add in reverse order as they are prepended...
$('<input type="password" style="display: none;" />').prependTo(dialogForm);
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
// - the elements cannot be hidden otherwise Chrome will ignore them.
// - the elements need to have id's that imply password/username
$('<span style="position: absolute; top: -2000px;"><input id="red-ui-trap-password" type="password"/></span>').prependTo(dialogForm);
$('<span style="position: absolute; top: -2000px;"><input id="red-ui-trap-username" type="text"/></span>').prependTo(dialogForm);
dialogForm.on("submit", function(e) { e.preventDefault();});
dialogForm.find('input').attr("autocomplete","disable");
dialogForm.find('input').attr("autocomplete","off");
return dialogForm;
}
@@ -818,99 +825,6 @@ RED.editor = (function() {
searchInput.trigger("focus");
}
function createColorPicker(colorRow, color) {
var colorButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(colorRow);
$('<i class="fa fa-caret-down"></i>').appendTo(colorButton);
var colorDisp = $('<div>',{class:"red-ui-search-result-node"}).appendTo(colorButton);
var selector = $("<input/>", {
id: "red-ui-editor-node-color",
type: "text",
value: color
}).css({
marginLeft: "10px",
width: "150px",
}).appendTo(colorRow);
selector.on("change", function (e) {
var color = selector.val();
$(".red-ui-editor-node-appearance-button .red-ui-search-result-node").css({
"background-color": color
});
});
selector.trigger("change");
colorButton.on("click", function (e) {
var recommendedColors = [
"#DDAA99",
"#3FADB5", "#87A980", "#A6BBCF",
"#AAAA66", "#C0C0C0", "#C0DEED",
"#C7E9C0", "#D7D7A0", "#D8BFD8",
"#DAC4B4", "#DEB887", "#DEBD5C",
"#E2D96E", "#E6E0F8", "#E7E7AE",
"#E9967A", "#F3B567", "#FDD0A2",
"#FDF0C2", "#FFAAAA", "#FFCC66",
"#FFF0F0", "#FFFFFF"
].map(function(c) {
var r = parseInt(c.substring(1, 3), 16) / 255;
var g = parseInt(c.substring(3, 5), 16) / 255;
var b = parseInt(c.substring(5, 7), 16) / 255;
return {
hex: c,
r: r,
g: g,
b: b,
l: 0.3 * r + 0.59 * g + 0.11 * b
}
});
// Sort by luminosity.
recommendedColors.sort(function (a, b) {
return a.l - b.l;
});
var numColors = recommendedColors.length;
var width = 30;
var height = 30;
var margin = 2;
var perRow = 6;
var picker = $("<div/>", {
class: "red-ui-color-picker"
}).css({
width: ((width+margin+margin)*perRow)+"px",
height: Math.ceil(numColors/perRow)*(height+margin+margin)+"+px"
});
var count = 0;
var row = null;
recommendedColors.forEach(function (col) {
if ((count % perRow) == 0) {
row = $("<div/>").appendTo(picker);
}
var button = $("<button/>", {
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px",
backgroundColor: col.hex,
"border-style": "solid",
"border-width": "1px",
"border-color": col.luma<0.92?col.hex:'#ccc'
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
colorPanel.hide();
selector.val(col.hex);
selector.trigger("change");
});
count++;
});
var colorPanel = RED.popover.panel(picker);
colorPanel.show({
target: colorButton
})
});
}
function buildAppearanceForm(container,node) {
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
@@ -996,7 +910,30 @@ RED.editor = (function() {
class: "form-row"
}).appendTo(dialogForm);
$("<label/>").text(RED._("editor.color")).appendTo(colorRow);
createColorPicker(colorRow, color);
var recommendedColors = [
"#DDAA99",
"#3FADB5", "#87A980", "#A6BBCF",
"#AAAA66", "#C0C0C0", "#C0DEED",
"#C7E9C0", "#D7D7A0", "#D8BFD8",
"#DAC4B4", "#DEB887", "#DEBD5C",
"#E2D96E", "#E6E0F8", "#E7E7AE",
"#E9967A", "#F3B567", "#FDD0A2",
"#FDF0C2", "#FFAAAA", "#FFCC66",
"#FFF0F0", "#FFFFFF"
]
RED.colorPicker.create({
id: "red-ui-editor-node-color",
value: color,
palette: recommendedColors,
sortPalette: function (a, b) {return a.l - b.l;}
}).appendTo(colorRow);
$("#red-ui-editor-node-color").on('change', function(ev) {
// Horribly out of scope...
nodeDiv.css('backgroundColor',$(this).val());
})
}
@@ -1320,11 +1257,13 @@ RED.editor = (function() {
newValue = parseInt(newValue);
}
}
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
@@ -1468,6 +1407,19 @@ RED.editor = (function() {
if (type === "subflow") {
var old_env = editing_node.env;
var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node);
if (new_env && new_env.length > 0) {
new_env.forEach(function(prop) {
if (prop.type === "cred") {
editing_node.credentials = editing_node.credentials || {_:{}};
editing_node.credentials[prop.name] = prop.value;
editing_node.credentials['has_'+prop.name] = (prop.value !== "");
if (prop.value !== '__PWRD__') {
changed = true;
}
delete prop.value;
}
});
}
if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env;
changes.env = editing_node.env;
@@ -1596,12 +1548,13 @@ RED.editor = (function() {
id: "editor-subflow-envProperties",
label: RED._("editor-tab.envProperties"),
name: RED._("editor-tab.envProperties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
content: $('<div>', {id:"editor-subflow-envProperties-content",class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-list"
};
RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node);
editorTabs.addTab(subflowPropertiesTab);
// This tab is populated by the oneditprepare function of this
// subflow. That ensures it is done *after* any credentials
// have been loaded for the instance.
}
if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) {
@@ -2249,6 +2202,21 @@ RED.editor = (function() {
var old_env = editing_node.env;
var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items"));
if (new_env && new_env.length > 0) {
new_env.forEach(function(prop) {
if (prop.type === "cred") {
editing_node.credentials = editing_node.credentials || {_:{}};
editing_node.credentials[prop.name] = prop.value;
editing_node.credentials['has_'+prop.name] = (prop.value !== "");
if (prop.value !== '__PWRD__') {
changed = true;
}
delete prop.value;
}
});
}
if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env;
changes.env = editing_node.env;
@@ -2308,7 +2276,7 @@ RED.editor = (function() {
$("#node-input-env-container").editableList('height',height-95);
}
},
open: function(tray) {
open: function(tray, done) {
var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left"
@@ -2359,7 +2327,6 @@ RED.editor = (function() {
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node);
editorTabs.addTab(nodePropertiesTab);
var descriptionTab = {
@@ -2388,11 +2355,19 @@ RED.editor = (function() {
buildAppearanceForm(appearanceTab.content,editing_node);
editorTabs.addTab(appearanceTab);
$("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name"));
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node);
trayBody.i18n();
finishedBuilding = true;
$.getJSON(getCredentialsURL("subflow", subflow.id), function (data) {
subflow.credentials = data;
subflow.credentials._ = $.extend(true,{},data);
$("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name"));
finishedBuilding = true;
done();
});
},
close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
@@ -2411,6 +2386,249 @@ RED.editor = (function() {
RED.tray.show(trayOptions);
}
function showEditGroupDialog(group) {
var editing_node = group;
editStack.push(group);
RED.view.state(RED.state.EDITING);
var nodeInfoEditor;
var finishedBuilding = false;
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
class: "primary",
text: RED._("common.label.done"),
click: function() {
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
var outputMap;
if (editing_node._def.oneditsave) {
var oldValues = {};
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
try {
var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
changed = true;
}
} catch(err) {
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
}
}
}
}
}
var newValue;
if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d);
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") {
// An empty select-multiple box returns null.
// Need to treat that as an empty array.
newValue = input.val();
if (newValue == null) {
newValue = [];
}
} else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
if (newValue != null) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_node),1);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_node);
}
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
}
}
}
}
}
var oldInfo = editing_node.info;
if (nodeInfoEditor) {
var newInfo = nodeInfoEditor.getValue();
if (!!oldInfo) {
// Has existing info property
if (newInfo.trim() === "") {
// New value is blank - remove the property
changed = true;
changes.info = oldInfo;
delete editing_node.info;
} else if (newInfo !== oldInfo) {
// New value is different
changed = true;
changes.info = oldInfo;
editing_node.info = newInfo;
}
} else {
// No existing info
if (newInfo.trim() !== "") {
// New value is not blank
changed = true;
changes.info = undefined;
editing_node.info = newInfo;
}
}
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.tray.close();
RED.view.redraw(true);
}
}
],
resize: function(size) {
editTrayWidthCache['group'] = size.width;
$(".red-ui-tray-content").height(size.height - 50);
// var form = $(".red-ui-tray-content form").height(dimensions.height - 50 - 40);
// if (editing_node && editing_node._def.oneditresize) {
// try {
// editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
// } catch(err) {
// console.log("oneditresize",editing_node.id,editing_node.type,err.toString());
// }
// }
},
open: function(tray, done) {
var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left"
}).appendTo(trayFooter)
var trayBody = tray.find('.red-ui-tray-body');
trayBody.parent().css('overflow','hidden');
var editorTabEl = $('<ul></ul>').appendTo(trayBody);
var editorContent = $('<div></div>').appendTo(trayBody);
var editorTabs = RED.tabs.create({
element:editorTabEl,
onchange:function(tab) {
editorContent.children().hide();
if (tab.onchange) {
tab.onchange.call(tab);
}
tab.content.show();
if (finishedBuilding) {
RED.tray.resize();
}
},
collapsible: true,
menu: false
});
var nodePropertiesTab = {
id: "editor-tab-properties",
label: RED._("editor-tab.properties"),
name: RED._("editor-tab.properties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form","group","node-red",group);
editorTabs.addTab(nodePropertiesTab);
var descriptionTab = {
id: "editor-tab-description",
label: RED._("editor-tab.description"),
name: RED._("editor-tab.description"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-file-text-o",
onchange: function() {
nodeInfoEditor.focus();
}
};
editorTabs.addTab(descriptionTab);
nodeInfoEditor = buildDescriptionForm(descriptionTab.content,editing_node);
prepareEditDialog(group,group._def,"node-input", function() {
trayBody.i18n();
finishedBuilding = true;
done();
});
},
close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
nodeInfoEditor.destroy();
nodeInfoEditor = null;
editStack.pop();
editing_node = null;
},
show: function() {
}
}
if (editTrayWidthCache.hasOwnProperty('group')) {
trayOptions.width = editTrayWidthCache['group'];
}
RED.tray.show(trayOptions);
}
function showTypeEditor(type, options) {
if (customEditTypes.hasOwnProperty(type)) {
if (editStack.length > 0) {
@@ -2484,7 +2702,7 @@ RED.editor = (function() {
editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
if (options.expandable !== false) {
var expandButton = $('<button type="button" class="red-ui-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
expandButton.on("click", function(e) {
e.preventDefault();
var value = editor.getValue();
@@ -2534,6 +2752,7 @@ RED.editor = (function() {
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
editGroup: showEditGroupDialog,
editJavaScript: function(options) { showTypeEditor("_js",options) },
editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) },

View File

@@ -102,7 +102,7 @@
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:''}));
var body = RED.utils.renderMarkdown(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
$("#red-ui-editor-type-expression-help").html(title+"<p>"+body+"</p>");
})
@@ -207,6 +207,7 @@
}
expressionEditor.getSession().setValue(v||"",-1);
});
funcSelect.change();
var tabs = RED.tabs.create({
element: $("#red-ui-editor-type-expression-tabs"),

View File

@@ -106,7 +106,7 @@
options.push({id:"red-ui-editor-type-json-menu-duplicate", icon:"fa fa-copy", label:RED._("jsonEditor.duplicate"),onselect:function(){
var newKey = item.key;
if (item.parent.type === 'array') {
newKey = parent.children.length;
newKey = item.parent.children.length;
} else {
var m = /^(.*?)(-(\d+))?$/.exec(newKey);
var usedKeys = {};
@@ -301,9 +301,9 @@
var val = $('<input type="text" class="red-ui-editor-type-json-editor-value">').css({width:w+"px"}).val(""+valValue).insertAfter(valueLabel).typedInput({
types:[
'str','num','bool',
{value:"null",label:"null",hasValue:false},
{value:"array",label:"array",hasValue:false},
{value:"object",label:"object",hasValue:false}
{value:"null",label:RED._("common.type.null"),hasValue:false},
{value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"}
],
default: valType
});
@@ -327,10 +327,10 @@
item.value = valValue;
var valClass;
switch(valType) {
case 'str': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "string"; valClass = "red-ui-debug-msg-type-string"; valValue = '"'+valValue+'"'; break;
case 'num': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "number"; valClass = "red-ui-debug-msg-type-number"; break;
case 'bool': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "boolean"; valClass = "red-ui-debug-msg-type-other"; item.value = (valValue === "true"); break;
case 'null': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "null"; valClass = "red-ui-debug-msg-type-null"; item.value = valValue = "null"; break;
case 'str': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "string"; valClass = "red-ui-debug-msg-type-string"; valValue = '"'+valValue+'"'; break;
case 'num': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "number"; valClass = "red-ui-debug-msg-type-number"; break;
case 'bool': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "boolean"; valClass = "red-ui-debug-msg-type-other"; item.value = (valValue === "true"); break;
case 'null': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "null"; valClass = "red-ui-debug-msg-type-null"; item.value = valValue = "null"; break;
case 'object':
item.treeList.makeParent(orphanedChildren);
item.type = "object";
@@ -485,7 +485,7 @@
} else if (activeTab === "json-raw") {
result = expressionEditor.getValue();
}
onComplete && onComplete(result);
if (onComplete) { onComplete(result) }
RED.tray.close();
}
}
@@ -577,7 +577,7 @@
} catch(err) {
rootNode = null;
list.treeList('data',[{
label: "Invalid JSON: "+err.toString()
label: RED._("jsonEditor.error.invalidJSON")+err.toString()
}]);
}
}

View File

@@ -32,7 +32,7 @@
'<button type="button" class="red-ui-button" data-style="bq"><i class="fa fa-quote-left"></i></button>'+
'<button type="button" class="red-ui-button" data-style="hr"><i class="fa fa-minus"></i></button>'+
'<button type="button" class="red-ui-button" data-style="link"><i class="fa fa-link"></i></button>'+
'</span>'
'</span>'+
'</div>';
var template = '<script type="text/x-red" data-template-name="_markdown">'+
@@ -107,7 +107,7 @@
clearTimeout(changeTimer);
changeTimer = setTimeout(function() {
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
$(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
},200);
})
@@ -116,7 +116,7 @@
}
if (value) {
$(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
}
panels = RED.panels.create({
id:"red-ui-editor-type-markdown-panels",

View File

@@ -0,0 +1,633 @@
/**
* 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.group = (function() {
var _groupEditTemplate = '<script type="text/x-red" data-template-name="group">'+
'<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>'+
// '<div class="node-input-group-style-tools"><span class="button-group"><button class="red-ui-button red-ui-button-small">Use default style</button><button class="red-ui-button red-ui-button-small">Set as default style</button></span></div>'+
'<div class="form-row" id="node-input-row-style-stroke">'+
'<label>Style</label>'+
'<label style="width: 70px;margin-right:10px" for="node-input-style-stroke" data-i18n="editor:common.label.line"></label>'+
'</div>'+
'<div class="form-row" style="padding-left: 100px;" id="node-input-row-style-fill">'+
'<label style="width: 70px;margin-right: 10px " for="node-input-style-fill" data-i18n="editor:common.label.fill"></label>'+
'</div>'+
'<div class="form-row">'+
'<label for="node-input-style-label">Label</label>'+
'<input type="checkbox" id="node-input-style-label"/>'+
'</div>'+
'<div class="form-row" id="node-input-row-style-label-options">'+
'<div style="margin-left: 100px; display: inline-block">'+
'<div class="form-row">'+
'<span style="display: inline-block; min-width: 140px" id="node-input-row-style-label-color">'+
'<label style="width: 70px;margin-right: 10px" for="node-input-style-fill" data-i18n="editor:common.label.color"></label>'+
'</span>'+
'</div>'+
'<div class="form-row">'+
'<span style="display: inline-block; min-width: 140px;" id="node-input-row-style-label-position">'+
'<label style="width: 70px;margin-right: 10px " for="node-input-style-label-position" data-i18n="editor:common.label.position"></label>'+
'</span>'+
'</div>'+
'</div>'+
'</div>'+
'</script>';
var colorPalette = [
"#ff0000",
"#ffC000",
"#ffff00",
"#92d04f",
"#0070c0",
"#001f60",
"#6f2fa0",
"#000000",
"#777777"
]
var colorSteps = 3;
var colorCount = colorPalette.length;
for (var i=0,len=colorPalette.length*colorSteps;i<len;i++) {
var ci = i%colorCount;
var j = Math.floor(i/colorCount)+1;
var c = colorPalette[ci];
var r = parseInt(c.substring(1, 3), 16);
var g = parseInt(c.substring(3, 5), 16);
var b = parseInt(c.substring(5, 7), 16);
var dr = (255-r)/(colorSteps+((ci===colorCount-1) ?0:1));
var dg = (255-g)/(colorSteps+((ci===colorCount-1) ?0:1));
var db = (255-b)/(colorSteps+((ci===colorCount-1) ?0:1));
r = Math.min(255,Math.floor(r+j*dr));
g = Math.min(255,Math.floor(g+j*dg));
b = Math.min(255,Math.floor(b+j*db));
colorPalette.push('#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0'));
}
var defaultGroupStyle = {};
var groupDef = {
defaults:{
name:{value:""},
style:{value:{}},
nodes:{value:[]}
},
category: "config",
oneditprepare: function() {
var style = this.style || {};
RED.colorPicker.create({
id:"node-input-style-stroke",
value: style.stroke || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['stroke-opacity'] || 1.0
}).appendTo("#node-input-row-style-stroke");
RED.colorPicker.create({
id:"node-input-style-fill",
value: style.fill || "none",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['fill-opacity'] || 1.0
}).appendTo("#node-input-row-style-fill");
createLayoutPicker({
id:"node-input-style-label-position",
value:style["label-position"] || "nw"
}).appendTo("#node-input-row-style-label-position");
RED.colorPicker.create({
id:"node-input-style-color",
value: style.color || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3
}).appendTo("#node-input-row-style-label-color");
$("#node-input-style-label").toggleButton({
enabledLabel: RED._("editor.show"),
disabledLabel: RED._("editor.hide")
})
$("#node-input-style-label").on("change", function(evt) {
$("#node-input-row-style-label-options").toggle($(this).prop("checked"));
})
$("#node-input-style-label").prop("checked", this.style.label)
$("#node-input-style-label").trigger("change");
},
oneditresize: function(size) {
},
oneditsave: function() {
this.style.stroke = $("#node-input-style-stroke").val();
this.style.fill = $("#node-input-style-fill").val();
this.style["stroke-opacity"] = $("#node-input-style-stroke-opacity").val();
this.style["fill-opacity"] = $("#node-input-style-fill-opacity").val();
this.style.label = $("#node-input-style-label").prop("checked");
if (this.style.label) {
this.style["label-position"] = $("#node-input-style-label-position").val();
this.style.color = $("#node-input-style-color").val();
} else {
delete this.style["label-position"];
delete this.style.color;
}
if (this.style["stroke-opacity"] === "1") {
delete this.style["stroke-opacity"]
}
if (this.style["fill-opacity"] === "1") {
delete this.style["fill-opacity"]
}
this.resize = true;
},
set:{
module: "node-red"
}
}
function init() {
RED.events.on("view:selection-changed",function(selection) {
RED.menu.setDisabled("menu-item-group-group",!!!selection.nodes);
RED.menu.setDisabled("menu-item-group-ungroup",!!!selection.nodes || selection.nodes.filter(function(n) { return n.type==='group'}).length === 0);
RED.menu.setDisabled("menu-item-group-merge",!!!selection.nodes);
RED.menu.setDisabled("menu-item-group-remove",!!!selection.nodes || selection.nodes.filter(function(n) { return !!n.g }).length === 0);
});
RED.actions.add("core:group-selection", function() { groupSelection() })
RED.actions.add("core:ungroup-selection", function() { ungroupSelection() })
RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() })
RED.actions.add("core:remove-selection-from-group", function() { removeSelection() })
RED.actions.add("core:copy-group-style", function() { copyGroupStyle() });
RED.actions.add("core:paste-group-style", function() { pasteGroupStyle() });
$(_groupEditTemplate).appendTo("#red-ui-editor-node-configs");
var groupStyleDiv = $("<div>",{
class:"red-ui-flow-group-body",
style: "position: absolute; top: -1000px;"
}).appendTo(document.body);
var groupStyle = getComputedStyle(groupStyleDiv[0]);
defaultGroupStyle = {
stroke: convertColorToHex(groupStyle.stroke),
"stroke-opacity": groupStyle.strokeOpacity,
fill: convertColorToHex(groupStyle.fill),
"fill-opacity": groupStyle.fillOpacity
}
groupStyleDiv.remove();
}
function convertColorToHex(c) {
var m = /^rgb\((\d+), (\d+), (\d+)\)$/.exec(c);
if (m) {
return "#"+(((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).padStart(6,'0'))
}
return c;
}
var groupStyleClipboard;
function copyGroupStyle() {
var selection = RED.view.selection();
if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].type === 'group') {
groupStyleClipboard = JSON.parse(JSON.stringify(selection.nodes[0].style));
RED.notify(RED._("clipboard.groupStyleCopied"),{id:"clipboard"})
}
}
function pasteGroupStyle() {
if (groupStyleClipboard) {
var selection = RED.view.selection();
if (selection.nodes) {
var historyEvent = {
t:'multi',
events:[],
dirty: RED.nodes.dirty()
}
selection.nodes.forEach(function(n) {
if (n.type === 'group') {
historyEvent.events.push({
t: "edit",
node: n,
changes: {
style: JSON.parse(JSON.stringify(n.style))
},
dirty: RED.nodes.dirty()
});
n.style = JSON.parse(JSON.stringify(groupStyleClipboard));
n.dirty = true;
}
})
if (historyEvent.events.length > 0) {
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.view.redraw();
}
}
}
}
function groupSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var group = createGroup(selection.nodes);
if (group) {
var historyEvent = {
t:"createGroup",
groups: [ group ],
dirty: RED.nodes.dirty()
}
RED.history.push(historyEvent);
RED.view.select({nodes:[group]});
RED.nodes.dirty(true);
}
}
}
function ungroupSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var newSelection = [];
groups = selection.nodes.filter(function(n) { return n.type === "group" });
var historyEvent = {
t:"ungroup",
groups: [ ],
dirty: RED.nodes.dirty()
}
RED.history.push(historyEvent);
groups.forEach(function(g) {
newSelection = newSelection.concat(ungroup(g))
historyEvent.groups.push(g);
})
RED.history.push(historyEvent);
RED.view.select({nodes:newSelection})
RED.nodes.dirty(true);
}
}
function ungroup(g) {
var nodes = [];
var parentGroup = RED.nodes.group(g.g);
g.nodes.forEach(function(n) {
nodes.push(n);
if (parentGroup) {
// Move nodes to parent group
n.g = parentGroup.id;
parentGroup.nodes.push(n);
parentGroup.dirty = true;
n.dirty = true;
} else {
delete n.g;
}
})
RED.nodes.removeGroup(g);
return nodes;
}
function mergeSelection() {
// TODO: this currently creates an entirely new group. Need to merge properties
// of any existing group
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
var historyEvent = {
t: "multi",
events: []
}
var ungroupHistoryEvent = {
t: "ungroup",
groups: []
}
var n;
var parentGroup;
// First pass, check they are all in the same parent
// TODO: DRY mergeSelection,removeSelection,...
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (i === 0) {
parentGroup = n.g;
} else if (n.g !== parentGroup) {
RED.notify(RED._("group.errors.cannotCreateDiffGroups"),"error");
return;
}
}
// Second pass, ungroup any groups in the selection and add their contents
// to the selection
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (n.type === "group") {
ungroupHistoryEvent.groups.push(n);
nodes = nodes.concat(ungroup(n));
} else {
nodes.push(n);
}
n.dirty = true;
}
if (ungroupHistoryEvent.groups.length > 0) {
historyEvent.events.push(ungroupHistoryEvent);
}
// Finally, create the new group
var group = createGroup(nodes);
if (group) {
RED.view.select({nodes:[group]})
}
historyEvent.events.push({
t:"createGroup",
groups: [ group ],
dirty: RED.nodes.dirty()
});
RED.history.push(historyEvent);
RED.nodes.dirty(true);
}
}
function removeSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
var n;
var parentGroup = RED.nodes.group(selection.nodes[0].g);
if (parentGroup) {
try {
removeFromGroup(parentGroup,selection.nodes,true);
var historyEvent = {
t: "removeFromGroup",
dirty: RED.nodes.dirty(),
group: parentGroup,
nodes: selection.nodes
}
RED.history.push(historyEvent);
RED.nodes.dirty(true);
} catch(err) {
RED.notify(err,"error");
return;
}
}
RED.view.select({nodes:selection.nodes})
}
}
function createGroup(nodes) {
if (nodes.length === 0) {
return;
}
if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
return;
}
// nodes is an array
// each node must be on the same tab (z)
var group = {
id: RED.nodes.id(),
type: 'group',
nodes: [],
style: JSON.parse(JSON.stringify(defaultGroupStyle)),
x: Number.POSITIVE_INFINITY,
y: Number.POSITIVE_INFINITY,
w: 0,
h: 0,
_def: RED.group.def
}
try {
addToGroup(group,nodes);
} catch(err) {
RED.notify(err,"error");
return;
}
group.z = nodes[0].z;
RED.nodes.addGroup(group);
return group;
}
function addToGroup(group,nodes) {
if (!Array.isArray(nodes)) {
nodes = [nodes];
}
var i,n,z;
var g;
// First pass - validate we can safely add these nodes to the group
for (i=0;i<nodes.length;i++) {
n = nodes[i]
if (!n.z) {
throw new Error("Cannot add node without a z property to a group")
}
if (!z) {
z = n.z;
} else if (z !== n.z) {
throw new Error("Cannot add nooes with different z properties")
}
if (n.g) {
// This is already in a group.
// - check they are all in the same group
if (!g) {
if (i!==0) {
// TODO: this might be ok when merging groups
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
}
g = n.g
}
}
if (g !== n.g) {
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
}
}
// The nodes are already in a group. The assumption is they should be
// wrapped in the newly provided group, and that group added to in their
// place to the existing containing group.
if (g) {
g = RED.nodes.group(g);
g.nodes.push(group);
g.dirty = true;
group.g = g.id;
}
// Second pass - add them to the group
for (i=0;i<nodes.length;i++) {
n = nodes[i];
if (n.type !== "subflow") {
if (g && n.g === g.id) {
var ni = g.nodes.indexOf(n);
if (ni > -1) {
g.nodes.splice(ni,1)
}
}
n.g = group.id;
n.dirty = true;
group.nodes.push(n);
group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0));
group.y = Math.min(group.y,n.y-n.h/2-25);
group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x);
group.h = Math.max(group.h,n.y+n.h/2+25-group.y);
}
}
markDirty(group);
}
function removeFromGroup(group, nodes, reparent) {
if (!Array.isArray(nodes)) {
nodes = [nodes];
}
var n;
// First pass, check they are all in the same parent
// TODO: DRY mergeSelection,removeSelection,...
for (var i=0; i<nodes.length; i++) {
if (nodes[i].g !== group.id) {
return;
}
}
var parentGroup = RED.nodes.group(group.g);
for (var i=0; i<nodes.length; i++) {
n = nodes[i];
n.dirty = true;
var index = group.nodes.indexOf(n);
group.nodes.splice(index,1);
if (reparent && group.g) {
n.g = group.g
parentGroup.nodes.push(n);
} else {
delete n.g;
}
}
markDirty(group);
}
function getNodes(group,recursive) {
var nodes = [];
group.nodes.forEach(function(n) {
nodes.push(n);
if (recursive && n.type === 'group') {
nodes = nodes.concat(getNodes(n,recursive))
}
})
return nodes;
}
function groupContains(group,item) {
if (item.g === group.id) {
return true;
}
for (var i=0;i<group.nodes.length;i++) {
if (group.nodes[i].type === "group") {
if (groupContains(group.nodes[i],item)) {
return true;
}
}
}
return false;
}
function getRootGroup(group) {
if (!group.g) {
return group;
}
return getRootGroup(RED.nodes.group(group.g))
}
function createLayoutPicker(options) {
var container = $("<div>",{style:"display:inline-block"});
var layoutHiddenInput = $("<input/>", { id: options.id, type: "hidden", value: options.value }).appendTo(container);
var layoutButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
$('<i class="fa fa-caret-down"></i>').appendTo(layoutButton);
var layoutDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(layoutButton);
var layoutDisp = $('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"}).appendTo(layoutDispContainer);
var refreshDisplay = function() {
var val = layoutHiddenInput.val();
layoutDisp.removeClass().addClass("red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val)
}
layoutButton.on("click", function(e) {
var picker = $("<div/>", {
class: "red-ui-group-layout-picker"
}).css({
width: "126px"
});
var row = null;
row = $("<div/>").appendTo(picker);
for (var y=0;y<2;y++) { //red-ui-group-layout-text-pos
var yComponent= "ns"[y];
row = $("<div/>").appendTo(picker);
for (var x=0;x<3;x++) {
var xComponent = ["w","","e"][x];
var val = yComponent+xComponent;
var button = $("<button/>", { class:"red-ui-search-result-node","data-pos":val }).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
layoutHiddenInput.val($(this).data("pos"));
layoutPanel.hide()
refreshDisplay();
});
$('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val}).appendTo(button);
}
}
refreshDisplay();
var layoutPanel = RED.popover.panel(picker);
layoutPanel.show({
target: layoutButton
})
})
refreshDisplay();
return container;
}
function markDirty(group) {
group.dirty = true;
while(group) {
group.dirty = true;
group = RED.nodes.group(group.g);
}
}
return {
def: groupDef,
init: init,
createGroup: createGroup,
ungroup: ungroup,
addToGroup: addToGroup,
removeFromGroup: removeFromGroup,
getNodes: getNodes,
contains: groupContains,
markDirty: markDirty
}
})();

View File

@@ -524,12 +524,12 @@ RED.keyboard = (function() {
var pane = $('<div id="red-ui-settings-tab-keyboard"></div>');
$('<div class="keyboard-shortcut-entry keyboard-shortcut-list-header">'+
'<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input id="red-ui-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+
'<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input autocomplete="off" name="keyboard-filter" id="red-ui-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+
'<div class="keyboard-shortcut-entry-key" data-i18n="keyboard.shortcut"></div>'+
'<div class="keyboard-shortcut-entry-scope" data-i18n="keyboard.scope"></div>'+
'</div>').appendTo(pane);
pane.find("input").searchBox({
pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({
delay: 100,
change: function() {
var filterValue = $(this).val().trim();

View File

@@ -75,13 +75,16 @@ RED.palette.editor = (function() {
});
})
}
function installNodeModule(id,version,callback) {
function installNodeModule(id,version,url,callback) {
var requestBody = {
module: id
};
if (version) {
requestBody.version = version;
}
if (url) {
requestBody.url = url;
}
$.ajax({
url:"nodes",
type: "POST",
@@ -381,6 +384,7 @@ RED.palette.editor = (function() {
handleCatalogResponse(null,catalog,index,v);
refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",catalog,":",error);
handleCatalogResponse(jqxhr,catalog,index);
}).always(function() {
handled++;
@@ -622,7 +626,7 @@ RED.palette.editor = (function() {
if ($(this).hasClass('disabled')) {
return;
}
update(entry,loadedIndex[entry.name].version,container,function(err){});
update(entry,loadedIndex[entry.name].version,loadedIndex[entry.name].pkg_url,container,function(err){});
})
@@ -872,7 +876,7 @@ RED.palette.editor = (function() {
$('<div id="red-ui-palette-module-install-shade" class="red-ui-palette-module-shade hide"><div class="red-ui-palette-module-shade-status"></div><img src="red/images/spin.svg" class="red-ui-palette-spinner"/></div>').appendTo(installTab);
}
function update(entry,version,container,done) {
function update(entry,version,url,container,done) {
if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable'));
return;
@@ -898,7 +902,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version);
installNodeModule(entry.name,version,function(xhr) {
installNodeModule(entry.name,version,url,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
@@ -1023,7 +1027,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,function(xhr) {
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {

View File

@@ -92,8 +92,11 @@ RED.palette = (function() {
var lineHeight = 20;
var portHeight = 10;
el.attr("data-palette-label",label);
label = RED.utils.sanitize(label);
var words = label.split(/[ -]/);
var displayLines = [];
@@ -212,16 +215,14 @@ RED.palette = (function() {
}
$('<div/>', {
class: "red-ui-palette-label"
+ (((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-label-right" : "")
class: "red-ui-palette-label"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-label-right" : "")
}).appendTo(d);
if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>', {
class: "red-ui-palette-icon-container"
+ (((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-icon-container-right" : "")
class: "red-ui-palette-icon-container"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-icon-container-right" : "")
}).appendTo(d);
RED.utils.createIconElement(icon_url, iconContainer, true);
}
@@ -268,7 +269,7 @@ RED.palette = (function() {
RED.view.focus();
var helpText;
if (nt.indexOf("subflow:") === 0) {
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
} else {
helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
}
@@ -283,6 +284,9 @@ RED.palette = (function() {
var mouseX;
var mouseY;
var spliceTimer;
var groupTimer;
var activeGroup;
var hoverGroup;
var paletteWidth;
var paletteTop;
$(d).draggable({
@@ -294,16 +298,53 @@ RED.palette = (function() {
start: function() {
paletteWidth = $("#red-ui-palette").width();
paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
hoverGroup = null;
activeGroup = RED.view.getActiveGroup();
if (activeGroup) {
document.getElementById("group_select_"+activeGroup.id).classList.add("red-ui-flow-group-active-hovered");
}
RED.view.focus();
},
stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
stop: function() {
d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
if (hoverGroup) {
document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
}
if (activeGroup) {
document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
}
if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
},
drag: function(e,ui) {
var paletteNode = getPaletteNode(nt);
ui.originalPosition.left = paletteNode.offset().left;
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop();
if (!groupTimer) {
groupTimer = setTimeout(function() {
mouseX /= RED.view.scale();
mouseY /= RED.view.scale();
var group = RED.view.getGroupAtPoint(mouseX,mouseY);
if (group !== hoverGroup) {
if (hoverGroup) {
document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
}
if (group) {
document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
}
hoverGroup = group;
if (hoverGroup) {
$(ui.helper).data('group',hoverGroup);
} else {
$(ui.helper).removeData('group');
}
}
groupTimer = null;
},200)
}
if (def.inputs > 0 && def.outputs > 0) {
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop();
if (!spliceTimer) {
spliceTimer = setTimeout(function() {
var nodes = [];
@@ -369,7 +410,7 @@ RED.palette = (function() {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
nodeInfo = marked(def.info||"");
nodeInfo = RED.utils.renderMarkdown(def.info||"");
}
setLabel(nt,d,label,nodeInfo);
@@ -419,14 +460,10 @@ RED.palette = (function() {
var portOutput = paletteNode.find(".red-ui-palette-port-output");
var paletteLabel = paletteNode.find(".red-ui-palette-label");
paletteLabel.attr("class","red-ui-palette-label"
+ (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : "")
);
paletteLabel.attr("class","red-ui-palette-label" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : ""));
var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container");
paletteIconContainer.attr("class","red-ui-palette-icon-container"
+ (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : "")
);
paletteIconContainer.attr("class","red-ui-palette-icon-container" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : ""));
if (portInput.length === 0 && sf.in.length > 0) {
var portIn = document.createElement("div");
@@ -443,7 +480,7 @@ RED.palette = (function() {
} else if (portOutput.length !== 0 && sf.out.length === 0) {
portOutput.remove();
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||""));
setIcon(paletteNode,sf);
var currentCategory = paletteNode.data('category');
@@ -475,7 +512,7 @@ RED.palette = (function() {
function filterChange(val) {
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
var currentLabel = $(el).find(".red-ui-palette-label").text();
var currentLabel = $(el).attr("data-palette-label");
var type = $(el).attr("data-palette-type");
if (val === "" || re.test(type) || re.test(currentLabel)) {
$(this).show();

View File

@@ -158,7 +158,7 @@ RED.projects.settings = (function() {
container.empty();
var desc;
if (activeProject.description) {
desc = marked(activeProject.description);
desc = RED.utils.renderMarkdown(activeProject.description);
} else {
desc = '<span class="red-ui-help-info-none">' + RED._("sidebar.project.noDescriptionAvailable") + '</span>';
}

View File

@@ -30,13 +30,13 @@ RED.projects.userSettings = (function() {
$('<div class="red-ui-settings-section-description"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip"));
var row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text">').appendTo(row);
$('<label for="user-settings-gitconfig-username"></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text" id="user-settings-gitconfig-username">').appendTo(row);
gitUsernameInput.val(currentGitSettings.user.name||"");
row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row);
$('<label for="user-settings-gitconfig-email"></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text" id="user-settings-gitconfig-email">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||"");
}

View File

@@ -1938,100 +1938,122 @@ RED.projects = (function() {
resultCallbackArgs = data;
}
}).fail(function(xhr,textStatus,err) {
var responses;
if (options.responses && options.responses[xhr.status]) {
var responses = options.responses[xhr.status];
responses = options.responses[xhr.status];
if (typeof responses === 'function') {
resultCallback = responses;
resultCallbackArgs = {error:responses.statusText};
return;
} else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
} else if (options.handleAuthFail !== false && (xhr.responseJSON.code === 'git_auth_failed' || xhr.responseJSON.code === 'git_host_key_verification_failed')) {
if (xhr.responseJSON.code === 'git_auth_failed') {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
var message = $('<div>'+
var message = $('<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">'+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);
$('<label for="projects-user-auth-key">SSH Key</label>').appendTo(row);
var projectRepoSSHKeySelect = $('<select id="projects-user-auth-key">').width('70%').appendTo(row);
$.getJSON("settings/user/keys", function(data) {
var count = 0;
data.keys.forEach(function(key) {
projectRepoSSHKeySelect.append($("<option></option>").val(key.name).text(key.name));
count++;
var isSSH = false;
if (/^https?:\/\//.test(url)) {
$('<div class="form-row"><label for="projects-user-auth-username">'+RED._("projects.send-req.username")+'</label><input id="projects-user-auth-username" type="text"></input></div>'+
'<div class="form-row"><label for=projects-user-auth-password">'+RED._("projects.send-req.password")+'</label><input id="projects-user-auth-password" type="password"></input></div>').appendTo(message);
} else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) {
isSSH = true;
var row = $('<div class="form-row"></div>').appendTo(message);
$('<label for="projects-user-auth-key">SSH Key</label>').appendTo(row);
var projectRepoSSHKeySelect = $('<select id="projects-user-auth-key">').width('70%').appendTo(row);
$.getJSON("settings/user/keys", function(data) {
var count = 0;
data.keys.forEach(function(key) {
projectRepoSSHKeySelect.append($("<option></option>").val(key.name).text(key.name));
count++;
});
if (count === 0) {
//TODO: handle no keys yet setup
}
});
if (count === 0) {
//TODO: handle no keys yet setup
}
});
row = $('<div class="form-row"></div>').appendTo(message);
$('<label for="projects-user-auth-passphrase">'+RED._("projects.send-req.passphrase")+'</label>').appendTo(row);
$('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row);
}
row = $('<div class="form-row"></div>').appendTo(message);
$('<label for="projects-user-auth-passphrase">'+RED._("projects.send-req.passphrase")+'</label>').appendTo(row);
$('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row);
}
var notification = RED.notify(message,{
type:"error",
fixed: true,
modal: true,
buttons: [
{
//id: "node-dialog-delete",
//class: 'leftButton',
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
},{
text: '<span><i class="fa fa-refresh"></i> ' +RED._("projects.send-req.retry") +'</span>',
click: function() {
body = body || {};
var authBody = {};
if (isSSH) {
authBody.keyFile = $('#projects-user-auth-key').val();
authBody.passphrase = $('#projects-user-auth-passphrase').val();
} else {
authBody.username = $('#projects-user-auth-username').val();
authBody.password = $('#projects-user-auth-password').val();
var notification = RED.notify(message,{
type:"error",
fixed: true,
modal: true,
buttons: [
{
//id: "node-dialog-delete",
//class: 'leftButton',
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
var done = function(err) {
if (err) {
console.log(RED._("projects.send-req.update-failed"));
console.log(err);
},{
text: '<span><i class="fa fa-refresh"></i> ' +RED._("projects.send-req.retry") +'</span>',
click: function() {
body = body || {};
var authBody = {};
if (isSSH) {
authBody.keyFile = $('#projects-user-auth-key').val();
authBody.passphrase = $('#projects-user-auth-passphrase').val();
} else {
sendRequest(options,body);
notification.close();
authBody.username = $('#projects-user-auth-username').val();
authBody.password = $('#projects-user-auth-password').val();
}
var done = function(err) {
if (err) {
console.log(RED._("projects.send-req.update-failed"));
console.log(err);
} else {
sendRequest(options,body);
notification.close();
}
}
sendRequest({
url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'),
type: "PUT",
responses: {
0: function(error) {
done(error,null);
},
200: function(data) {
done(null,data);
},
400: {
'unexpected_error': function(error) {
done(error,null);
}
},
}
},{auth:authBody});
sendRequest({
url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'),
type: "PUT",
responses: {
0: function(error) {
done(error,null);
},
200: function(data) {
done(null,data);
},
400: {
'unexpected_error': function(error) {
done(error,null);
}
},
}
},{auth:authBody});
}
}
}
]
});
return;
]
});
return;
} else if (xhr.responseJSON.code === 'git_host_key_verification_failed') {
var message = $('<div>'+
'<div class="form-row">'+RED._("projects.send-req.host-key-verify-failed")+'</div>'+
'</div>');
var notification = RED.notify(message,{
type:"error",
fixed: true,
modal: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
}
]
});
return;
}
} else if (responses[xhr.responseJSON.code]) {
resultCallback = responses[xhr.responseJSON.code];
resultCallbackArgs = xhr.responseJSON;

View File

@@ -336,7 +336,7 @@ RED.sidebar.versionControl = (function() {
var unstagedContent = $('<div class="red-ui-sidebar-vc-change-container"></div>').appendTo(localChanges.content);
var header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.localFiles")+'</div>').appendTo(unstagedContent);
stageAllButton = $('<button class="red-ui-button red-ui-button-small" style="float: right"><i class="fa fa-plus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
stageAllButton = $('<button class="red-ui-button red-ui-button-small" style="position: absolute; right: 5px; top: 5px;"><i class="fa fa-plus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
.appendTo(header)
.on("click", function(evt) {
evt.preventDefault();
@@ -368,7 +368,7 @@ RED.sidebar.versionControl = (function() {
unmergedContent = $('<div class="red-ui-sidebar-vc-change-container"></div>').appendTo(localChanges.content);
header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.unmergedChanges")+'</div>').appendTo(unmergedContent);
bg = $('<div style="float: right"></div>').appendTo(header);
bg = $('<div style="position: absolute; right: 5px; top: 5px;"></div>').appendTo(header);
var abortMergeButton = $('<button class="red-ui-button red-ui-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.abortMerge")+'</button>')
.appendTo(bg)
.on("click", function(evt) {
@@ -433,7 +433,7 @@ RED.sidebar.versionControl = (function() {
header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.changeToCommit")+'</div>').appendTo(stagedContent);
bg = $('<div style="float: right"></div>').appendTo(header);
bg = $('<div style="position: absolute; right: 5px; top: 5px;"></div>').appendTo(header);
var showCommitBox = function() {
commitMessage.val("");
submitCommitButton.prop("disabled",true);

View File

@@ -25,5 +25,7 @@ RED.state = {
IMPORT_DRAGGING: 8,
QUICK_JOINING: 9,
PANNING: 10,
SELECTING_NODE: 11
SELECTING_NODE: 11,
GROUP_DRAGGING: 12,
GROUP_RESIZE: 13
}

File diff suppressed because it is too large Load Diff

View File

@@ -231,7 +231,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format,
sourceId: id+"."+k,
tools: tools
tools: tools,
path: ""
}).appendTo(propRow.children()[1]);
}
})
@@ -275,7 +276,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format,
sourceId: id+"."+k,
tools: tools
tools: tools,
path: ""
}).appendTo(propRow.children()[1]);
}
});
@@ -295,7 +297,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: v.format,
sourceId: id+"."+k,
tools: tools
tools: tools,
path: ""
}).appendTo(propRow.children()[1]);
if (contextStores.length > 1) {
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))

View File

@@ -15,17 +15,6 @@
**/
RED.sidebar.info = (function() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var content;
var sections;
var propertiesSection;
@@ -163,7 +152,8 @@ RED.sidebar.info = (function() {
var types = {
nodes:0,
flows:0,
subflows:0
subflows:0,
groups: 0
}
node.forEach(function(n) {
if (n.type === 'tab') {
@@ -171,6 +161,8 @@ RED.sidebar.info = (function() {
types.nodes += RED.nodes.filterNodes({z:n.id}).length;
} else if (n.type === 'subflow') {
types.subflows++;
} else if (n.type === 'group') {
types.groups++;
} else {
types.nodes++;
}
@@ -190,6 +182,9 @@ RED.sidebar.info = (function() {
if (types.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts);
}
if (types.groups > 0) {
$('<div>').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts);
}
} else {
// A single 'thing' selected.
@@ -220,6 +215,36 @@ RED.sidebar.info = (function() {
propRow = $('<tr class="red-ui-help-info-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 === "group") {
// An actual node is selected in the editor - build up its properties table
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.group")+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
if (node.name) {
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody);
$('<span class="red-ui-text-bidi-aware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]);
}
propRow = $('<tr class="red-ui-help-info-row"><td>&nbsp;</td><td></td></tr>').appendTo(tableBody);
var typeCounts = {
nodes:0,
groups: 0
}
var allNodes = RED.group.getNodes(node,true);
allNodes.forEach(function(n) {
if (n.type === "group") {
typeCounts.groups++;
} else {
typeCounts.nodes++
}
});
var counts = $('<div>').appendTo($(propRow.children()[1]));
if (typeCounts.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts);
}
if (typeCounts.groups > 0) {
$('<div>').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts);
}
} else {
// An actual node is selected in the editor - build up its properties table
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
@@ -236,7 +261,7 @@ RED.sidebar.info = (function() {
}
}
var count = 0;
if (!m && node.type != "subflow") {
if (!m && node.type != "subflow" && node.type != "group") {
var defaults;
if (node.type === 'unknown') {
defaults = {};
@@ -314,7 +339,7 @@ RED.sidebar.info = (function() {
if (subflowNode && node.type !== "subflow") {
// Selected a subflow instance node.
// - The subflow template info goes into help
helpText = (marked(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
} else {
helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
}
@@ -326,10 +351,10 @@ RED.sidebar.info = (function() {
if (node._def && node._def.info) {
var info = node._def.info;
var textInfo = (typeof info === "function" ? info.call(node) : info);
infoText = infoText + marked(textInfo);
infoText = infoText + RED.utils.renderMarkdown(textInfo);
}
if (node.info) {
infoText = infoText + marked(node.info || "")
infoText = infoText + RED.utils.renderMarkdown(node.info || "")
}
setInfoText(infoText, infoSection.content);

View File

@@ -16,6 +16,28 @@
RED.utils = (function() {
window._marked = window.marked;
window.marked = function(txt) {
console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead");
return renderMarkdown(txt);
}
_marked.setOptions({
renderer: new _marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
smartLists: true,
smartypants: false
});
function renderMarkdown(txt) {
var rendered = _marked(txt);
var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
return cleaned;
}
function formatString(str) {
return str.replace(/\r?\n/g,"&crarr;").replace(/\t/g,"&rarr;");
}
@@ -1053,6 +1075,7 @@ RED.utils = (function() {
decodeObject: decodeObject,
parseContextKey: parseContextKey,
createIconElement: createIconElement,
sanitize: sanitize
sanitize: sanitize,
renderMarkdown: renderMarkdown
}
})();

View File

@@ -67,9 +67,16 @@ RED.view.tools = (function() {
function moveSelection(dx,dy) {
if (moving_set === null) {
moving_set = [];
var selection = RED.view.selection();
if (selection.nodes) {
moving_set = selection.nodes.map(function(n) { return {n:n}});
while (selection.nodes.length > 0) {
var n = selection.nodes.shift();
moving_set.push({n:n});
if (n.type === "group") {
selection.nodes = selection.nodes.concat(n.nodes);
}
}
}
}
if (moving_set && moving_set.length > 0) {
@@ -93,6 +100,9 @@ RED.view.tools = (function() {
node.n.x += dx;
node.n.y += dy;
node.n.dirty = true;
if (node.n.type === "group") {
RED.group.markDirty(node.n);
}
minX = Math.min(node.n.x-node.n.w/2-5,minX);
minY = Math.min(node.n.y-node.n.h/2-5,minY);
}
@@ -105,6 +115,8 @@ RED.view.tools = (function() {
}
}
RED.view.redraw();
} else {
RED.view.scroll(dx*10,dy*10);
}
}
@@ -112,6 +124,16 @@ RED.view.tools = (function() {
init: function() {
RED.actions.add("core:align-selection-to-grid", alignToGrid);
RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());});
RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);});
RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());});
RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);});
RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());});
RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);});
RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());});
RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);});
RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);});
RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);});
RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);});

File diff suppressed because it is too large Load Diff

View File

@@ -164,7 +164,7 @@ RED.workspaces = (function() {
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
$('<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name">'+
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>').appendTo(dialogForm);

View File

@@ -9,19 +9,15 @@
color: transparent !important;
}
}
.ace_gutter {
background: $text-editor-gutter-background;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.ace_scroller {
background: $text-editor-background;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.ace_scroller {
background: $text-editor-background;
color: $text-editor-color;
}
.ace_marker-layer .ace_active-line {
@@ -37,9 +33,6 @@
.ace_gutter-active-line {
background: $text-editor-gutter-active-line-background;
}
.ace_gutter {
background: $text-editor-gutter-background;
}
.ace_tooltip {
font-family: $primary-font;
line-height: 1.4em;
@@ -52,6 +45,10 @@
@include component-shadow;
border-color: $popover-background;
}
textarea.ace_text-input {
overflow: hidden;
padding: 0px 1px !important;
}
#red-ui-event-log-editor {
.ace_scroller {

View File

@@ -284,3 +284,8 @@ $debug-message-border: #eee;
$debug-message-border-hover: #999;
$debug-message-border-warning: #ffdf9d;
$debug-message-border-error: #f99;
$group-default-fill: none;
$group-default-fill-opacity: 1;
$group-default-stroke: #999;
$group-default-stroke-opacity: 1;

View File

@@ -160,6 +160,7 @@
.red-ui-debug-msg-element {
color: $debug-message-text-color;
line-height: 1.3em;
overflow-wrap: break-word;
}
.red-ui-debug-msg-object-key {
color: $debug-message-text-color-object-key;

View File

@@ -411,6 +411,133 @@ button.red-ui-button.red-ui-editor-node-appearance-button {
}
}
.red-ui-group-layout-picker {
padding: 5px;
background: $primary-background;
}
.red-ui-group-layout-picker-cell-text {
position: absolute;
width: 14px;
height: 2px;
border-top: 2px solid $secondary-text-color;
border-bottom: 2px solid $secondary-text-color;
margin: 2px;
&.red-ui-group-layout-text-pos-nw { top: 0; left: 0; }
&.red-ui-group-layout-text-pos-n { top: 0; left: calc(50% - 9px); }
&.red-ui-group-layout-text-pos-ne { top: 0; right: 0; }
&.red-ui-group-layout-text-pos-sw { bottom: 0; left: 0; }
&.red-ui-group-layout-text-pos-s { bottom: 0; left: calc(50% - 9px); }
&.red-ui-group-layout-text-pos-se { bottom: 0; right: 0; }
&.red-ui-group-layout-text-pos- {
width: 100%;
height: 100%;
border-radius: 5px;
margin: 0;
background-color: #FFF;
background-size: 100% 100%;
background-position: 0 0, 50% 50%;
background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent);
border: none;
}
}
.red-ui-group-layout-picker button.red-ui-search-result-node {
float: none;
position: relative;
padding: 0;
margin: 5px;
width: 32px;
height: 27px;
}
button.red-ui-group-layout-picker-none {
width: 100%;
}
.red-ui-color-picker {
input[type="text"] {
border-radius:0;
width: 100%;
margin-bottom: 0;
border: none;
border-bottom: 1px solid $form-input-border-color;
}
small {
color: $secondary-text-color;
margin-left: 5px;
margin-right: 4px;
display: inline-block;
min-width: 35px;
text-align: right;
}
background: $primary-background;
}
.red-ui-editor-node-appearance-button {
.red-ui-search-result-node {
overflow: hidden
}
}
.red-ui-color-picker-cell {
padding: 0;
border-style: solid;
border-width: 1px;
border-color: $secondary-border-color;
}
.red-ui-color-picker-swatch {
position: absolute;
top:-1px;right:-1px;left:-1px;bottom:-1px;
border-radius: 4px;
}
.red-ui-color-picker-cell-none {
height: 100%;
background-color: #FFF;
background-size: 100% 100%;
background-position: 0 0, 50% 50%;
background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent)
}
.red-ui-search-result-node .red-ui-color-picker-cell-none {
border-radius: 4px;
background-size: 50% 50%;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
}
.red-ui-color-picker-opacity-slider {
position:relative;
vertical-align: middle;
display: inline-block;
width: calc(100% - 50px);
height: 14px;
margin: 6px 3px 8px;
box-sizing: border-box;
background-color: white;
background-image:
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%),
linear-gradient(-45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%);
background-size: 6px 6px;
}
.red-ui-color-picker-opacity-slider-overlay {
position: absolute;
top:0;right:0;left:0;bottom:0;
background-image:linear-gradient(90deg, transparent 0%, #f00 100%);
background-size: 100% 100%;
border: 1px solid $primary-border-color;
}
div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
z-Index: 10;
top: -4px;
cursor: pointer;
min-width: 0;
width: 10px;
height: 22px;
padding: 0;
border: 1px solid $primary-border-color;
border-radius: 1px;
background: $secondary-background;
box-sizing: border-box;
}
.red-ui-icon-picker {
select {
box-sizing: border-box;

View File

@@ -71,6 +71,48 @@
}
}
.red-ui-flow-group {
&.red-ui-flow-group-hovered {
.red-ui-flow-group-outline-select {
stroke-opacity: 0.8 !important;
stroke-dasharray: 10 4 !important;
}
}
&.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) {
.red-ui-flow-group-outline-select {
stroke: $link-link-color;
}
}
}
.red-ui-flow-group-outline {
fill: none;
stroke: $node-selected-color;
stroke-opacity: 0;
stroke-width: 12;
pointer-events: stroke;
}
.red-ui-flow-group-outline-select {
fill: none;
stroke: $node-selected-color;
pointer-events: stroke;
stroke-opacity: 0;
stroke-width: 3;
}
.red-ui-flow-group-body {
pointer-events: none;
fill: $group-default-fill;
fill-opacity: $group-default-fill-opacity;
stroke-width: 2;
stroke: $group-default-stroke;
stroke-opacity: $group-default-stroke-opacity;
}
.red-ui-flow-group-label {
@include disable-selection;
}
.red-ui-flow-node-unknown {
stroke-dasharray:10,4;
stroke: $node-border-unknown;
@@ -145,8 +187,8 @@ g.red-ui-flow-node-selected {
border-color: $node-selected-color !important;
border-style: dashed !important;
stroke: $node-selected-color;
stroke-width: 2;
stroke-dasharray: 8, 3;
stroke-width: 3;
stroke-dasharray: 8, 4;
}
.red-ui-flow-subflow .red-ui-flow-node {
@@ -248,6 +290,7 @@ g.red-ui-flow-node-selected {
.red-ui-flow-link-outline {
stroke: $view-background;
stroke-opacity: 0.4;
stroke-width: 5;
cursor: crosshair;
fill: none;

View File

@@ -27,9 +27,22 @@
display: none;
}
}
.red-ui-info-table {
table-layout: fixed;
}
table.red-ui-info-table tr:not(.blank) td:first-child {
width: 30%;
}
table.red-ui-info-table tr:not(.blank) td:last-child {
vertical-align: top;
}
}
.red-ui-sidebar-context-property {
overflow-wrap: break-word;
position: relative;
.red-ui-debug-msg-tools {
right: 0px;

View File

@@ -32,6 +32,9 @@
right: 5px;
top: 9px;
}
form.red-ui-searchBox-form {
margin: 0;
}
input.red-ui-searchBox-input {
border-radius: 0;
border: none;

View File

@@ -56,7 +56,10 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.red-ui-typedInput-value-label-inactive {
background: $secondary-background-disabled;
color: $secondary-text-color-disabled;
}
}
}
.red-ui-typedInput-options {
@@ -70,6 +73,7 @@
border: 1px solid $primary-border-color;
box-sizing: border-box;
background: $secondary-background;
white-space: nowrap;
z-index: 2000;
a {
padding: 6px 18px 6px 6px;
@@ -122,7 +126,7 @@ button.red-ui-typedInput-option-trigger
}
&.disabled {
cursor: default;
i.red-ui-typedInput-icon {
> i.red-ui-typedInput-icon {
color: $secondary-text-color-disabled;
}
}

View File

@@ -112,7 +112,7 @@
position: absolute;
bottom: 0;
right:0;
zIndex: 101;
z-index: 101;
border-left: 1px solid $primary-border-color;
border-top: 1px solid $primary-border-color;
background: $view-navigator-background;
@@ -122,7 +122,7 @@
stroke-dasharray: 5,5;
pointer-events: none;
stroke: $secondary-border-color;
strokeWidth: 1;
stroke-width: 1;
fill: $view-background;
}

View File

@@ -11,6 +11,7 @@
var length = str.length;
var start = 0;
var inString = false;
var inRegex = false;
var inBox = false;
var quoteChar;
var list = [];
@@ -24,8 +25,13 @@
}
for (var i=0;i<length;i++) {
var c = str[i];
if (!inString) {
if (c === "'" || c === '"') {
if (!inString && !inRegex) {
if (c === "/") {
inRegex = true;
frame = {type:"regex",pos:i};
list.push(frame);
stack.push(frame);
} else if (c === "'" || c === '"') {
inString = true;
quoteChar = c;
frame = {type:"string",pos:i};
@@ -37,6 +43,9 @@
} else if (c === ",") {
frame = {type:",",pos:i};
list.push(frame);
} else if (c === "&") {
frame = {type:"&",pos:i};
list.push(frame);
} else if (/[\(\[\{]/.test(c)) {
frame = {type:"open-block",char:c,pos:i};
list.push(frame);
@@ -44,7 +53,8 @@
} else if (/[\}\)\]]/.test(c)) {
var oldFrame = stack.pop();
if (matchingBrackets[oldFrame.char] !== c) {
//console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos);
// console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos);
// console.log(list);
return str;
}
//console.log("Closing",c,"at",i,"compare",oldFrame.type,oldFrame.pos);
@@ -53,19 +63,32 @@
list.push(frame);
}
} else {
if (c === quoteChar) {
// Next char must be a ]
inString = false;
stack.pop();
if (c === "\\") {
// an escaped char - stay in current mode and skip the next char
i++;
}
if (inString) {
if (c === quoteChar) {
// Next char must be a ]
inString = false;
var f = stack.pop();
f.end = i;
}
} else if (inRegex) {
if (c === "/") {
inRegex = false;
var f = stack.pop();
f.end = i;
}
}
}
}
// console.log(stack);
// console.log("list",list);
var result = str;
var indent = 0;
var offset = 0;
var pre,post,indented;
var pre,post,indented,hasNewline;
var longStack = [];
list.forEach(function(f) {
if (f.type === ";" || f.type === ",") {
@@ -73,29 +96,51 @@
pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
hasNewline = /\n$/.test(pre);
// console.log("A§"+pre+"§\n§"+indented+"§",hasNewline);
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
}
} else if (f.type === "&") {
pre = result.substring(0,offset+f.pos+1);
var lastLineBreak = pre.lastIndexOf("\n");
var lineLength = pre.length - lastLineBreak;
if (lineLength > 70) {
post = result.substring(offset+f.pos+1);
if (!/^\n/.test(post)) {
indented = indentLine(post,indent);
hasNewline = /\n$/.test(pre);
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
}
}
} else if (f.type === "open-block") {
if (f.width > 30) {
if (f.width > 40) {
longStack.push(true);
indent += 4;
pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1);
hasNewline = /\n$/.test(pre);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
} else {
longStack.push(false);
}
} else if (f.type === "close-block") {
if (f.width > 30) {
if (f.width > 40) {
indent -= 4;
pre = result.substring(0,offset+f.pos);
post = result.substring(offset+f.pos);
indented = indentLine(post,indent);
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
hasNewline = /\n *$/.test(pre);
if (hasNewline) {
result = pre + post;
} else {
result = pre+"\n"+indented;
offset += indented.length-post.length+1;
}
}
longStack.pop();
}
@@ -171,6 +216,7 @@
'$sum':{ args:[ 'array' ]},
'$toMillis':{args:['timestamp']}, // <-------------
'$trim':{ args:[ 'str' ]},
'$type':{ args:['value']},
'$uppercase':{ args:[ 'str' ]},
'$zip':{ args:[ 'array1' ]}
}

View File

@@ -28,6 +28,11 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
}, "identifier");
this.$rules = {
"start" : [
{
token: "string.regexp",
regex: "\\/",
next: "regex"
},
{
token : "string",
regex : "'(?=.)",
@@ -46,34 +51,35 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
},
{ token: "keyword",
regex: /λ/
},
{
token: "keyword",
regex: jsonataFunctions
},
{
token : keywordMapper,
regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*"
},
{
token : "punctuation.operator",
regex : /[.](?![.])/
},
{
token : "keyword.operator",
regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/,
next : "start"
},
{
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
},
{
token : "paren.lparen",
regex : /[\[({]/,
{
token: "keyword",
regex: /λ/
},
{
token: "keyword",
regex: jsonataFunctions
},
{
token : keywordMapper,
regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*"
},
{
token : "punctuation.operator",
regex : /[.](?![.])/
},
{
token : "keyword.operator",
regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/,
next : "start"
},
{
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
},
{
token : "paren.lparen",
regex : /[\[({]/,
next : "start"
},
{
@@ -86,7 +92,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "string",
regex : '"|$',
next : "start"
}, {
},
{
defaultToken: "string"
}
],
@@ -95,9 +102,24 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "string",
regex : "'|$",
next : "start"
}, {
},
{
defaultToken: "string"
}
],
"regex" : [
{
token: "string.regexp",
regex: "\\\\/"
},
{
token: "string.regexp",
regex: "/[sxngimy]*",
next: "start"
},
{
defaultToken: "string.regexp"
}
]
};
};

File diff suppressed because one or more lines are too long

View File

@@ -30,16 +30,16 @@
<!-- (with the 'node-input-' prefix). -->
<!-- The available icon classes are defined Font Awesome Icons (FA Icons) -->
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div>
<br/>
<!-- By convention, most nodes have a 'name' property. The following div -->
<!-- provides the necessary field. Should always be the last option -->
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="inject">
<script type="text/html" data-template-name="inject">
<div class="form-row">
<label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-input-payload" style="width:70%">

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="debug">
<script type="text/html" data-template-name="debug">
<div class="form-row">
<label for="node-input-typed-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
<input id="node-input-typed-complete" type="text" style="width: 70%">
@@ -131,8 +131,32 @@
RED.view.redraw();
}
},
messageSourceClick: function(sourceId) {
RED.view.reveal(sourceId);
messageSourceClick: function(sourceId, aliasId, path) {
// Get all of the nodes that could have logged this message
var candidateNodes = [RED.nodes.node(sourceId)]
if (path) {
for (var i=2;i<path.length;i++) {
candidateNodes.push(RED.nodes.node(path[i]))
}
}
if (aliasId) {
candidateNodes.push(RED.nodes.node(aliasId));
}
if (candidateNodes.length > 1) {
// The node is in a subflow. Check to see if the active
// workspace is a subflow in the node's parentage. If
// so, reveal the relevant subflow instance node.
var ws = RED.workspaces.active();
for (var i=0;i<candidateNodes.length;i++) {
if (candidateNodes[i].z === ws) {
RED.view.reveal(candidateNodes[i].id);
return
}
}
// The active workspace is unrelated to the node. So
// fall back to revealing the top most node
}
RED.view.reveal(candidateNodes[0].id);
},
clear: function() {
RED.nodes.eachNode(function(node) {
@@ -179,9 +203,44 @@
RED.events.on("workspace:change", this.refreshMessageList);
this.handleDebugMessage = function(t,o) {
var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
// console.log("->",o.id,o.z,o._alias);
//
// sourceNode should be the top-level node - one that is on a flow.
var sourceNode;
var pathParts;
if (o.path) {
// Path is a `/`-separated list of ids that identifies the
// complete parentage of the node that generated this message.
// flow-id/subflow-A-instance/subflow-A-type/subflow-B-instance/subflow-B-type/node-id
// If it has one id, that is a top level flow
// each subsequent id is the instance id of a subflow node
//
pathParts = o.path.split("/");
if (pathParts.length === 1) {
// The source node is on a flow - so can use its id to find
sourceNode = RED.nodes.node(o.id);
} else if (pathParts.length > 1) {
// Highlight the subflow instance node.
sourceNode = RED.nodes.node(pathParts[1]);
}
} else {
// This is probably redundant...
sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
}
if (sourceNode) {
o._source = {id:sourceNode.id,z:sourceNode.z,name:sourceNode.name,type:sourceNode.type,_alias:o._alias};
o._source = {
id:sourceNode.id,
z:sourceNode.z,
name:sourceNode.name,
type:sourceNode.type,
// _alias identifies the actual logging node. This is
// not necessarily the same as sourceNode, which will be
// the top-level subflow instance node.
// This means the node's name is displayed in the sidebar.
_alias:o._alias,
path: pathParts
};
}
RED.debug.handleDebugMessage(o);
if (subWindow) {
@@ -235,7 +294,7 @@
} else if (msg.event === "mouseLeave") {
options.messageMouseLeave(msg.id);
} else if (msg.event === "mouseClick") {
options.messageSourceClick(msg.id);
options.messageSourceClick(msg.id,msg._alias,msg.path);
} else if (msg.event === "clear") {
options.clear();
}

View File

@@ -62,7 +62,7 @@ module.exports = function(RED) {
if (err) {
done(RED._("debug.invalid-exp", {error: editExpression}));
} else {
done(null,{id:node.id, z:node.z, name:node.name, topic:msg.topic, msg:value, _path:msg._path});
done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:value});
}
});
} else {
@@ -77,7 +77,7 @@ module.exports = function(RED) {
output = undefined;
}
}
done(null,{id:node.id, z:node.z, name:node.name, topic:msg.topic, property:property, msg:output, _path:msg._path});
done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, property:property, msg:output});
}
}
@@ -88,7 +88,7 @@ module.exports = function(RED) {
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
}
if (this.active && this.tosidebar) {
sendDebug({id:node.id, z:node.z, name:node.name, topic:msg.topic, msg:msg, _path:msg._path});
sendDebug({id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:msg});
}
done();
} else {

View File

@@ -1,4 +1,4 @@
<script type="text/x-red" data-template-name="complete">
<script type="text/html" data-template-name="complete">
<div class="form-row node-input-target-row">
<button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div>

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="catch">
<script type="text/html" data-template-name="catch">
<div class="form-row">
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
<select id="node-input-scope-select">

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="status">
<script type="text/html" data-template-name="status">
<div class="form-row">
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
<select id="node-input-scope-select">

View File

@@ -1,12 +1,12 @@
<script type="text/x-red" data-template-name="link in">
<script type="text/html" data-template-name="link in">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row node-input-link-row"></div>
</script>
<script type="text/x-red" data-template-name="link out">
<script type="text/html" data-template-name="link out">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">

View File

@@ -1,8 +1,8 @@
<script type="text/x-red" data-template-name="comment">
<script type="text/html" data-template-name="comment">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name">
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row node-text-editor-row">
<input type="hidden" id="node-input-info" autofocus="autofocus">

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="unknown">
<script type="text/html" data-template-name="unknown">
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
</script>

View File

@@ -406,10 +406,16 @@ RED.debug = (function() {
msg.on("mouseenter", function() {
msg.addClass('red-ui-debug-msg-hover');
if (o._source) {
// highlight the top-level node (could be subflow instance)
config.messageMouseEnter(o._source.id);
if (o._source._alias) {
// this is inside a subflow - highlight the node itself
config.messageMouseEnter(o._source._alias);
}
// if path.length > 2, we are nested - highlight subflow instances
for (var i=2;i<o._source.path.length;i++) {
config.messageMouseEnter(o._source.path[i]);
}
}
});
msg.on("mouseleave", function() {
@@ -419,6 +425,9 @@ RED.debug = (function() {
if (o._source._alias) {
config.messageMouseLeave(o._source._alias);
}
for (var i=2;i<o._source.path.length;i++) {
config.messageMouseLeave(o._source.path[i]);
}
}
});
var name = sanitize(((o.name?o.name:o.id)||"").toString());
@@ -448,11 +457,11 @@ RED.debug = (function() {
var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg);
$('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
if (sourceNode) {
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+sanitize(o.name||sourceNode.name||sourceNode.id))
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
.appendTo(metaRow)
.on("click", function(evt) {
evt.preventDefault();
config.messageSourceClick(sourceNode.id);
config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path);
});
} else if (name) {
$('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow);

View File

@@ -7,8 +7,8 @@ $(function() {
messageMouseLeave: function(sourceId) {
window.opener.postMessage({event:"mouseLeave",id:sourceId},'*');
},
messageSourceClick: function(sourceId) {
window.opener.postMessage({event:"mouseClick",id:sourceId},'*');
messageSourceClick: function(sourceId, aliasId, path) {
window.opener.postMessage({event:"mouseClick",id:sourceId, _alias: aliasId, path: path},'*');
},
clear: function() {
window.opener.postMessage({event:"clear"},'*');

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="function">
<script type="text/html" data-template-name="function">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="switch">
<script type="text/html" data-template-name="switch">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -127,7 +127,7 @@
},
oneditprepare: function() {
var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
var previousValueType = {value:"prev",label:this._("switch.previous"),hasValue:false};
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata','env']});
var outputCount = $("#node-input-outputs").val("{}");
@@ -173,7 +173,7 @@
}
}
$("#node-input-rule-container").css('min-height','250px').css('min-width','450px').editableList({
$("#node-input-rule-container").css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
if (!opt.hasOwnProperty('r')) {
opt.r = {};
@@ -237,15 +237,15 @@
function createTypeValueField(){
return $('<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},
{value:"number",label:"number",hasValue:false},
{value:"boolean",label:"boolean",hasValue:false},
{value:"array",label:"array",hasValue:false},
{value:"buffer",label:"buffer",hasValue:false},
{value:"object",label:"object",hasValue:false},
{value:"json",label:"JSON string",hasValue:false},
{value:"undefined",label:"undefined",hasValue:false},
{value:"null",label:"null",hasValue:false}
{value:"string",label:RED._("common.type.string"),hasValue:false,icon:"red/images/typedInput/az.png"},
{value:"number",label:RED._("common.type.number"),hasValue:false,icon:"red/images/typedInput/09.png"},
{value:"boolean",label:RED._("common.type.boolean"),hasValue:false,icon:"red/images/typedInput/bool.png"},
{value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"buffer",label:RED._("common.type.buffer"),hasValue:false,icon:"red/images/typedInput/bin.png"},
{value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"json",label:RED._("common.type.jsonString"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"undefined",label:RED._("common.type.undefined"),hasValue:false},
{value:"null",label:RED._("common.type.null"),hasValue:false}
]});
}
@@ -453,6 +453,7 @@
}
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-rule-container").editableList('height',height);
}
});

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="change">
<script type="text/html" data-template-name="change">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -81,7 +81,7 @@
rule.find('.red-ui-typedInput').typedInput("width",newWidth-130);
}
$('#node-input-rule-container').css('min-height','300px').css('min-width','450px').editableList({
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
var rule = opt;
if (!rule.hasOwnProperty('t')) {
@@ -259,7 +259,7 @@
}
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-rule-container").editableList('height',height);
}
});

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="range">
<script type="text/html" data-template-name="range">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>

View File

@@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="template">
<script type="text/html" data-template-name="template">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="delay">
<script type="text/html" data-template-name="delay">
<div class="form-row">
<label for="node-input-delay-action"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
<select id="node-input-delay-action" style="width:270px !important">

View File

@@ -14,11 +14,11 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="trigger">
<script type="text/html" data-template-name="trigger">
<div class="form-row">
<label data-i18n="trigger.send" for="node-input-op1"></label>
<input type="hidden" id="node-input-op1type">
<input style="width: 70%" type="text" id="node-input-op1" placeholder="1">
<input style="width:70%" type="text" id="node-input-op1" placeholder="1">
</div>
<div class="form-row">
<label data-i18n="trigger.then"></label>
@@ -40,12 +40,12 @@
</div>
<div class="form-row node-type-wait">
<label></label>
<input type="checkbox" id="node-input-extend" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
<input type="checkbox" id="node-input-extend" style="margin-left:0px; vertical-align:top; width:auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
</div>
<div class="form-row node-type-wait">
<label data-i18n="trigger.then-send"></label>
<input type="hidden" id="node-input-op2type">
<input style="width: 70%" type="text" id="node-input-op2" placeholder="0">
<input style="width:70%" type="text" id="node-input-op2" placeholder="0">
</div>
<div class="form-row">
<label data-i18n="trigger.label.reset" style="width:auto"></label>
@@ -109,9 +109,11 @@
$(".node-type-duration").hide();
}
else if ($(this).val() == "loop") {
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
$(".node-type-wait").hide();
$(".node-type-duration").show();
} else {
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
$(".node-type-wait").show();
$(".node-type-duration").show();
}
@@ -175,9 +177,7 @@
}
if ($("#node-then-type").val() == "loop") {
$("#node-input-duration").val($("#node-input-duration").val() * -1);
}
}
}
});
</script>

View File

@@ -76,6 +76,7 @@ module.exports = function(RED) {
var node = this;
node.topics = {};
var npay = {};
var pendingMessages = [];
var activeMessagePromise = null;
var processMessageQueue = function(msg) {
@@ -106,7 +107,9 @@ module.exports = function(RED) {
});
}
var npay;
this.on('input', function(msg) {
if (node.op2type === "payl") { npay = RED.util.cloneMessage(msg); }
processMessageQueue(msg);
});
@@ -122,9 +125,10 @@ module.exports = function(RED) {
node.status({});
}
else {
if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); }
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); }
if (node.op2type === "pay") { 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") {
promise = new Promise((resolve,reject) => {
@@ -186,9 +190,17 @@ module.exports = function(RED) {
});
}
promise.then(() => {
msg2.payload = node.topics[topic].m2;
if (node.op2type === "payl") {
node.send(npay[topic]);
delete npay[topic];
}
else {
msg2.payload = node.topics[topic].m2;
node.send(msg2);
}
delete node.topics[topic];
node.send(msg2);
if (node.op2type === "payl") { node.send(npay); }
else { node.send(msg2); }
node.status({});
}).catch(err => {
node.error(err);
@@ -244,9 +256,9 @@ module.exports = function(RED) {
});
}, node.duration);
}
else {
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
}
// else {
// if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
// }
}
return Promise.resolve();
}

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="exec">
<script type="text/html" data-template-name="exec">
<div class="form-row">
<label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label>
<input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command">

View File

@@ -145,7 +145,7 @@ module.exports = function(RED) {
if (error.signal) { msg3.payload.signal = error.signal; }
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
node.log('error:' + error);
if (RED.settings.verbose) { node.log('error:' + error); }
}
else if (node.oldrc === "false") {
msg3 = RED.util.cloneMessage(msg);

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="tls-config">
<script type="text/html" data-template-name="tls-config">
<div class="form-row" class="hide" id="node-config-row-uselocalfiles">
<input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="http proxy">
<script type="text/html" data-template-name="http proxy">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-config-input-name">

View File

@@ -11,7 +11,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="mqtt in">
<script type="text/html" data-template-name="mqtt in">
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker">
@@ -75,7 +75,7 @@
});
</script>
<script type="text/x-red" data-template-name="mqtt out">
<script type="text/html" data-template-name="mqtt out">
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker">
@@ -129,7 +129,7 @@
});
</script>
<script type="text/x-red" data-template-name="mqtt-broker">
<script type="text/html" data-template-name="mqtt-broker">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
@@ -137,7 +137,7 @@
<div class="form-row">
<ul style="min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul>
</div>
<div id="node-config-mqtt-broker-tabs-content" style="min-height: 170px;">
<div id="node-config-mqtt-broker-tabs-content" style="min-height:150px;">
<div id="mqtt-broker-tab-connection" style="display:none">
<div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>

View File

@@ -111,9 +111,13 @@ 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; }
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
// Create the URL to pass in to the MQTT.js library
if (this.brokerurl === "") {
@@ -121,9 +125,15 @@ module.exports = function(RED) {
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) {
if (this.brokerurl.indexOf("wss://") > -1 || this.brokerurl.indexOf("ws://") > -1 ) {
// check if proxy is set in env
var noproxy;
if (noprox) {
for (var i = 0; i < noprox.length; i += 1) {
if (this.brokerurl.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
}
}
if (prox && !noproxy) {
var parsedUrl = url.parse(this.brokerurl);
var proxyOpts = url.parse(prox);
// true for wss
@@ -134,6 +144,7 @@ module.exports = function(RED) {
agent: agent
}
}
}
} else {
// construct the std mqtt:// url
if (this.usetls) {
@@ -142,7 +153,12 @@ module.exports = function(RED) {
this.brokerurl="mqtt://";
}
if (this.broker !== "") {
this.brokerurl = this.brokerurl+this.broker+":";
//Check for an IPv6 address
if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) {
this.brokerurl = this.brokerurl+"["+this.broker+"]:";
} else {
this.brokerurl = this.brokerurl+this.broker+":";
}
// port now defaults to 1883 if unset.
if (!this.port){
this.brokerurl = this.brokerurl+"1883";
@@ -453,6 +469,7 @@ module.exports = function(RED) {
this.broker = n.broker;
this.brokerConn = RED.nodes.getNode(this.broker);
var node = this;
var chk = /[\+#]/;
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
@@ -471,6 +488,7 @@ module.exports = function(RED) {
}
if ( msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
if (chk.test(msg.topic)) { node.warn(RED._("mqtt.errors.invalid-topic")); }
this.brokerConn.publish(msg, done); // send the message
} else {
node.warn(RED._("mqtt.errors.invalid-topic"));

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="http in">
<script type="text/html" data-template-name="http in">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:70%;">
@@ -45,7 +45,7 @@
<div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>
</script>
<script type="text/x-red" data-template-name="http response">
<script type="text/html" data-template-name="http response">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<script type="text/x-red" data-template-name="http request">
<script type="text/html" data-template-name="http request">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:70%;">
@@ -22,6 +22,7 @@
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="HEAD">HEAD</option>
<option value="use" data-i18n="httpin.setby"></option>
</select>
</div>

View File

@@ -39,10 +39,10 @@ module.exports = function(RED) {
else { this.reqTimeout = 120000; }
var prox, noprox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); }
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
var proxyConfig = null;
if (n.proxy) {
@@ -88,8 +88,13 @@ module.exports = function(RED) {
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
}
var isHttps = (/^https/i.test(url));
var opts = {};
opts.url = url;
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
opts.defaultPort = isHttps?443:80;
opts.timeout = node.reqTimeout;
opts.method = method;
opts.headers = {};
@@ -284,9 +289,10 @@ module.exports = function(RED) {
opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length'];
}
var noproxy;
if (noprox) {
for (var i in noprox) {
for (var i = 0; i < noprox.length; i += 1) {
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
}
}

View File

@@ -14,7 +14,7 @@
limitations under the License.
-->
<!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in">
<script type="text/html" data-template-name="websocket in">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode">
@@ -163,7 +163,7 @@
if (root === "") {
$("#node-config-ws-tip").hide();
} else {
$("#node-config-ws-path").html(root);
$("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root }));
$("#node-config-ws-tip").show();
}
}
@@ -198,7 +198,7 @@
</script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<script type="text/html" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode">
@@ -221,7 +221,7 @@
</script>
<!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener">
<script type="text/html" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input id="node-config-input-path" type="text" placeholder="/ws/example">
@@ -235,12 +235,12 @@
</div>
<div class="form-tips">
<span data-i18n="[html]websocket.tip.path1"></span>
<p id="node-config-ws-tip"><span data-i18n="[html]websocket.tip.path2"></span><code><span id="node-config-ws-path"></span></code>.</p>
<p id="node-config-ws-tip"><span id="node-config-ws-path"></span></p>
</div>
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<script type="text/html" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input id="node-config-input-path" type="text" placeholder="ws://example.com/ws">

View File

@@ -19,6 +19,8 @@ module.exports = function(RED) {
var ws = require("ws");
var inspect = require("util").inspect;
var url = require("url");
var HttpsProxyAgent = require('https-proxy-agent');
var serverUpgradeAdded = false;
function handleServerUpgrade(request, socket, head) {
@@ -55,7 +57,28 @@ module.exports = function(RED) {
function startconn() { // Connect to remote endpoint
node.tout = null;
var prox, noprox;
if (process.env.http_proxy) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY) { noprox = process.env.NO_PROXY.split(","); }
var noproxy = false;
if (noprox) {
for (var i in noprox) {
if (node.path.indexOf(noprox[i].trim()) !== -1) { noproxy=true; }
}
}
var agent = undefined;
if (prox && !noproxy) {
agent = new HttpsProxyAgent(prox);
}
var options = {};
if (agent) {
options.agent = agent;
}
if (node.tls) {
var tlsNode = RED.nodes.getNode(node.tls);
if (tlsNode) {

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