Compare commits

..

146 Commits

Author SHA1 Message Date
Dave Conway-Jones
6ae42eb787 Let JSON parser attempt to parse buffers if they contain strings
and add/fix test
2021-12-10 15:21:43 +00:00
Nick O'Leary
7cd3e49f04 Merge branch 'master' into dev 2021-12-05 19:32:13 +00:00
Nick O'Leary
173e75175e update changelog 2021-12-02 09:30:41 +00:00
Nick O'Leary
800006dd76 Merge pull request #3282 from node-red/tab-buttons
Change tab hide button icon to an eye and add search option
2021-12-02 09:29:28 +00:00
Nick O'Leary
a824b6910a Add tooltips to tabs and close/hide buttons 2021-12-02 09:23:13 +00:00
Nick O'Leary
dcea382b38 Change tab hide button icon to an eye and add search option 2021-12-01 17:54:17 +00:00
Nick O'Leary
d9f976baea Update package for 2.1.4 2021-12-01 16:06:03 +00:00
Nick O'Leary
1fa13efe19 Merge pull request #3281 from node-red/i18n-space-ns-fix
Fix i18n handling of namespaces with spaces in
2021-12-01 15:15:38 +00:00
Nick O'Leary
33af5cd7c6 Apply i18n namespace fix to runtime component 2021-12-01 15:15:08 +00:00
Nick O'Leary
7cb8f97ef1 Fix i18n handling of namespaces with spaces in 2021-12-01 15:08:04 +00:00
Nick O'Leary
bf965a9cde Merge pull request #3280 from node-red/auto-complete-fix
Trigger change event when autoComplete fills in input
2021-12-01 14:38:10 +00:00
Nick O'Leary
17ffff685a Trigger change event when autoComplete fills in input
Fixes #3271
2021-12-01 14:35:45 +00:00
Nick O'Leary
ae76271cff Merge pull request #3279 from node-red/cn-fix
Apply CN i18n fix
2021-12-01 14:26:37 +00:00
Nick O'Leary
8f3a96d615 Apply CN i18n fix
Fixes #3272

Verified with Google Translate this is a better translation for
"Export to library"
2021-12-01 14:25:35 +00:00
Nick O'Leary
30e750dfe5 Merge pull request #3278 from node-red-hitachi/fix-env-var-access
fix env var access using $parent for groups
2021-12-01 14:23:44 +00:00
Nick O'Leary
682dff7c6f Merge pull request #3273 from node-red-hitachi/fix-config-select-name
fix select menu label of config node to use paletteLabel
2021-12-01 14:02:55 +00:00
Nick O'Leary
85415eb8a8 Merge pull request #3275 from node-red-hitachi/fix-node-conflict
fix removed tab not to cause node conflict
2021-12-01 14:01:50 +00:00
Nick O'Leary
68a80b9244 Merge pull request #3277 from node-red/debug-path-fix
Add proper error handling for 404 errors when serving debug files
2021-12-01 13:48:25 +00:00
Hiroyasu Nishiyama
c331da7323 fix env var access using $env for groups 2021-11-27 19:29:57 +09:00
Nick O'Leary
04ffa06221 Add proper error handling for 404 errors when serving debug files 2021-11-26 11:30:51 +00:00
Hiroyasu Nishiyama
1f0690c6ec fix removed tab not to cause node conflict 2021-11-25 13:26:52 +09:00
Hiroyasu Nishiyama
711467abcd fix select menu label of config node to use paletteLabel 2021-11-24 14:06:45 +09:00
Nick O'Leary
9439cd0e3d Merge pull request #3179 from kazuhitoyokoi/dev-addjpn
Add Japanese translations for Node-RED v2.1.0-beta.1
2021-11-22 23:25:33 +00:00
Nick O'Leary
314c19650d Merge pull request #3239 from node-red/group-diff-fix
Group diff fix
2021-11-22 23:25:19 +00:00
Nick O'Leary
ed6afcd802 Merge pull request #3251 from node-red/regex-debug
Handle RegExp types in Debug sidebar
2021-11-22 23:24:55 +00:00
Nick O'Leary
082d4fe8e1 Merge pull request #3252 from node-red/disable-hidden-flow
Only toggle disabled workspace flag if on activeWorkspace
2021-11-22 23:24:39 +00:00
Nick O'Leary
cd23b44506 Merge pull request #3253 from node-red/clear-disabled-status
Do not show status for disabled nodes
2021-11-22 23:24:18 +00:00
Nick O'Leary
46b6b024b9 Merge pull request #3269 from node-red/auth-id
Include full user object on login audit events
2021-11-22 23:24:00 +00:00
Nick O'Leary
cb88cc35e5 Merge pull request #3261 from node-red/fix-delay-2nd-output-when-in-per-topic-mode
fix 2nd output when in rate limit per topic modes
2021-11-22 23:23:35 +00:00
Nick O'Leary
75c0c44809 Merge pull request #3265 from kazuhitoyokoi/master-fixtourguide
Set dimension value for tour guide
2021-11-22 23:23:03 +00:00
Nick O'Leary
a091b82ba9 Merge pull request #3263 from node-red/ti-fix
Avoid redundant initialisation of TypedInput type
2021-11-22 23:20:40 +00:00
Nick O'Leary
a3b8f022e6 Merge pull request #3270 from bonanitech/node-port-label-color
Don't let themes change flow port label color
2021-11-22 23:20:30 +00:00
Nick O'Leary
279fcb7c51 Merge pull request #3267 from node-red-hitachi/fix-link-target-select
fix to show link target when selected
2021-11-22 23:20:04 +00:00
Mauricio Bonani
49a9376073 Change flow port label CSS variable 2021-11-22 16:24:14 -05:00
Nick O'Leary
96840ede56 Include full user object on login audit events 2021-11-22 19:58:48 +00:00
Hiroyasu Nishiyama
7e7f481f99 fix to show link target when selected 2021-11-19 17:14:22 +09:00
Kazuhito Yokoi
3edbf52bc6 Set dimension value for tour guide 2021-11-18 15:38:49 +09:00
Nick O'Leary
fba6e801fc Avoid redundant initialisation of TypedInput type 2021-11-16 14:32:09 +00:00
Nick O'Leary
720a163273 Bump dev version 2021-11-15 23:38:01 +00:00
Dave Conway-Jones
6ac0c0a367 fix 2nd output when in rate limit per topic modes
to fix issue found in #3260
2021-11-15 14:05:16 +00:00
Kazuhito Yokoi
300402d253 Add Japanese translation for tour button 2021-11-12 18:15:57 +09:00
Kazuhito Yokoi
0d9bfae503 Add Japanese translations to tour guide for first flow 2021-11-11 13:02:52 +09:00
Nick O'Leary
bfe0d3b8a3 Do not show status for disabled nodes
Fixes #3249
2021-11-08 21:12:13 +00:00
Nick O'Leary
5fdd9c0546 Only toggle disabled workspace flag if on activeWorkspace
Fixes #3244
2021-11-08 20:38:34 +00:00
Nick O'Leary
b6570a16b8 Handle RegExp types in Debug sidebar
Fixes #3245
2021-11-08 20:24:10 +00:00
Kazuhito Yokoi
8e2d3ea16f Remove unused messages in language catalog 2021-11-08 13:46:24 +09:00
Kazuhito Yokoi
bc2c81f058 Add Japanese translations for link call, mqtt and file nodes 2021-11-08 13:38:17 +09:00
Nick O'Leary
3e0f080ea7 Merge pull request #3242 from node-red/inject-fix
Do not modify inject node props in oneditprepare
2021-11-01 21:40:47 +00:00
Nick O'Leary
679e07189d Do not modify inject node props in oneditprepare
Fixes #3241
2021-11-01 20:10:52 +00:00
Nick O'Leary
a38ebef100 Display group changes property in diff view 2021-10-28 12:14:56 +01:00
Nick O'Leary
b8ad6475e1 Strip off env property if it is empty - avoid inflated flow files 2021-10-28 12:14:34 +01:00
Nick O'Leary
0f0cb3ac6d Fix setting object env var properties so they can be undone 2021-10-28 12:14:03 +01:00
Nick O'Leary
d3efb9d7cc Merge pull request #3238 from node-red/treeLister-gutter-fix
Fix treeList gutter calculation to handle floating gutters
2021-10-28 10:46:04 +01:00
Nick O'Leary
84a237d3f5 Fix treeList gutter calculation to handle floating gutters
Fixes #3234
2021-10-28 10:44:50 +01:00
Nick O'Leary
e6de52eede Merge pull request #3236 from hardillb/http-basic-username-only
HTTP Basic Auth should always add : between username and password even if empty
2021-10-28 10:14:02 +01:00
Nick O'Leary
98aee964d7 Merge pull request #3237 from node-red/remove-de-style
Remove styling from de locale files
2021-10-28 10:11:54 +01:00
Nick O'Leary
570e5442e0 Remove styling from de locale files
Fixes #3230
2021-10-28 10:10:22 +01:00
Ben Hardill
b77a2dc353 Better fix 2021-10-28 10:08:28 +01:00
Ben Hardill
87af31de20 HTTP Basic Auth should always add : to username
fix for #3235
2021-10-28 09:18:17 +01:00
Nick O'Leary
cfe201dbe1 Bump for 2.1.3 2021-10-26 10:42:53 +01:00
Nick O'Leary
6ccdab35e0 Merge pull request #3227 from node-red/reload-node-settings
Refresh editor settings whenever a node is added or enabled
2021-10-26 09:51:41 +01:00
Nick O'Leary
55b9f36b45 Merge pull request #3229 from node-red/spinner-css
Revert spinner css change that made it shrink in some cases
2021-10-26 09:51:31 +01:00
Nick O'Leary
fbcb1130c9 Merge pull request #3221 from hardillb/disable-tours
Add environment variable to enable/disable tours
2021-10-26 09:47:17 +01:00
Nick O'Leary
d4f7a6d2bc Revert spinner css change that made it shrink in some cases 2021-10-26 09:44:32 +01:00
Nick O'Leary
8a19f71abe Refresh editor settings whenever a node is added or enabled
Fixes #3217

This ensures any node-provided settings are loaded and ready
for use by the nodes
2021-10-25 20:48:01 +01:00
Nick O'Leary
fb153757b5 Merge pull request #3225 from node-red/i18n-fix
Fix loading non-default language files leaving runtime in wrong locale
2021-10-25 16:06:12 +01:00
Nick O'Leary
4f175fc93e Fix loading non-default language files leaving runtime in wrong locale 2021-10-25 15:58:07 +01:00
Nick O'Leary
2d4ca7cec0 Merge pull request #3224 from node-red/import-msg-fix
Fix import notification message when importing config nodes
2021-10-25 15:12:46 +01:00
Nick O'Leary
bf0ea89969 Fix import notification message when importing config nodes 2021-10-25 15:08:30 +01:00
Nick O'Leary
073f0c2a20 Merge pull request #3223 from node-red/ti-fix
Handle changing types of TypedInput repeatedly
2021-10-25 13:55:38 +01:00
Nick O'Leary
ba83be9062 Handle changing types of TypedInput repeatedly
Fixes #3222
2021-10-25 13:54:42 +01:00
Ben Hardill
2e7188ea4f Add environment variable to enable/disable tours
NODE_RED_ENABLE_TOURS

Also had to patch editor-api/lib/editor/themes.js to pass
`editorTheme.tours` to the editor.
2021-10-25 11:25:31 +01:00
Nick O'Leary
5a012182d9 Update gen-publish script to update 'next' tag for main releases 2021-10-25 10:05:23 +01:00
Nick O'Leary
b855438af6 Fix changelog 2021-10-25 09:42:44 +01:00
Nick O'Leary
2ffea143e7 Bump for 2.1.2 2021-10-25 09:38:32 +01:00
Duncan Bellamy
61d85b49e6 Remove bash dependency (#3216)
Change backticks to dollar sign and parentheses
2021-10-25 08:44:49 +01:00
Nick O'Leary
35f617e96c Merge pull request #3213 from GerwinvBeek/markdown-regex
Improved regex for markdown renderer
2021-10-24 23:04:20 +01:00
Nick O'Leary
6b6ad47c35 Merge pull request #3220 from node-red/ti-fixes
Fix TypedInput initialisation
2021-10-24 22:58:55 +01:00
Nick O'Leary
e57183ed0e Merge pull request #3219 from Steve-Mcl/mqtt-use-datatype
fix datatype in node config not used. fixes #3215
2021-10-24 22:58:36 +01:00
Nick O'Leary
ecfd61a822 Fix TypedInput initialisation handle 2021-10-24 22:53:22 +01:00
Steve-Mcl
153f87704b fix datatype in node config not used. fixes #3215 2021-10-24 22:21:44 +01:00
Gerwin van Beek
836f7d2163 Improved regex for markdown renderer 2021-10-22 17:05:37 +02:00
Nick O'Leary
d4d6f71cf4 Bump for 2.1.1 2021-10-22 09:27:52 +01:00
Nick O'Leary
42a9da006e Merge pull request #3212 from node-red/fix-tour-guide-width
Ensure tourGuide popover doesn't fall offscreen
2021-10-22 09:26:31 +01:00
Nick O'Leary
2bd5c4f527 Ensure tourGuide popover doesn't fall offscreen
Only handles the left hand edge - will need expanding to cover
the other edges as needed
2021-10-22 09:24:40 +01:00
Nick O'Leary
6a49b5c106 Merge pull request #3210 from node-red/fix-old-inject-migration
Fix issue with old inject nodes that migrated topic to 'string' type
2021-10-22 09:16:54 +01:00
Nick O'Leary
23e14d1b72 Merge pull request #3211 from node-red/cache-bust
Add cache-busting query params to index.mst
2021-10-22 09:16:45 +01:00
Nick O'Leary
f4f11c8884 Add cache-busting query params to index.mst 2021-10-22 09:14:01 +01:00
Nick O'Leary
2b220abdb7 Fix issue with old inject nodes that migrated topic to 'string' type 2021-10-22 09:01:24 +01:00
Nick O'Leary
c1d947ebe3 Merge pull request #3207 from node-red/fix-debug-all
Fix TypedInput validation of type without options
2021-10-21 14:45:09 +01:00
Nick O'Leary
d695cf392e Fix TypedInput validation of type without options
Fixes #3206
2021-10-21 14:44:15 +01:00
Nick O'Leary
21304a695c Fix tcprequest deprecation warnings 2021-10-21 09:40:25 +01:00
Nick O'Leary
fa51b06c46 Merge pull request #3205 from node-red/dev
Node-RED 2.1.0
2021-10-21 09:13:09 +01:00
Nick O'Leary
7560bb8d7b Bump for 2.1.0 2021-10-21 09:08:35 +01:00
Nick O'Leary
fc9d65abcc Merge pull request #3204 from node-red/tcp-req-text-output
Add string option to TCP request node output
2021-10-20 09:40:44 +01:00
Dave Conway-Jones
a7413cccd0 reuse existing labels for tcp request buffer/string option 2021-10-20 09:36:08 +01:00
Nick O'Leary
7610353f07 Position popover properly on a scrolled page
Not needed in the core editor, but needed in the docs pages that can scroll
2021-10-19 22:22:53 +01:00
Dave Conway-Jones
d3f978c90c Add optional string type output to tcp request node
to be similar to tcp in. node
2021-10-19 21:34:23 +01:00
Kazuhito Yokoi
b55a8ef62a Merge branch 'dev' into dev-addjpn 2021-10-19 19:20:32 +09:00
Nick O'Leary
8d79deffb5 Merge pull request #3202 from node-red/tour-guide-fix
Fixes from 2.1.0-beta.2
2021-10-19 10:20:24 +01:00
Nick O'Leary
8158487c3e Reduce churn in setting Switch rule typedInput type 2021-10-19 10:12:13 +01:00
Nick O'Leary
0cc061196d Handle changing value before type without reseting TypedInput 2021-10-19 10:11:37 +01:00
Nick O'Leary
d0ec055222 Fix Inject/Change node restoring typedInput options 2021-10-18 21:23:42 +01:00
Nick O'Leary
ae12ddd32b Change node: Fix deepCopy checkbox spacing on Safari 2021-10-18 20:38:46 +01:00
Nick O'Leary
31da3adaa9 Fix hide/show of palette in tour and add MQTT nodes 2021-10-18 20:29:37 +01:00
Nick O'Leary
9fd5213f13 Merge pull request #3201 from node-red/link-fixes
Fix saving link out node links
2021-10-18 09:43:30 +01:00
Nick O'Leary
de882f5849 Fix saving link out node links 2021-10-18 09:32:00 +01:00
Nick O'Leary
fded1e0021 Refix #3170 - copy switch rule type when adding new rule 2021-10-14 21:47:49 +01:00
Nick O'Leary
6cb06c146d Update for 2.1.0-beta.2 2021-10-14 13:07:58 +01:00
Nick O'Leary
b8f1386ad0 Dynamic MQTT connections (#3189)
* add mqtt-control
- adds auto-connect option to broker
- add new node mqtt-control
- adds i18n messages
- adds documentation

* documentation tweaks

* built in documentation improvements

* fix tip layout causing oversized editor

* remove unused requires

* add missing `unsubscribe` dropdown option
- oddly forgotten - now added

* ensure clientid is updated dynamically

* [rewrite] move mqtt-control login into mqtt-in

* Remove dynamic label

* remove redundant mqtt-control code left overs

* Callback for brokerConn.connect (improve done())
- done is now called on connect callback

* fix race condition if connect/disconnect too fast
- node.connected and node.client.connected getting out of sync

* fix connection fail when switching protocol 3 ~ 5
- ensure protocolId is correct for protocolVersion

* change msg.subscribe prop to `msg.topic`

* unsub all topics if msg.topic is `true`

* delete temprary debugger statements

* Final rework of dynamic mqtt connections

Co-authored-by: Steve-Mcl <sdmclaughlin@gmail.com>
2021-10-14 12:05:06 +01:00
Nick O'Leary
2b38b5ea50 Merge pull request #3200 from node-red/fix-link-call-label
Fix link call label
2021-10-14 12:04:25 +01:00
Nick O'Leary
2f707a6b16 Merge pull request #3199 from node-red/switch-project-fix
Fix switching projects
2021-10-14 12:04:07 +01:00
Nick O'Leary
d4c2fcd559 Merge pull request #3198 from node-red/locale-fix
Use locale setting when installing/enabling node
2021-10-14 12:03:54 +01:00
Nick O'Leary
082970cdb7 Merge pull request #3197 from node-red/tour-projects-fix
Do not show projects-wecome dialog until welcome tour completes
2021-10-14 12:03:41 +01:00
Nick O'Leary
fe97c78977 Merge pull request #3196 from node-red/link-fixes
Fix converting selection to subflow
2021-10-14 12:03:28 +01:00
Nick O'Leary
79394aa69f Merge pull request #3195 from node-red/keyboard-fix
Avoid conflicts with native browser cmd-ctrl type shortcuts
2021-10-14 12:03:17 +01:00
Nick O'Leary
21fd6e3c21 Ensure link call node label updates when link-in changes 2021-10-14 12:00:27 +01:00
Nick O'Leary
de4944cd83 Ensure node is resized if its label changes 2021-10-14 11:53:55 +01:00
Nick O'Leary
3fde5c27ed Fix switching projects 2021-10-14 11:07:49 +01:00
Nick O'Leary
e1d492813e Use locale setting when installing/enabling node
Fixes #3194
2021-10-14 10:57:11 +01:00
Nick O'Leary
48d0ee3b6d Do not show projects-wecome dialog until welcome tour completes
Fixes #3193
2021-10-14 10:17:27 +01:00
Nick O'Leary
eebb64901c Fix converting selection to subflow
Fixes #3191
2021-10-14 09:48:35 +01:00
Nick O'Leary
60e0ed2af6 Avoid conflicts with native browser cmd-ctrl type shortcuts
Fixes #3190
2021-10-14 09:34:46 +01:00
Nick O'Leary
f030694ef4 Merge pull request #3183 from node-red/debug-filter-opts
Redesign debug filter options and make them persistant
2021-10-13 14:47:08 +01:00
Nick O'Leary
e9ed13459a Merge pull request #3184 from node-red/inject-spinner-css
Widen Inject interval box for >1 digit
2021-10-13 14:41:52 +01:00
Nick O'Leary
af1e38fdf7 Merge pull request #3185 from node-red/switch-focus-fix
Fix rule focus when switch 'otherwise' rule is used
2021-10-13 14:41:42 +01:00
Nick O'Leary
b12900e680 Merge pull request #3186 from node-red/context-tools-fix
Ensure message tools stay attached to top-level entry in Debug/Context
2021-10-13 14:41:29 +01:00
Nick O'Leary
44aa1f4a5e Filter out Link Out Return nodes in Link In edit dialog
Fixes #3187
2021-10-13 14:23:43 +01:00
Nick O'Leary
9425548a85 Ensure message tools stay attached to top-level entry in Debug/Context
Fixes #3180
2021-10-12 21:11:20 +01:00
Nick O'Leary
bfd4fc81fe Widen Inject interval box for >1 digit 2021-10-12 17:25:30 +01:00
Nick O'Leary
439af2a325 Fix rule focus when switch 'otherwise' rule is used
Fixes #3182
2021-10-12 17:25:13 +01:00
Nick O'Leary
3204b04455 Overhaul Debug sidebar filter and clear button options 2021-10-12 17:24:51 +01:00
Nick O'Leary
bed1be14ba Allow toolip action/content to be updated 2021-10-12 17:24:46 +01:00
Nick O'Leary
7cd92faf0d Separate 'focus' from 'selected' state in treeList 2021-10-12 17:24:41 +01:00
Nick O'Leary
be7e28af5d Update package dependencies 2021-10-12 17:24:10 +01:00
Nick O'Leary
8eaa762ec5 Merge branch 'dev' of github.com:node-red/node-red into dev 2021-10-11 10:01:46 +01:00
Nick O'Leary
953a9f7cd4 Update to latest node-red-admin 2021-10-11 09:58:55 +01:00
Kazuhito Yokoi
36f099d68b Add Japanese translations for Node-RED v2.1.0-beta.1 2021-10-11 16:02:18 +09:00
Nick O'Leary
c8fd5090bd Merge pull request #3178 from node-red/fix-md-help
Fix table tokenizer for node help markdown renderer
2021-10-08 18:14:10 +01:00
Nick O'Leary
155e1be494 Fix table tokenizer for node help markdown renderer 2021-10-08 17:56:24 +01:00
Nick O'Leary
cf5e125cb3 Merge pull request #3175 from node-red/tab-disabled-fix
Ensure tab state updates properly when toggling enable state
2021-10-08 15:52:16 +01:00
Nick O'Leary
764fc8477d Merge pull request #3176 from node-red/long-module-names
Improve handling of long labels in TreeList
2021-10-08 15:52:05 +01:00
Nick O'Leary
d35e62f8cf Merge pull request #3177 from node-red/scroll-tab-bar
Shift-click tab scroll arrows to jump to start/end
2021-10-08 15:51:56 +01:00
Nick O'Leary
904babdd13 Shift-click tab scroll arrows to jump to start/end 2021-10-08 15:45:17 +01:00
Nick O'Leary
154d3842a8 Improve handling of long labels in TreeList 2021-10-08 15:30:43 +01:00
Nick O'Leary
edb8a120bd Ensure tab state updates properly when toggling enable state 2021-10-08 15:09:51 +01:00
98 changed files with 2474 additions and 1249 deletions

View File

@@ -1,3 +1,123 @@
#### 2.2.0-beta.1: Beta Release
#### 2.1.4: Maintenance Release
Runtime
- fix env var access using $parent for groups (#3278) @HiroyasuNishiyama
- Add proper error handling for 404 errors when serving debug files (#3277) @knolleary
- Add Japanese translations for Node-RED v2.1.0-beta.1 (#3179) @kazuhitoyokoi
- Include full user object on login audit events (#3269) @knolleary
- Remove styling from de locale files (#3237) @knolleary
Editor
- Change tab hide button icon to an eye and add search option (#3282) @knolleary
- Fix i18n handling of namespaces with spaces in (#3281) @knolleary
- Trigger change event when autoComplete fills in input (#3280) @knolleary
- Apply CN i18n fix (#3279) @knolleary
- fix select menu label of config node to use paletteLabel (#3273) @HiroyasuNishiyama
- fix removed tab not to cause node conflict (#3275) @HiroyasuNishiyama
- Group diff fix (#3239) @knolleary
- Only toggle disabled workspace flag if on activeWorkspace (#3252) @knolleary
- Do not show status for disabled nodes (#3253) @knolleary
- Set dimension value for tour guide (#3265) @kazuhitoyokoi
- Avoid redundant initialisation of TypedInput type (#3263) @knolleary
- Don't let themes change flow port label color (#3270) @bonanitech
- Fix treeList gutter calculation to handle floating gutters (#3238) @knolleary
Nodes
- Debug: Handle RegExp types in Debug sidebar (#3251) @knolleary
- Delay: fix 2nd output when in rate limit per topic modes (#3261) @dceejay
- Link: fix to show link target when selected (#3267) @HiroyasuNishiyama
- Inject: Do not modify inject node props in oneditprepare (#3242) @knolleary
- HTTP Request: HTTP Basic Auth should always add : between username and password even if empty (#3236) @hardillb
#### 2.1.3: Maintenance Release
Runtime
- Update gen-publish script to update 'next' tag for main releases
- Add environment variable to enable/disable tours (#3221) @hardillb
- Fix loading non-default language files leaving runtime in wrong locale (#3225) @knolleary
Editor
- Refresh editor settings whenever a node is added or enabled (#3227) @knolleary
- Revert spinner css change that made it shrink in some cases (#3229) @knolleary
- Fix import notification message when importing config nodes (#3224) @knolleary
- Handle changing types of TypedInput repeatedly (#3223) @knolleary
#### 2.1.2: Maintenance Release
Runtime
- node-red-pi: Remove bash dependency (#3216) @a16bitsysop
Editor
- Improved regex for markdown renderer (#3213) @GerwinvBeek
- Fix TypedInput initialisation (#3220) @knolleary
Nodes
- MQTT: fix datatype in node config not used. fixes #3215 (#3219) @Steve-Mcl
#### 2.1.1: Maintenance Release
Editor
- Ensure tourGuide popover doesn't fall offscreen (#3212) @knolleary
- Fix issue with old inject nodes that migrated topic to 'string' type (#3210) @knolleary
- Add cache-busting query params to index.mst (#3211) @knolleary
- Fix TypedInput validation of type without options (#3207) @knolleary
#### 2.1.0: Milestone Release
Editor
- Position popover properly on a scrolled page
- Fixes from 2.1.0-beta.2 (#3202) @knolleary
Nodes
- Link Out: Fix saving link out node links (#3201) @knolleary
- Switch: Refix #3170 - copy switch rule type when adding new rule
- TCP Request: Add string option to TCP request node output (#3204) @dceejay
#### 2.1.0-beta.2: Beta Release
Editor
- Fix switching projects (#3199) @knolleary
- Use locale setting when installing/enabling node (#3198) @knolleary
- Do not show projects-wecome dialog until welcome tour completes (#3197) @knolleary
- Fix converting selection to subflow (#3196) @knolleary
- Avoid conflicts with native browser cmd-ctrl type shortcuts (#3195) @knolleary
- Ensure message tools stay attached to top-level entry in Debug/Context (#3186) @knolleary
- Ensure tab state updates properly when toggling enable state (#3175) @knolleary
- Improve handling of long labels in TreeList (#3176) @knolleary
- Shift-click tab scroll arrows to jump to start/end (#3177) @knolleary
Runtime
- Update package dependencies
- Update to latest node-red-admin
Nodes
- Dynamic MQTT connections (#3189)
- Link: Filter out Link Out Return nodes in Link In edit dialog Fixes #3187
- Link: Fix link call label (#3200) @knolleary
- Debug: Redesign debug filter options and make them persistant (#3183) @knolleary
- Inject: Widen Inject interval box for >1 digit (#3184) @knolleary
- Switch: Fix rule focus when switch 'otherwise' rule is used (#3185) @knolleary
#### 2.1.0-beta.1: Beta Release #### 2.1.0-beta.1: Beta Release
Editor Editor
@@ -46,7 +166,7 @@ Nodes
- Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary - Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary
- Delay node: add option to send intermediate messages on separate output (#3166) @knolleary - Delay node: add option to send intermediate messages on separate output (#3166) @knolleary
- Typo in http request set method translation (#3173) @mailsvb - Typo in http request set method translation (#3173) @mailsvb
#### 2.0.6: Maintenance Release #### 2.0.6: Maintenance Release
Editor Editor

View File

@@ -583,7 +583,7 @@ module.exports = function(grunt) {
grunt.registerMultiTask('attachCopyright', function() { grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src; var files = this.data.src;
var copyright = "/**\n"+ var copyright = "/**\n"+
" * Copyright JS Foundation and other contributors, http://js.foundation\n"+ " * Copyright OpenJS Foundation and other contributors, https://openjsf.org/\n"+
" *\n"+ " *\n"+
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+ " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"+
" * you may not use this file except in compliance with the License.\n"+ " * you may not use this file except in compliance with the License.\n"+

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -26,9 +26,9 @@
} }
], ],
"dependencies": { "dependencies": {
"acorn": "8.5.0", "acorn": "8.6.0",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.6.3", "ajv": "8.8.2",
"async-mutex": "0.3.2", "async-mutex": "0.3.2",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
@@ -37,7 +37,7 @@
"clone": "2.1.2", "clone": "2.1.2",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie": "0.4.1", "cookie": "0.4.1",
"cookie-parser": "1.4.5", "cookie-parser": "1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.0.1", "denque": "2.0.1",
@@ -46,11 +46,11 @@
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"got": "11.8.2", "got": "11.8.3",
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"i18next": "21.2.4", "i18next": "21.5.4",
"iconv-lite": "0.6.3", "iconv-lite": "0.6.3",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
@@ -60,22 +60,22 @@
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.6", "memorystore": "1.6.6",
"mime": "2.5.2", "mime": "2.5.2",
"moment-timezone": "0.5.33", "moment-timezone": "0.5.34",
"mqtt": "4.2.8", "mqtt": "4.2.8",
"multer": "1.4.3", "multer": "1.4.3",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-red-admin": "^2.2.0", "node-red-admin": "^2.2.1",
"nopt": "5.0.0", "nopt": "5.0.0",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.1",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"passport": "0.5.0", "passport": "0.5.0",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.1", "raw-body": "2.4.2",
"semver": "7.3.5", "semver": "7.3.5",
"tar": "6.1.11", "tar": "6.1.11",
"tough-cookie": "4.0.0", "tough-cookie": "4.0.0",
"uglify-js": "3.14.2", "uglify-js": "3.14.4",
"uuid": "8.3.2", "uuid": "8.3.2",
"ws": "7.5.1", "ws": "7.5.1",
"xml2js": "0.4.23" "xml2js": "0.4.23"
@@ -107,13 +107,13 @@
"i18next-http-backend": "1.3.1", "i18next-http-backend": "1.3.1",
"jquery-i18next": "1.2.1", "jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "3.0.4", "marked": "3.0.7",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "9.1.2", "mocha": "9.1.3",
"node-red-node-test-helper": "^0.2.7", "node-red-node-test-helper": "^0.2.7",
"nodemon": "2.0.13", "nodemon": "2.0.15",
"proxy": "^1.0.2", "proxy": "^1.0.2",
"sass": "1.42.1", "sass": "1.44.0",
"should": "13.2.3", "should": "13.2.3",
"sinon": "11.1.2", "sinon": "11.1.2",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",

View File

@@ -141,7 +141,7 @@ function completeVerify(profile,done) {
Users.authenticate(profile).then(function(user) { Users.authenticate(profile).then(function(user) {
if (user) { if (user) {
Tokens.create(user.username,"node-red-editor",user.permissions).then(function(tokens) { Tokens.create(user.username,"node-red-editor",user.permissions).then(function(tokens) {
log.audit({event: "auth.login",username:user.username,scope:user.permissions}); log.audit({event: "auth.login",user,username:user.username,scope:user.permissions});
user.tokens = tokens; user.tokens = tokens;
done(null,user); done(null,user);
}); });

View File

@@ -93,7 +93,7 @@ var passwordTokenExchange = function(client, username, password, scope, done) {
return logEntry.user !== username; return logEntry.user !== username;
}); });
Tokens.create(username,client.id,scope).then(function(tokens) { Tokens.create(username,client.id,scope).then(function(tokens) {
log.audit({event: "auth.login",username:username,client:client.id,scope:scope}); log.audit({event: "auth.login",user,username:username,client:client.id,scope:scope});
done(null,tokens.accessToken,null,{expires_in:tokens.expires_in}); done(null,tokens.accessToken,null,{expires_in:tokens.expires_in});
}); });
} else { } else {

View File

@@ -48,9 +48,10 @@ module.exports = {
var prevLang = i18n.i.language; var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default // Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){ i18n.i.changeLanguage(lang, function(){
var catalog = loadResource(lang, namespace); i18n.i.changeLanguage(prevLang, function() {
res.json(catalog||{}); var catalog = loadResource(lang, namespace);
res.json(catalog||{});
});
}); });
i18n.i.changeLanguage(prevLang);
} }
} }

View File

@@ -27,7 +27,8 @@ var defaultContext = {
tabicon: { tabicon: {
icon: "red/images/node-red-icon-black.svg", icon: "red/images/node-red-icon-black.svg",
colour: "#8f0000" colour: "#8f0000"
} },
version: require(path.join(__dirname,"../../package.json")).version
}, },
header: { header: {
title: "Node-RED", title: "Node-RED",
@@ -227,6 +228,11 @@ module.exports = {
if (theme.theme) { if (theme.theme) {
themeSettings.theme = theme.theme; themeSettings.theme = theme.theme;
} }
if (theme.hasOwnProperty("tours")) {
themeSettings.tours = theme.tours;
}
return themeApp; return themeApp;
}, },
context: async function() { context: async function() {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,8 +16,8 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "2.1.0-beta.1", "@node-red/util": "2.2.0-beta.1",
"@node-red/editor-client": "2.1.0-beta.1", "@node-red/editor-client": "2.2.0-beta.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.0", "body-parser": "1.19.0",
"clone": "2.1.2", "clone": "2.1.2",
@@ -28,7 +28,7 @@
"mime": "2.5.2", "mime": "2.5.2",
"multer": "1.4.3", "multer": "1.4.3",
"mustache": "4.2.0", "mustache": "4.2.0",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.1",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"passport": "0.5.0", "passport": "0.5.0",

View File

@@ -59,6 +59,8 @@
"hideOtherFlows": "Hide other flows", "hideOtherFlows": "Hide other flows",
"showAllFlows": "Show all flows", "showAllFlows": "Show all flows",
"hideAllFlows": "Hide all flows", "hideAllFlows": "Hide all flows",
"hiddenFlows": "List __count__ hidden flow",
"hiddenFlows_plural": "List __count__ hidden flows",
"showLastHiddenFlow": "Show last hidden flow", "showLastHiddenFlow": "Show last hidden flow",
"listFlows": "List flows", "listFlows": "List flows",
"listSubflows": "List subflows", "listSubflows": "List subflows",
@@ -90,6 +92,7 @@
"palette": { "palette": {
"show": "Show palette" "show": "Show palette"
}, },
"edit": "Edit",
"settings": "Settings", "settings": "Settings",
"userSettings": "User Settings", "userSettings": "User Settings",
"nodes": "Nodes", "nodes": "Nodes",
@@ -668,7 +671,8 @@
"unusedConfigNodes": "Unused configuration nodes", "unusedConfigNodes": "Unused configuration nodes",
"invalidNodes": "Invalid nodes", "invalidNodes": "Invalid nodes",
"uknownNodes": "Unknown nodes", "uknownNodes": "Unknown nodes",
"unusedSubflows": "Unused subflows" "unusedSubflows": "Unused subflows",
"hiddenFlows": "Hidden flows"
} }
}, },
"help": { "help": {
@@ -1135,6 +1139,7 @@
"defaultValue": "Default value" "defaultValue": "Default value"
}, },
"tourGuide": { "tourGuide": {
"takeATour": "Take a tour",
"start": "Start", "start": "Start",
"next": "Next" "next": "Next"
}, },

View File

@@ -54,7 +54,14 @@
"delete": "本当に '__label__' を削除しますか?", "delete": "本当に '__label__' を削除しますか?",
"dropFlowHere": "ここにフローをドロップしてください", "dropFlowHere": "ここにフローをドロップしてください",
"addFlow": "フローの追加", "addFlow": "フローの追加",
"addFlowToRight": "右側にフローを追加",
"hideFlow": "フローを非表示",
"hideOtherFlows": "他のフローを非表示",
"showAllFlows": "全てのフローを表示",
"hideAllFlows": "全てのフローを非表示",
"showLastHiddenFlow": "最後に非表示にしたフローを表示",
"listFlows": "フロー一覧", "listFlows": "フロー一覧",
"listSubflows": "サブフロー一覧",
"status": "状態", "status": "状態",
"enabled": "有効", "enabled": "有効",
"disabled": "無効", "disabled": "無効",
@@ -83,6 +90,7 @@
"palette": { "palette": {
"show": "パレットを表示" "show": "パレットを表示"
}, },
"edit": "編集",
"settings": "設定", "settings": "設定",
"userSettings": "ユーザ設定", "userSettings": "ユーザ設定",
"nodes": "ノード", "nodes": "ノード",
@@ -105,6 +113,7 @@
"editPalette": "パレットの管理", "editPalette": "パレットの管理",
"other": "その他", "other": "その他",
"showTips": "ヒントを表示", "showTips": "ヒントを表示",
"showWelcomeTours": "新バージョンのガイドツアーを表示",
"help": "Node-REDウェブサイト", "help": "Node-REDウェブサイト",
"projects": "プロジェクト", "projects": "プロジェクト",
"projects-new": "新規", "projects-new": "新規",
@@ -116,7 +125,20 @@
"groupSelection": "選択部分をグループ化", "groupSelection": "選択部分をグループ化",
"ungroupSelection": "選択部分をグループ解除", "ungroupSelection": "選択部分をグループ解除",
"groupMergeSelection": "選択部分をマージ", "groupMergeSelection": "選択部分をマージ",
"groupRemoveSelection": "グループから削除" "groupRemoveSelection": "グループから削除",
"arrange": "配置",
"alignLeft": "左揃え",
"alignCenter": "左右中央揃え",
"alignRight": "右揃え",
"alignTop": "上揃え",
"alignMiddle": "上下中央揃え",
"alignBottom": "下揃え",
"distributeHorizontally": "左右に整列",
"distributeVertically": "上下に整列",
"moveToBack": "最背面へ移動",
"moveToFront": "最前面へ移動",
"moveBackwards": "背面へ移動",
"moveForwards": "前面へ移動"
} }
}, },
"actions": { "actions": {
@@ -451,7 +473,8 @@
"global": "グローバル", "global": "グローバル",
"workspace": "ワークスペース", "workspace": "ワークスペース",
"selectAll": "全てのノードを選択", "selectAll": "全てのノードを選択",
"selectAllConnected": "接続された全てのノードを選択", "selectNone": "選択を外す",
"selectAllConnected": "接続されたノードを選択",
"addRemoveNode": "ノードの選択、選択解除", "addRemoveNode": "ノードの選択、選択解除",
"editSelected": "選択したノードを編集", "editSelected": "選択したノードを編集",
"deleteSelected": "選択したノードや接続を削除", "deleteSelected": "選択したノードや接続を削除",
@@ -461,10 +484,13 @@
"moveNode": "選択したノードを移動(移動量大)", "moveNode": "選択したノードを移動(移動量大)",
"toggleSidebar": "サイドバーの表示/非表示", "toggleSidebar": "サイドバーの表示/非表示",
"togglePalette": "パレットの表示/非表示", "togglePalette": "パレットの表示/非表示",
"copyNode": "選択したノードをコピー", "copyNode": "ノードをコピー",
"cutNode": "選択したノードを切り取り", "cutNode": "ノードを切り取り",
"pasteNode": "ノードを貼り付け", "pasteNode": "ノードを貼り付け",
"copyGroupStyle": "グループ様式をコピー",
"pasteGroupStyle": "グループ様式を貼り付け",
"undoChange": "変更操作を戻す", "undoChange": "変更操作を戻す",
"redoChange": "変更操作をやり直し",
"searchBox": "ノードを検索", "searchBox": "ノードを検索",
"managePalette": "パレットの管理", "managePalette": "パレットの管理",
"actionList": "動作一覧" "actionList": "動作一覧"
@@ -519,7 +545,8 @@
"nodeEnabled_plural": "ノードを有効化しました:", "nodeEnabled_plural": "ノードを有効化しました:",
"nodeDisabled": "ノードを無効化しました:", "nodeDisabled": "ノードを無効化しました:",
"nodeDisabled_plural": "ノードを無効化しました:", "nodeDisabled_plural": "ノードを無効化しました:",
"nodeUpgraded": "ノードモジュール __module__ をバージョン __version__ へ更新しました" "nodeUpgraded": "ノードモジュール __module__ をバージョン __version__ へ更新しました",
"unknownNodeRegistered": "ノードの読み込みエラー: <ul><li>__type__<br>__error__</li></ul>"
}, },
"editor": { "editor": {
"title": "パレットの管理", "title": "パレットの管理",
@@ -1108,6 +1135,11 @@
"preview": "UIプレビュー", "preview": "UIプレビュー",
"defaultValue": "デフォルト値" "defaultValue": "デフォルト値"
}, },
"tourGuide": {
"takeATour": "ツアーを開始",
"start": "開始",
"next": "次へ"
},
"languages": { "languages": {
"de": "ドイツ語", "de": "ドイツ語",
"en-US": "英語", "en-US": "英語",

View File

@@ -225,7 +225,7 @@
"compact": "紧凑", "compact": "紧凑",
"formatted": "已格式化", "formatted": "已格式化",
"copy": "导出到剪贴板", "copy": "导出到剪贴板",
"export": "到处到库", "export": "导出到库",
"exportAs": "导出为", "exportAs": "导出为",
"overwrite": "替换", "overwrite": "替换",
"exists": "<p><b>\"__file__\"</b>已存在</p><p>是否要替换它?</p>" "exists": "<p><b>\"__file__\"</b>已存在</p><p>是否要替换它?</p>"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-client", "name": "@node-red/editor-client",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -38,6 +38,8 @@ RED.i18n = (function() {
defaultNS: "editor", defaultNS: "editor",
fallbackLng: ['en-US'], fallbackLng: ['en-US'],
returnObjects: true, returnObjects: true,
keySeparator: ".",
nsSeparator: ":",
interpolation: { interpolation: {
unescapeSuffix: 'HTML', unescapeSuffix: 'HTML',
escapeValue: false, escapeValue: false,

View File

@@ -266,8 +266,8 @@ RED.nodes = (function() {
}, },
moveNode: function(n, newZ) { moveNode: function(n, newZ) {
api.removeNode(n); api.removeNode(n);
tabMap[newZ] = tabMap[newZ] || []; n.z = newZ;
tabMap[newZ].push(n); api.addNode(n)
}, },
moveNodesForwards: function(nodes) { moveNodesForwards: function(nodes) {
var result = []; var result = [];
@@ -719,29 +719,29 @@ RED.nodes = (function() {
moveGroupToTab(node,z); moveGroupToTab(node,z);
return; return;
} }
var oldZ = node.z;
allNodes.moveNode(node,z); allNodes.moveNode(node,z);
var nl = nodeLinks[node.id]; var nl = nodeLinks[node.id];
if (nl) { if (nl) {
nl.in.forEach(function(l) { nl.in.forEach(function(l) {
var idx = linkTabMap[node.z].indexOf(l); var idx = linkTabMap[oldZ].indexOf(l);
if (idx != -1) { if (idx != -1) {
linkTabMap[node.z].splice(idx, 1); linkTabMap[oldZ].splice(idx, 1);
} }
if ((l.source.z === z) && linkTabMap[z]) { if ((l.source.z === z) && linkTabMap[z]) {
linkTabMap[z].push(l); linkTabMap[z].push(l);
} }
}); });
nl.out.forEach(function(l) { nl.out.forEach(function(l) {
var idx = linkTabMap[node.z].indexOf(l); var idx = linkTabMap[oldZ].indexOf(l);
if (idx != -1) { if (idx != -1) {
linkTabMap[node.z].splice(idx, 1); linkTabMap[oldZ].splice(idx, 1);
} }
if ((l.target.z === z) && linkTabMap[z]) { if ((l.target.z === z) && linkTabMap[z]) {
linkTabMap[z].push(l); linkTabMap[z].push(l);
} }
}); });
} }
node.z = z;
RED.events.emit("nodes:change",node); RED.events.emit("nodes:change",node);
} }
function moveGroupToTab(group, z) { function moveGroupToTab(group, z) {
@@ -805,7 +805,6 @@ RED.nodes = (function() {
var removedGroups = []; var removedGroups = [];
if (ws) { if (ws) {
delete workspaces[id]; delete workspaces[id];
allNodes.removeTab(id);
delete linkTabMap[id]; delete linkTabMap[id];
workspacesOrder.splice(workspacesOrder.indexOf(id),1); workspacesOrder.splice(workspacesOrder.indexOf(id),1);
var i; var i;
@@ -843,6 +842,7 @@ RED.nodes = (function() {
for (i=removedGroups.length-1; i>=0; i--) { for (i=removedGroups.length-1; i>=0; i--) {
removeGroup(removedGroups[i]); removeGroup(removedGroups[i]);
} }
allNodes.removeTab(id);
RED.events.emit('flows:remove',ws); RED.events.emit('flows:remove',ws);
} }
return {nodes:removedNodes,links:removedLinks, groups: removedGroups}; return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
@@ -1097,6 +1097,11 @@ RED.nodes = (function() {
// Until we know how that can happen, add a filter here to remove them // Until we know how that can happen, add a filter here to remove them
node.nodes = node.nodes.filter(function(n) { return !!n }).map(function(n) { return n.id }); node.nodes = node.nodes.filter(function(n) { return !!n }).map(function(n) { return n.id });
} }
if (n.type === "tab" || n.type === "group") {
if (node.env && node.env.length === 0) {
delete node.env;
}
}
if (n._def.category != "config") { if (n._def.category != "config") {
node.x = n.x; node.x = n.x;
node.y = n.y; node.y = n.y;
@@ -2384,7 +2389,6 @@ RED.nodes = (function() {
} }
function clear() { function clear() {
allNodes.clear();
links = []; links = [];
linkTabMap = {}; linkTabMap = {};
nodeLinks = {}; nodeLinks = {};
@@ -2405,6 +2409,8 @@ RED.nodes = (function() {
initialLoad = null; initialLoad = null;
workspaces = {}; workspaces = {};
allNodes.clear();
RED.nodes.dirty(false); RED.nodes.dirty(false);
RED.view.redraw(true, true); RED.view.redraw(true, true);
RED.palette.refresh(); RED.palette.refresh();

View File

@@ -201,6 +201,7 @@ var RED = (function() {
RED.projects.refresh(function(activeProject) { RED.projects.refresh(function(activeProject) {
loadFlows(function() { loadFlows(function() {
RED.sidebar.info.refresh() RED.sidebar.info.refresh()
var showProjectWelcome = false;
if (!activeProject) { if (!activeProject) {
// Projects enabled but no active project // Projects enabled but no active project
RED.menu.setDisabled('menu-item-projects-open',true); RED.menu.setDisabled('menu-item-projects-open',true);
@@ -208,10 +209,10 @@ var RED = (function() {
if (activeProject === false) { if (activeProject === false) {
// User previously decline the migration to projects. // User previously decline the migration to projects.
} else { // null/undefined } else { // null/undefined
RED.projects.showStartup(); showProjectWelcome = true;
} }
} }
completeLoad(); completeLoad(showProjectWelcome);
}); });
}); });
} else { } else {
@@ -251,6 +252,9 @@ var RED = (function() {
if (/^#flow\/.+$/.test(currentHash)) { if (/^#flow\/.+$/.test(currentHash)) {
RED.workspaces.show(currentHash.substring(6),true); RED.workspaces.show(currentHash.substring(6),true);
} }
if (RED.workspaces.active() === 0 && RED.workspaces.count() > 0) {
RED.workspaces.show(RED.nodes.getWorkspaceOrder()[0])
}
} catch(err) { } catch(err) {
console.warn(err); console.warn(err);
RED.notify( RED.notify(
@@ -267,7 +271,7 @@ var RED = (function() {
}); });
} }
function completeLoad() { function completeLoad(showProjectWelcome) {
var persistentNotifications = {}; var persistentNotifications = {};
RED.comms.subscribe("notification/#",function(topic,msg) { RED.comms.subscribe("notification/#",function(topic,msg) {
var parts = topic.split("/"); var parts = topic.split("/");
@@ -471,22 +475,33 @@ var RED = (function() {
var typeList; var typeList;
var info; var info;
if (topic == "notification/node/added") { if (topic == "notification/node/added") {
var addedTypes = []; RED.settings.refreshSettings(function(err, data) {
msg.forEach(function(m) { var addedTypes = [];
var id = m.id; msg.forEach(function(m) {
RED.nodes.addNodeSet(m); var id = m.id;
addedTypes = addedTypes.concat(m.types); RED.nodes.addNodeSet(m);
RED.i18n.loadNodeCatalog(id, function() { addedTypes = addedTypes.concat(m.types);
$.get('nodes/'+id, function(data) { RED.i18n.loadNodeCatalog(id, function() {
appendNodeConfig(data); var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
$.ajax({
headers: {
"Accept":"text/html",
"Accept-Language": lang
},
cache: false,
url: 'nodes/'+id,
success: function(data) {
appendNodeConfig(data);
}
});
}); });
}); });
}); if (addedTypes.length) {
if (addedTypes.length) { typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>"; RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success"); }
} loadIconList();
loadIconList(); })
} else if (topic == "notification/node/removed") { } else if (topic == "notification/node/removed") {
for (i=0;i<msg.length;i++) { for (i=0;i<msg.length;i++) {
m = msg[i]; m = msg[i];
@@ -499,18 +514,29 @@ var RED = (function() {
loadIconList(); loadIconList();
} else if (topic == "notification/node/enabled") { } else if (topic == "notification/node/enabled") {
if (msg.types) { if (msg.types) {
info = RED.nodes.getNodeSet(msg.id); RED.settings.refreshSettings(function(err, data) {
if (info.added) { info = RED.nodes.getNodeSet(msg.id);
RED.nodes.enableNodeSet(msg.id); if (info.added) {
typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>"; RED.nodes.enableNodeSet(msg.id);
RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
} else {
$.get('nodes/'+msg.id, function(data) {
appendNodeConfig(data);
typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>"; typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success"); RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
}); } else {
} var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
$.ajax({
headers: {
"Accept":"text/html",
"Accept-Language": lang
},
cache: false,
url: 'nodes/'+msg.id,
success: function(data) {
appendNodeConfig(data);
typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
}
});
}
});
} }
} else if (topic == "notification/node/disabled") { } else if (topic == "notification/node/disabled") {
if (msg.types) { if (msg.types) {
@@ -535,18 +561,24 @@ var RED = (function() {
setTimeout(function() { setTimeout(function() {
loader.end(); loader.end();
checkFirstRun(); checkFirstRun(function() {
if (showProjectWelcome) {
RED.projects.showStartup();
}
});
},100); },100);
} }
function checkFirstRun() { function checkFirstRun(done) {
if (RED.settings.theme("tours") === false) { if (RED.settings.theme("tours") === false) {
done();
return; return;
} }
if (!RED.settings.get("editor.view.view-show-welcome-tours", true)) { if (!RED.settings.get("editor.view.view-show-welcome-tours", true)) {
done();
return; return;
} }
RED.actions.invoke("core:show-welcome-tour", RED.settings.get("editor.tours.welcome")); RED.actions.invoke("core:show-welcome-tour", RED.settings.get("editor.tours.welcome"), done);
} }
function buildMainMenu() { function buildMainMenu() {
@@ -558,7 +590,7 @@ var RED = (function() {
{id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"} {id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
]}); ]});
} }
menuOptions.push({id:"menu-item-edit-menu", label:"Edit", options: [ menuOptions.push({id:"menu-item-edit-menu", label:RED._("menu.label.edit"), options: [
{id: "menu-item-edit-undo", label:RED._("keyboard.undoChange"), disabled: true, onselect: "core:undo"}, {id: "menu-item-edit-undo", label:RED._("keyboard.undoChange"), disabled: true, onselect: "core:undo"},
{id: "menu-item-edit-redo", label:RED._("keyboard.redoChange"), disabled: true, onselect: "core:redo"}, {id: "menu-item-edit-redo", label:RED._("keyboard.redoChange"), disabled: true, onselect: "core:redo"},
null, null,

View File

@@ -125,7 +125,7 @@ RED.settings = (function () {
load(done); load(done);
} }
var load = function(done) { var refreshSettings = function(done) {
$.ajax({ $.ajax({
headers: { headers: {
"Accept": "application/json" "Accept": "application/json"
@@ -135,6 +135,23 @@ RED.settings = (function () {
url: 'settings', url: 'settings',
success: function (data) { success: function (data) {
setProperties(data); setProperties(data);
done(null, data);
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
window.location.search = "";
}
RED.user.login(function() { refreshSettings(done); });
} else {
console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
}
}
});
}
var load = function(done) {
refreshSettings(function(err, data) {
if (!err) {
if (!RED.settings.user || RED.settings.user.anonymous) { if (!RED.settings.user || RED.settings.user.anonymous) {
RED.settings.remove("auth-tokens"); RED.settings.remove("auth-tokens");
} }
@@ -147,18 +164,8 @@ RED.settings = (function () {
console.log("D3",d3.version); console.log("D3",d3.version);
console.groupEnd(); console.groupEnd();
loadUserSettings(done); loadUserSettings(done);
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
window.location.search = "";
}
RED.user.login(function() { load(done); });
} else {
console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
}
} }
}); })
}; };
function loadUserSettings(done) { function loadUserSettings(done) {
@@ -234,6 +241,7 @@ RED.settings = (function () {
init: init, init: init,
load: load, load: load,
loadUserSettings: loadUserSettings, loadUserSettings: loadUserSettings,
refreshSettings: refreshSettings,
set: set, set: set,
get: get, get: get,
remove: remove, remove: remove,

View File

@@ -21,7 +21,7 @@
* value: String : the value to insert if selected * value: String : the value to insert if selected
* label: String|DOM Element : the label to display in the dropdown. * label: String|DOM Element : the label to display in the dropdown.
* } * }
* *
*/ */
$.widget( "nodered.autoComplete", { $.widget( "nodered.autoComplete", {
@@ -62,7 +62,7 @@
maxHeight: 200, maxHeight: 200,
class: "red-ui-autoComplete-container", class: "red-ui-autoComplete-container",
options: completions, options: completions,
onselect: (opt) => { this.element.val(opt.value); this.element.focus() }, onselect: (opt) => { this.element.val(opt.value); this.element.focus(); this.element.trigger("change") },
onclose: () => { this.completionMenuShown = false; delete this.menu; this.element.focus()} onclose: () => { this.completionMenuShown = false; delete this.menu; this.element.focus()}
}); });
this.menu.show({ this.menu.show({

View File

@@ -436,18 +436,17 @@ RED.popover = (function() {
return { return {
create: createPopover, create: createPopover,
tooltip: function(target,content, action) { tooltip: function(target,content, action) {
var label = content; var label = function() {
if (action) { var label = content;
label = function() { if (action) {
var label = content;
var shortcut = RED.keyboard.getShortcut(action); var shortcut = RED.keyboard.getShortcut(action);
if (shortcut && shortcut.key) { if (shortcut && shortcut.key) {
label = $('<span>'+content+' <span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span></span>'); label = $('<span>'+content+' <span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span></span>');
} }
return label;
} }
return label;
} }
return RED.popover.create({ var popover = RED.popover.create({
tooltip: true, tooltip: true,
target:target, target:target,
trigger: "hover", trigger: "hover",
@@ -456,6 +455,14 @@ RED.popover = (function() {
content: label, content: label,
delay: { show: 750, hide: 50 } delay: { show: 750, hide: 50 }
}); });
popover.setContent = function(newContent) {
content = newContent;
}
popover.setAction = function(newAction) {
action = newAction;
}
return popover;
}, },
menu: function(options) { menu: function(options) {
var list = $('<ul class="red-ui-menu"></ul>'); var list = $('<ul class="red-ui-menu"></ul>');
@@ -596,7 +603,7 @@ RED.popover = (function() {
var panelWidth = panel.width(); var panelWidth = panel.width();
var top = (targetHeight+pos.top) + offset[1]; var top = (targetHeight+pos.top) + offset[1];
if (top+panelHeight > $(window).height()) { if (top+panelHeight-$(document).scrollTop() > $(window).height()) {
top -= (top+panelHeight)-$(window).height() + 5; top -= (top+panelHeight)-$(window).height() + 5;
} }
if (top < 0) { if (top < 0) {

View File

@@ -117,6 +117,8 @@ RED.tabs = (function() {
menuOptions = options.menu() menuOptions = options.menu()
} else if (Array.isArray(options.menu)) { } else if (Array.isArray(options.menu)) {
menuOptions = options.menu; menuOptions = options.menu;
} else if (typeof options.menu === 'function') {
menuOptions = options.menu();
} }
menu = RED.menu.init({options: menuOptions}); menu = RED.menu.init({options: menuOptions});
menu.attr("id",options.id+"-menu"); menu.attr("id",options.id+"-menu");
@@ -164,9 +166,9 @@ RED.tabs = (function() {
} }
}) })
scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a"); scrollLeft = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();}); scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();});
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a"); scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();}); scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,evt.shiftKey?('+='+(scrollContainer[0].scrollWidth - scrollContainer.width()-scrollContainer.scrollLeft())):'+=150') }).on('click',function(evt){ evt.preventDefault();});
} }
if (options.collapsible) { if (options.collapsible) {
@@ -809,15 +811,18 @@ RED.tabs = (function() {
event.preventDefault(); event.preventDefault();
removeTab(tab.id); removeTab(tab.id);
}); });
RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
} }
if (tab.hideable) { if (tab.hideable) {
li.addClass("red-ui-tabs-closeable") li.addClass("red-ui-tabs-closeable")
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li); var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
closeLink.append('<i class="fa fa-times" />'); closeLink.append('<i class="fa fa-eye" />');
closeLink.append('<i class="fa fa-eye-slash" />');
closeLink.on("click",function(event) { closeLink.on("click",function(event) {
event.preventDefault(); event.preventDefault();
hideTab(tab.id); hideTab(tab.id);
}); });
RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
} }
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li); var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
@@ -826,7 +831,8 @@ RED.tabs = (function() {
$('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges); $('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
} }
link.attr("title",tab.label); // link.attr("title",tab.label);
RED.popover.tooltip(link,function() { return tab.label})
if (options.onadd) { if (options.onadd) {
options.onadd(tab); options.onadd(tab);
@@ -945,7 +951,6 @@ RED.tabs = (function() {
renameTab: function(id,label) { renameTab: function(id,label) {
tabs[id].label = label; tabs[id].label = label;
var tab = ul.find("a[href='#"+id+"']"); var tab = ul.find("a[href='#"+id+"']");
tab.attr("title",label);
tab.find("span.red-ui-text-bidi-aware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label)); tab.find("span.red-ui-text-bidi-aware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
updateTabWidths(); updateTabWidths();
}, },

View File

@@ -24,6 +24,9 @@
* - rootSortable: boolean - if 'sortable' is set, then setting this to * - rootSortable: boolean - if 'sortable' is set, then setting this to
* false, prevents items being sorted to the * false, prevents items being sorted to the
* top level of the tree * top level of the tree
* - autoSelect: boolean - default true - triggers item selection when navigating
* list by keyboard. If the list has checkboxed items
* you probably want to set this to false
* *
* methods: * methods:
* - data(items) - clears existing items and replaces with new data * - data(items) - clears existing items and replaces with new data
@@ -50,6 +53,7 @@
* deferBuild: true/false, // don't build any ui elements for the item's children * deferBuild: true/false, // don't build any ui elements for the item's children
* until it is expanded by the user. * until it is expanded by the user.
* element: // custom dom element to use for the item - ignored if `label` is set * element: // custom dom element to use for the item - ignored if `label` is set
* collapsible: true/false, // prevent a parent item from being collapsed. default true.
* } * }
* ] * ]
* *
@@ -90,77 +94,99 @@
$.widget( "nodered.treeList", { $.widget( "nodered.treeList", {
_create: function() { _create: function() {
var that = this; var that = this;
var autoSelect = true;
if (that.options.autoSelect === false) {
autoSelect = false;
}
this.element.addClass('red-ui-treeList'); this.element.addClass('red-ui-treeList');
this.element.attr("tabIndex",0); this.element.attr("tabIndex",0);
var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element); var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
this.element.on('keydown', function(evt) { this.element.on('keydown', function(evt) {
var selected = that._topList.find(".selected").parent().data('data'); var focussed = that._topList.find(".focus").parent().data('data');
if (!selected && (evt.keyCode === 40 || evt.keyCode === 38)) { if (!focussed && (evt.keyCode === 40 || evt.keyCode === 38)) {
that.select(that._data[0]); if (that._data[0]) {
if (autoSelect) {
that.select(that._data[0]);
} else {
that._topList.find(".focus").removeClass("focus")
}
that._data[0].treeList.label.addClass('focus')
}
return; return;
} }
var target; var target;
switch(evt.keyCode) { switch(evt.keyCode) {
case 32: // SPACE
case 13: // ENTER case 13: // ENTER
if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
return
}
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
if (selected.children) { if (focussed.checkbox) {
if (selected.treeList.container.hasClass("expanded")) { focussed.treeList.checkbox.trigger("click");
selected.treeList.collapse() } else if (focussed.radio) {
focussed.treeList.radio.trigger("click");
} else if (focussed.children) {
if (focussed.treeList.container.hasClass("expanded")) {
focussed.treeList.collapse()
} else { } else {
selected.treeList.expand() focussed.treeList.expand()
} }
} else { } else {
that._trigger("confirm",null,selected) that._trigger("confirm",null,focussed)
} }
break; break;
case 37: // LEFT case 37: // LEFT
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
if (selected.children&& selected.treeList.container.hasClass("expanded")) { if (focussed.children&& focussed.treeList.container.hasClass("expanded")) {
selected.treeList.collapse() focussed.treeList.collapse()
} else if (selected.parent) { } else if (focussed.parent) {
target = selected.parent; target = focussed.parent;
} }
break; break;
case 38: // UP case 38: // UP
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
target = that._getPreviousSibling(selected); target = that._getPreviousSibling(focussed);
if (target) { if (target) {
target = that._getLastDescendant(target); target = that._getLastDescendant(target);
} }
if (!target && selected.parent) { if (!target && focussed.parent) {
target = selected.parent; target = focussed.parent;
} }
break; break;
case 39: // RIGHT case 39: // RIGHT
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
if (selected.children) { if (focussed.children) {
if (!selected.treeList.container.hasClass("expanded")) { if (!focussed.treeList.container.hasClass("expanded")) {
selected.treeList.expand() focussed.treeList.expand()
} }
} }
break break
case 40: //DOWN case 40: //DOWN
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
if (selected.children && Array.isArray(selected.children) && selected.children.length > 0 && selected.treeList.container.hasClass("expanded")) { if (focussed.children && Array.isArray(focussed.children) && focussed.children.length > 0 && focussed.treeList.container.hasClass("expanded")) {
target = selected.children[0]; target = focussed.children[0];
} else { } else {
target = that._getNextSibling(selected); target = that._getNextSibling(focussed);
while (!target && selected.parent) { while (!target && focussed.parent) {
selected = selected.parent; focussed = focussed.parent;
target = that._getNextSibling(selected); target = that._getNextSibling(focussed);
} }
} }
break break
} }
if (target) { if (target) {
that.select(target); if (autoSelect) {
that.select(target);
} else {
that._topList.find(".focus").removeClass("focus")
}
target.treeList.label.addClass('focus')
} }
}); });
this._data = []; this._data = [];
@@ -314,7 +340,7 @@
if (child.depth !== parent.depth+1) { if (child.depth !== parent.depth+1) {
child.depth = parent.depth+1; child.depth = parent.depth+1;
// var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20)); // var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20)); var labelPaddingWidth = (((child.gutter&&!child.gutter.hasClass("red-ui-treeList-gutter-float"))?child.gutter.width()+2:0)+(child.depth*20));
child.treeList.labelPadding.width(labelPaddingWidth+'px'); child.treeList.labelPadding.width(labelPaddingWidth+'px');
if (child.element) { if (child.element) {
$(child.element).css({ $(child.element).css({
@@ -463,6 +489,9 @@
container.addClass("expanded"); container.addClass("expanded");
} }
item.treeList.collapse = function() { item.treeList.collapse = function() {
if (item.collapsible === false) {
return
}
if (!item.children) { if (!item.children) {
return; return;
} }
@@ -533,10 +562,12 @@
}).appendTo(label) }).appendTo(label)
} }
// var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (depth * 20) var labelPaddingWidth = ((item.gutter&&!item.gutter.hasClass("red-ui-treeList-gutter-float"))?item.gutter.width()+2:0)+(depth*20);
item.treeList.labelPadding = $('<span>').css({ item.treeList.labelPadding = $('<span>').css({
display: "inline-block", display: "inline-block",
"flex-shrink": 0,
width: labelPaddingWidth+'px' width: labelPaddingWidth+'px'
}).appendTo(label); }).appendTo(label);
@@ -582,7 +613,7 @@
// Already a parent because we've got the angle-right icon // Already a parent because we've got the angle-right icon
return; return;
} }
$('<i class="fa fa-angle-right" />').appendTo(treeListIcon); $('<i class="fa fa-angle-right" />').toggleClass("hide",item.collapsible === false).appendTo(treeListIcon);
treeListIcon.on("click.red-ui-treeList-expand", function(e) { treeListIcon.on("click.red-ui-treeList-expand", function(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@@ -633,6 +664,8 @@
label.on("click", function(e) { label.on("click", function(e) {
e.stopPropagation(); e.stopPropagation();
cb.trigger("click"); cb.trigger("click");
that._topList.find(".focus").removeClass("focus")
label.addClass('focus')
}) })
} }
item.treeList.select = function(v) { item.treeList.select = function(v) {
@@ -640,6 +673,7 @@
cb.trigger("click"); cb.trigger("click");
} }
} }
item.treeList.checkbox = cb;
selectWrapper.appendTo(label) selectWrapper.appendTo(label)
} else if (item.radio) { } else if (item.radio) {
var selectWrapper = $('<span class="red-ui-treeList-icon"></span>'); var selectWrapper = $('<span class="red-ui-treeList-icon"></span>');
@@ -668,6 +702,8 @@
label.on("click", function(e) { label.on("click", function(e) {
e.stopPropagation(); e.stopPropagation();
cb.trigger("click"); cb.trigger("click");
that._topList.find(".focus").removeClass("focus")
label.addClass('focus')
}) })
} }
item.treeList.select = function(v) { item.treeList.select = function(v) {
@@ -676,6 +712,7 @@
} }
} }
selectWrapper.appendTo(label) selectWrapper.appendTo(label)
item.treeList.radio = cb;
} else { } else {
label.on("click", function(e) { label.on("click", function(e) {
if (!that.options.multi) { if (!that.options.multi) {
@@ -683,10 +720,14 @@
} }
label.addClass("selected"); label.addClass("selected");
that._selected.add(item); that._selected.add(item);
that._topList.find(".focus").removeClass("focus")
label.addClass('focus')
that._trigger("select",e,item) that._trigger("select",e,item)
}) })
label.on("dblclick", function(e) { label.on("dblclick", function(e) {
that._topList.find(".focus").removeClass("focus")
label.addClass('focus')
if (!item.children) { if (!item.children) {
that._trigger("confirm",e,item); that._trigger("confirm",e,item);
} }
@@ -834,6 +875,9 @@
if (item.treeList.label) { if (item.treeList.label) {
item.treeList.label.addClass("selected"); item.treeList.label.addClass("selected");
} }
that._topList.find(".focus").removeClass("focus");
if (triggerEvent !== false) { if (triggerEvent !== false) {
this._trigger("select",null,item) this._trigger("select",null,item)
} }
@@ -841,6 +885,9 @@
clearSelection: function() { clearSelection: function() {
this._selected.forEach(function(item) { this._selected.forEach(function(item) {
item.selected = false; item.selected = false;
if (item.treeList.checkbox) {
item.treeList.checkbox.prop('checked',false)
}
if (item.treeList.label) { if (item.treeList.label) {
item.treeList.label.removeClass("selected") item.treeList.label.removeClass("selected")
} }

View File

@@ -345,6 +345,47 @@
} }
} }
}; };
// For a type with options, check value is a valid selection
// If !opt.multiple, returns the valid option object
// if opt.multiple, returns an array of valid option objects
// If not valid, returns null;
function isOptionValueValid(opt, currentVal) {
if (!opt.multiple) {
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
if (typeof op === "string" && op === currentVal) {
return {value:currentVal}
} else if (op.value === currentVal) {
return op;
}
}
} else {
// Check to see if value is a valid csv of
// options.
var currentValues = {};
var selected = [];
currentVal.split(",").forEach(function(v) {
if (v) {
currentValues[v] = true;
}
});
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
var val = typeof op === "string" ? op : op.value;
if (currentValues.hasOwnProperty(val)) {
delete currentValues[val];
selected.push(typeof op === "string" ? {value:op} : op.value)
}
}
if (!$.isEmptyObject(currentValues)) {
return null;
}
return selected
}
}
var nlsd = false; var nlsd = false;
$.widget( "nodered.typedInput", { $.widget( "nodered.typedInput", {
@@ -378,7 +419,8 @@
} }
nlsd = true; nlsd = true;
var that = this; var that = this;
this.identifier = this.element.attr('id') || "TypedInput-"+Math.floor(Math.random()*100);
if (this.options.debug) { console.log(this.identifier,"Create",{defaultType:this.options.default, value:this.element.val()}) }
this.disarmClick = false; this.disarmClick = false;
this.input = $('<input class="red-ui-typedInput-input" type="text"></input>'); this.input = $('<input class="red-ui-typedInput-input" type="text"></input>');
this.input.insertAfter(this.element); this.input.insertAfter(this.element);
@@ -408,6 +450,8 @@
}); });
this.defaultInputType = this.input.attr('type'); this.defaultInputType = this.input.attr('type');
// Used to remember selections per-type to restore them when switching between types
this.oldValues = {};
this.uiSelect.addClass("red-ui-typedInput-container"); this.uiSelect.addClass("red-ui-typedInput-container");
@@ -490,9 +534,9 @@
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect); this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger); this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
RED.popover.tooltip(this.optionSelectLabel,function() { // RED.popover.tooltip(this.optionSelectLabel,function() {
return that.optionValue; // return that.optionValue;
}); // });
this.optionSelectTrigger.on("click", function(event) { this.optionSelectTrigger.on("click", function(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@@ -512,10 +556,8 @@
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect); this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton); this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
// Used to remember selections per-type to restore them when switching between types this.type(this.typeField.val() || this.options.default||this.typeList[0].value);
this.oldValues = {}; this.typeChanged = !!this.options.default;
this.type(this.options.default||this.typeList[0].value);
}catch(err) { }catch(err) {
console.log(err.stack); console.log(err.stack);
} }
@@ -763,6 +805,7 @@
var that = this; var that = this;
var currentType = this.type(); var currentType = this.type();
this.typeMap = {}; this.typeMap = {};
var firstCall = (this.typeList === undefined);
this.typeList = types.map(function(opt) { this.typeList = types.map(function(opt) {
var result; var result;
if (typeof opt === 'string') { if (typeof opt === 'string') {
@@ -787,10 +830,14 @@
} }
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) }); this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) { if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value); if (!firstCall) {
this.type(this.typeList[0].value);
}
} else { } else {
this.propertyType = null; this.propertyType = null;
this.type(currentType); if (!firstCall) {
this.type(currentType);
}
} }
if (this.typeList.length === 1 && !this.typeList[0].icon && (!this.typeList[0].label || this.typeList[0].showLabel === false)) { if (this.typeList.length === 1 && !this.typeList[0].icon && (!this.typeList[0].label || this.typeList[0].showLabel === false)) {
this.selectTrigger.hide() this.selectTrigger.hide()
@@ -806,7 +853,10 @@
}, },
value: function(value) { value: function(value) {
var that = this; var that = this;
var opt = this.typeMap[this.propertyType]; // If the default type has been set to an invalid type, then on first
// creation, the current propertyType will not exist. Default to an
// empty object on the assumption the corrent type will be set shortly
var opt = this.typeMap[this.propertyType] || {};
if (!arguments.length) { if (!arguments.length) {
var v = this.input.val(); var v = this.input.val();
if (opt.export) { if (opt.export) {
@@ -814,27 +864,38 @@
} }
return v; return v;
} else { } else {
if (this.options.debug) { console.log(this.identifier,"----- SET VALUE ------",value) }
var selectedOption = []; var selectedOption = [];
var valueToCheck = value;
if (opt.options) { if (opt.options) {
var checkValues = [value]; if (opt.hasValue && opt.parse) {
var parts = opt.parse(value);
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
value = parts.value;
valueToCheck = parts.option || parts.value;
}
var checkValues = [valueToCheck];
if (opt.multiple) { if (opt.multiple) {
selectedOption = []; selectedOption = [];
checkValues = value.split(","); checkValues = valueToCheck.split(",");
} }
checkValues.forEach(function(value) { checkValues.forEach(function(valueToCheck) {
for (var i=0;i<opt.options.length;i++) { for (var i=0;i<opt.options.length;i++) {
var op = opt.options[i]; var op = opt.options[i];
if (typeof op === "string") { if (typeof op === "string") {
if (op === value || op === ""+value) { if (op === valueToCheck || op === ""+valueToCheck) {
selectedOption.push(that.activeOptions[op]); selectedOption.push(that.activeOptions[op]);
break; break;
} }
} else if (op.value === value) { } else if (op.value === valueToCheck) {
selectedOption.push(op); selectedOption.push(op);
break; break;
} }
} }
}) })
if (this.options.debug) { console.log(this.identifier,"set value to",value) }
this.input.val(value); this.input.val(value);
if (!opt.multiple) { if (!opt.multiple) {
if (selectedOption.length === 0) { if (selectedOption.length === 0) {
@@ -859,23 +920,56 @@
return this.propertyType; return this.propertyType;
} else { } else {
var that = this; var that = this;
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
var previousValue = null;
var opt = this.typeMap[type]; var opt = this.typeMap[type];
if (opt && this.propertyType !== type) { if (opt && this.propertyType !== type) {
// If previousType is !null, then this is a change of the type, rather than the initialisation // If previousType is !null, then this is a change of the type, rather than the initialisation
var previousType = this.typeMap[this.propertyType]; var previousType = this.typeMap[this.propertyType];
var typeChanged = !!previousType; previousValue = this.input.val();
if (typeChanged) { if (previousType && this.typeChanged) {
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
if (previousType.options && opt.hasValue !== true) { if (previousType.options && opt.hasValue !== true) {
this.oldValues[previousType.value] = this.input.val(); this.oldValues[previousType.value] = previousValue;
} else if (previousType.hasValue === false) { } else if (previousType.hasValue === false) {
this.oldValues[previousType.value] = this.input.val(); this.oldValues[previousType.value] = previousValue;
} else { } else {
this.oldValues["_"] = this.input.val(); this.oldValues["_"] = previousValue;
} }
if ((opt.options && opt.hasValue !== true) || opt.hasValue === false) { if ((opt.options && opt.hasValue !== true) || opt.hasValue === false) {
this.input.val(this.oldValues.hasOwnProperty(opt.value)?this.oldValues[opt.value]:(opt.default||[]).join(",")) if (this.oldValues.hasOwnProperty(opt.value)) {
if (this.options.debug) { console.log(this.identifier,"restored previous (1)",this.oldValues[opt.value]) }
this.input.val(this.oldValues[opt.value]);
} else if (opt.options) {
// No old value for the option type.
// It is possible code has called 'value' then 'type'
// to set the selected option. This is what the Inject/Switch/Change
// nodes did before 2.1.
// So we need to be careful to not reset the value if it is a valid option.
var validOptions = isOptionValueValid(opt,previousValue);
if (this.options.debug) { console.log(this.identifier,{previousValue,opt,validOptions}) }
if ((previousValue || previousValue === '') && validOptions) {
if (this.options.debug) { console.log(this.identifier,"restored previous (2)") }
this.input.val(previousValue);
} else {
if (typeof opt.default === "string") {
if (this.options.debug) { console.log(this.identifier,"restored previous (3)",opt.default) }
this.input.val(opt.default);
} else if (Array.isArray(opt.default)) {
if (this.options.debug) { console.log(this.identifier,"restored previous (4)",opt.default.join(",")) }
this.input.val(opt.default.join(","))
} else {
if (this.options.debug) { console.log(this.identifier,"restored previous (5)") }
this.input.val("");
}
}
} else {
if (this.options.debug) { console.log(this.identifier,"restored default/blank",opt.default||"") }
this.input.val(opt.default||"")
}
} else { } else {
if (this.options.debug) { console.log(this.identifier,"restored old/default/blank") }
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||"")) this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
} }
if (previousType.autoComplete) { if (previousType.autoComplete) {
@@ -883,6 +977,7 @@
} }
} }
this.propertyType = type; this.propertyType = type;
this.typeChanged = true;
if (this.typeField) { if (this.typeField) {
this.typeField.val(type); this.typeField.val(type);
} }
@@ -951,22 +1046,12 @@
var op; var op;
if (!opt.hasValue) { if (!opt.hasValue) {
var validValue = false; // Check the value is valid for the available options
var currentVal = this.input.val(); var validValues = isOptionValueValid(opt,this.input.val());
if (!opt.multiple) { if (!opt.multiple) {
for (var i=0;i<opt.options.length;i++) { if (validValues) {
op = opt.options[i]; that._updateOptionSelectLabel(validValues)
if (typeof op === "string" && op === currentVal) { } else {
that._updateOptionSelectLabel({value:currentVal});
validValue = true;
break;
} else if (op.value === currentVal) {
that._updateOptionSelectLabel(op);
validValue = true;
break;
}
}
if (!validValue) {
op = opt.options[0]; op = opt.options[0];
if (typeof op === "string") { if (typeof op === "string") {
this.value(op); this.value(op);
@@ -977,31 +1062,19 @@
} }
} }
} else { } else {
// Check to see if value is a valid csv of if (!validValues) {
// options. validValues = (opt.default || []).map(function(v) {
var currentValues = {}; return typeof v === "string"?v:v.value
var selected = []; });
currentVal.split(",").forEach(function(v) { this.value(validValues.join(","));
if (v) {
selected.push(v);
currentValues[v] = true;
}
});
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
delete currentValues[op.value||op];
} }
if (!$.isEmptyObject(currentValues)) { that._updateOptionSelectLabel(validValues);
selected = opt.default || [];
// Invalid, set to default/empty
this.value(selected.join(","));
}
that._updateOptionSelectLabel(selected);
} }
} else { } else {
var selectedOption = this.optionValue||opt.options[0]; var selectedOption = this.optionValue||opt.options[0];
if (opt.parse) { if (opt.parse) {
var parts = opt.parse(this.input.val(),selectedOption); var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
var parts = opt.parse(this.input.val(),selectedOptionObj);
if (parts.option) { if (parts.option) {
selectedOption = parts.option; selectedOption = parts.option;
if (!this.activeOptions.hasOwnProperty(selectedOption)) { if (!this.activeOptions.hasOwnProperty(selectedOption)) {
@@ -1025,6 +1098,7 @@
this._updateOptionSelectLabel(this.activeOptions[selectedOption]); this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
} }
} else if (selectedOption) { } else if (selectedOption) {
if (this.options.debug) { console.log(this.identifier,"HERE",{optionValue:selectedOption.value}) }
this.optionValue = selectedOption.value; this.optionValue = selectedOption.value;
this._updateOptionSelectLabel(selectedOption); this._updateOptionSelectLabel(selectedOption);
} else { } else {

View File

@@ -554,6 +554,8 @@ RED.diff = (function() {
color: "#DDAA99", color: "#DDAA99",
defaults:{name:{value:""}} defaults:{name:{value:""}}
} }
} else if (node.type === "group") {
def = RED.group.def;
} else { } else {
def = {}; def = {};
} }
@@ -763,16 +765,15 @@ RED.diff = (function() {
} }
} }
if (node.hasOwnProperty('x')) { if (node.hasOwnProperty('x')) {
if (localNode) { if (localNode) {
if (localNode.x !== node.x || localNode.y !== node.y) { if (localNode.x !== node.x || localNode.y !== node.y || localNode.w !== node.w || localNode.h !== node.h ) {
localChanged = true; localChanged = true;
localChanges++; localChanges++;
} }
} }
if (remoteNode) { if (remoteNode) {
if (remoteNode.x !== node.x || remoteNode.y !== node.y) { if (remoteNode.x !== node.x || remoteNode.y !== node.y|| remoteNode.w !== node.w || remoteNode.h !== node.h) {
remoteChanged = true; remoteChanged = true;
remoteChanges++; remoteChanges++;
} }
@@ -790,7 +791,12 @@ RED.diff = (function() {
localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged")); localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell); $('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell); element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
propertyElements['local.position'] = RED.utils.createObjectElement({x:localNode.x,y:localNode.y}, var localPosition = {x:localNode.x,y:localNode.y};
if (localNode.hasOwnProperty('w')) {
localPosition.w = localNode.w;
localPosition.h = localNode.h;
}
propertyElements['local.position'] = RED.utils.createObjectElement(localPosition,
{ {
path: "position", path: "position",
exposeApi: true, exposeApi: true,
@@ -811,7 +817,12 @@ RED.diff = (function() {
if (remoteNode) { if (remoteNode) {
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell); $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell); element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
propertyElements['remote.position'] = RED.utils.createObjectElement({x:remoteNode.x,y:remoteNode.y}, var remotePosition = {x:remoteNode.x,y:remoteNode.y};
if (remoteNode.hasOwnProperty('w')) {
remotePosition.w = remoteNode.w;
remotePosition.h = remoteNode.h;
}
propertyElements['remote.position'] = RED.utils.createObjectElement(remotePosition,
{ {
path: "position", path: "position",
exposeApi: true, exposeApi: true,
@@ -883,11 +894,11 @@ RED.diff = (function() {
} }
} }
} }
var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))}); var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='w'&&p!=='h'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
if (def.defaults) { if (def.defaults) {
properties = properties.concat(Object.keys(def.defaults)); properties = properties.concat(Object.keys(def.defaults));
} }
if (node.type !== 'tab') { if (node.type !== 'tab' && node.type !== "group") {
properties = properties.concat(['inputLabels','outputLabels']); properties = properties.concat(['inputLabels','outputLabels']);
} }
if ( ((localNode && localNode.hasOwnProperty('icon')) || (remoteNode && remoteNode.hasOwnProperty('icon'))) && if ( ((localNode && localNode.hasOwnProperty('icon')) || (remoteNode && remoteNode.hasOwnProperty('icon'))) &&

View File

@@ -744,7 +744,16 @@ RED.editor = (function() {
delete cn.__label__; delete cn.__label__;
}); });
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>'); var label = type;
if (typeof node_def.paletteLabel !== "undefined") {
try {
label = RED.utils.sanitize((typeof node_def.paletteLabel === "function" ? node_def.paletteLabel.call(node_def) : node_def.paletteLabel)||type);
} catch(err) {
console.log("Definition error: "+type+".paletteLabel",err);
}
}
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
window.setTimeout(function() { select.trigger("change");},50); window.setTimeout(function() { select.trigger("change");},50);
} }
} }
@@ -1755,8 +1764,12 @@ RED.editor = (function() {
editState.changes.disabled = workspace.disabled; editState.changes.disabled = workspace.disabled;
editState.changed = true; editState.changed = true;
workspace.disabled = disabled; workspace.disabled = disabled;
}
$("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
if (workspace.id === RED.workspaces.active()) {
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
}
}
if (editState.changed) { if (editState.changed) {
var historyEvent = { var historyEvent = {

View File

@@ -56,8 +56,12 @@
}); });
} }
if (!isSameObj(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
node.env = new_env;
editState.changes.env = node.env; editState.changes.env = node.env;
if (new_env.length === 0) {
delete node.env;
} else {
node.env = new_env;
}
editState.changed = true; editState.changed = true;
} }
} }

View File

@@ -243,7 +243,13 @@ RED.keyboard = (function() {
function resolveKeyEvent(evt) { function resolveKeyEvent(evt) {
var slot = partialState||handlers; var slot = partialState||handlers;
if (evt.ctrlKey || evt.metaKey) { // We cheat with MacOS CMD key and consider it the same as Ctrl.
// That means we don't have to have separate keymaps for different OS.
// It mostly works.
// One exception is shortcuts that include both Cmd and Ctrl. We don't
// support them - but we need to make sure we don't block browser-specific
// shortcuts (such as Cmd-Ctrl-F for fullscreen).
if ((evt.ctrlKey || evt.metaKey) && (evt.ctrlKey !== evt.metaKey)) {
slot = slot.ctrl; slot = slot.ctrl;
} }
if (slot && evt.shiftKey) { if (slot && evt.shiftKey) {

View File

@@ -2387,6 +2387,7 @@ RED.projects = (function() {
return { return {
init: init, init: init,
showStartup: function() { showStartup: function() {
console.warn("showStartup")
if (!RED.user.hasPermission("projects.write")) { if (!RED.user.hasPermission("projects.write")) {
RED.notify(RED._("user.errors.notAuthorized"),"error"); RED.notify(RED._("user.errors.notAuthorized"),"error");
return; return;

View File

@@ -105,6 +105,7 @@ RED.search = (function() {
val = extractFlag(val,"unused",flags); val = extractFlag(val,"unused",flags);
val = extractFlag(val,"config",flags); val = extractFlag(val,"config",flags);
val = extractFlag(val,"subflow",flags); val = extractFlag(val,"subflow",flags);
val = extractFlag(val,"hidden",flags);
// uses:<node-id> // uses:<node-id>
val = extractValue(val,"uses",flags); val = extractValue(val,"uses",flags);
@@ -150,7 +151,15 @@ RED.search = (function() {
continue; continue;
} }
} }
if (flags.hasOwnProperty("hidden")) {
// Only tabs can be hidden
if (node.node.type !== 'tab') {
continue
}
if (!RED.workspaces.isHidden(node.node.id)) {
continue
}
}
if (flags.hasOwnProperty("unused")) { if (flags.hasOwnProperty("unused")) {
var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) || var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
(isConfigNode && node.node.users.length === 0) (isConfigNode && node.node.users.length === 0)

View File

@@ -379,7 +379,7 @@ RED.sidebar.help = (function() {
var currentVersionParts = RED.settings.version.split("."); var currentVersionParts = RED.settings.version.split(".");
var tourVersionParts = tour.version.split("."); var tourVersionParts = tour.version.split(".");
if (tourVersionParts[0] === currentVersionParts[0] && tourVersionParts[1] === currentVersionParts[1]) { if (tourVersionParts[0] === currentVersionParts[0] && tourVersionParts[1] === currentVersionParts[1]) {
tourHeader = '<div><button type="button" onclick="RED.actions.invoke(\'core:show-welcome-tour\')" class="red-ui-button">Take a tour</button></div>' tourHeader = '<div><button type="button" onclick="RED.actions.invoke(\'core:show-welcome-tour\')" class="red-ui-button">' + RED._("tourGuide.takeATour") + '</button></div>';
} }
} }
var aboutHeader = '<div style="text-align:center;">'+tourHeader+'</div>' var aboutHeader = '<div style="text-align:center;">'+tourHeader+'</div>'
@@ -393,10 +393,12 @@ RED.sidebar.help = (function() {
treeList.treeList("select","changelog"); treeList.treeList("select","changelog");
show(); show();
} }
function showWelcomeTour(lastSeenVersion) { function showWelcomeTour(lastSeenVersion, done) {
done = done || function() {};
RED.tourGuide.load("./tours/welcome.js", function(err, tour) { RED.tourGuide.load("./tours/welcome.js", function(err, tour) {
if (err) { if (err) {
console.warn("Failed to load welcome tour",err); console.warn("Failed to load welcome tour",err);
done()
return; return;
} }
var currentVersionParts = RED.settings.version.split("."); var currentVersionParts = RED.settings.version.split(".");
@@ -405,6 +407,7 @@ RED.sidebar.help = (function() {
// Only display the tour if its MAJ.MIN versions the current version // Only display the tour if its MAJ.MIN versions the current version
// This means if we update MAJ/MIN without updating the tour, the old tour won't get shown // This means if we update MAJ/MIN without updating the tour, the old tour won't get shown
if (tourVersionParts[0] !== currentVersionParts[0] || tourVersionParts[1] !== currentVersionParts[1]) { if (tourVersionParts[0] !== currentVersionParts[0] || tourVersionParts[1] !== currentVersionParts[1]) {
done()
return; return;
} }
@@ -412,26 +415,31 @@ RED.sidebar.help = (function() {
// Previously displayed a welcome tour. // Previously displayed a welcome tour.
if (lastSeenVersion === RED.settings.version) { if (lastSeenVersion === RED.settings.version) {
// Exact match - don't show the tour // Exact match - don't show the tour
done()
return; return;
} }
var lastSeenParts = lastSeenVersion.split("."); var lastSeenParts = lastSeenVersion.split(".");
if (currentVersionParts[0] < lastSeenParts[0] || (currentVersionParts[0] === lastSeenParts[0] && currentVersionParts[1] < lastSeenParts[1])) { if (currentVersionParts[0] < lastSeenParts[0] || (currentVersionParts[0] === lastSeenParts[0] && currentVersionParts[1] < lastSeenParts[1])) {
// Running an *older* version than last displayed tour. // Running an *older* version than last displayed tour.
done()
return; return;
} }
if (currentVersionParts[0] === lastSeenParts[0] && currentVersionParts[1] === lastSeenParts[1]) { if (currentVersionParts[0] === lastSeenParts[0] && currentVersionParts[1] === lastSeenParts[1]) {
if (lastSeenParts.length === 3 && currentVersionParts.length === 3) { if (lastSeenParts.length === 3 && currentVersionParts.length === 3) {
// Matching non-beta MAJ.MIN - don't repeat tour // Matching non-beta MAJ.MIN - don't repeat tour
done()
return; return;
} }
if (currentVersionParts.length === 4 && (lastSeenParts.length === 3 || currentVersionParts[3] < lastSeenParts[3])) { if (currentVersionParts.length === 4 && (lastSeenParts.length === 3 || currentVersionParts[3] < lastSeenParts[3])) {
// Running an *older* beta than last displayed tour. // Running an *older* beta than last displayed tour.
done()
return return
} }
} }
} }
RED.tourGuide.run("./tours/welcome.js", function(err) { RED.tourGuide.run("./tours/welcome.js", function(err) {
RED.settings.set("editor.tours.welcome", RED.settings.version) RED.settings.set("editor.tours.welcome", RED.settings.version)
done()
}) })
}) })

View File

@@ -175,6 +175,7 @@ RED.sidebar.info.outliner = (function() {
n.d = true; n.d = true;
} }
n.dirty = true; n.dirty = true;
n.dirtyStatus = true;
n.changed = true; n.changed = true;
RED.events.emit("nodes:change",n); RED.events.emit("nodes:change",n);
groupHistoryEvent.events.push(historyEvent); groupHistoryEvent.events.push(historyEvent);
@@ -203,6 +204,7 @@ RED.sidebar.info.outliner = (function() {
n.d = true; n.d = true;
} }
n.dirty = true; n.dirty = true;
n.dirtyStatus = true;
n.changed = true; n.changed = true;
RED.events.emit("nodes:change",n); RED.events.emit("nodes:change",n);
RED.history.push(historyEvent); RED.history.push(historyEvent);
@@ -272,6 +274,7 @@ RED.sidebar.info.outliner = (function() {
{label:RED._("sidebar.info.search.invalidNodes"), value: "is:invalid"}, {label:RED._("sidebar.info.search.invalidNodes"), value: "is:invalid"},
{label:RED._("sidebar.info.search.uknownNodes"), value: "type:unknown"}, {label:RED._("sidebar.info.search.uknownNodes"), value: "type:unknown"},
{label:RED._("sidebar.info.search.unusedSubflows"), value:"is:subflow is:unused"}, {label:RED._("sidebar.info.search.unusedSubflows"), value:"is:subflow is:unused"},
{label:RED._("sidebar.info.search.hiddenFlows"), value:"is:hidden"},
] ]
}); });
@@ -287,11 +290,11 @@ RED.sidebar.info.outliner = (function() {
var node = RED.nodes.node(item.id) || RED.nodes.group(item.id); var node = RED.nodes.node(item.id) || RED.nodes.group(item.id);
if (node) { if (node) {
if (node.type === 'group' || node._def.category !== "config") { if (node.type === 'group' || node._def.category !== "config") {
RED.view.select({nodes:[node]}) // RED.view.select({nodes:[node]})
} else if (node._def.category === "config") { } else if (node._def.category === "config") {
RED.sidebar.info.refresh(node); RED.sidebar.info.refresh(node);
} else { } else {
RED.view.select({nodes:[]}) // RED.view.select({nodes:[]})
} }
} }
}) })
@@ -563,7 +566,7 @@ RED.sidebar.info.outliner = (function() {
} }
} }
function getGutter(n) { function getGutter(n) {
var span = $("<span>",{class:"red-ui-info-outline-gutter"}); var span = $("<span>",{class:"red-ui-info-outline-gutter red-ui-treeList-gutter-float"});
var revealButton = $('<button type="button" class="red-ui-info-outline-item-control-reveal red-ui-button red-ui-button-small"><i class="fa fa-search"></i></button>').appendTo(span).on("click",function(evt) { var revealButton = $('<button type="button" class="red-ui-info-outline-item-control-reveal red-ui-button red-ui-button-small"><i class="fa fa-search"></i></button>').appendTo(span).on("click",function(evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();

View File

@@ -348,7 +348,12 @@ RED.tourGuide = (function() {
maxWidth: maxWidth+"px", maxWidth: maxWidth+"px",
direction: direction, direction: direction,
}) })
setTimeout(function() {
var pos = popover.element.position()
if (pos.left < 0) {
popover.element.css({left: 0});
}
},100);
if (nextButton) { if (nextButton) {
setTimeout(function() { setTimeout(function() {
nextButton.focus(); nextButton.focus();
@@ -359,6 +364,8 @@ RED.tourGuide = (function() {
if (step.fallback) { if (step.fallback) {
focus.one("mouseenter", function(evt) { focus.one("mouseenter", function(evt) {
setTimeout(function() { setTimeout(function() {
var pos = targetElement[0].getBoundingClientRect();
var dimension = Math.max(50, Math.max(pos.width,pos.height)*1.5);
focus.css({ focus.css({
width: (4*dimension)+"px", width: (4*dimension)+"px",
height: (4*dimension)+"px" height: (4*dimension)+"px"

View File

@@ -27,7 +27,7 @@ RED.utils = (function() {
level: 'block', // Is this a block-level or inline-level tokenizer? level: 'block', // Is this a block-level or inline-level tokenizer?
start(src) { start(src) {
if (!src) { return null; } if (!src) { return null; }
let m = src.match(/:[^:\n]/); let m = src.match(/:[^:\n]/g);
return m && m.index; // Hint to Marked.js to stop and check for a match return m && m.index; // Hint to Marked.js to stop and check for a match
}, },
tokenizer(src, tokens) { tokenizer(src, tokens) {
@@ -39,12 +39,12 @@ RED.utils = (function() {
type: 'descriptionList', // Should match "name" above type: 'descriptionList', // Should match "name" above
raw: match[0], // Text to consume from the source raw: match[0], // Text to consume from the source
text: match[0].trim(), // Additional custom properties text: match[0].trim(), // Additional custom properties
tokens: this.inlineTokens(match[0].trim()) // inlineTokens to process **bold**, *italics*, etc. tokens: this.lexer.inlineTokens(match[0].trim()) // inlineTokens to process **bold**, *italics*, etc.
}; };
} }
}, },
renderer(token) { renderer(token) {
return `<dl class="message-properties">${this.parseInline(token.tokens)}\n</dl>`; // parseInline to turn child tokens into HTML return `<dl class="message-properties">${this.parser.parseInline(token.tokens)}\n</dl>`; // parseInline to turn child tokens into HTML
} }
}; };
@@ -53,7 +53,7 @@ RED.utils = (function() {
level: 'inline', // Is this a block-level or inline-level tokenizer? level: 'inline', // Is this a block-level or inline-level tokenizer?
start(src) { start(src) {
if (!src) { return null; } if (!src) { return null; }
let m = src.match(/:/); let m = src.match(/:/g);
return m && m.index; // Hint to Marked.js to stop and check for a match return m && m.index; // Hint to Marked.js to stop and check for a match
}, },
tokenizer(src, tokens) { tokenizer(src, tokens) {
@@ -64,14 +64,14 @@ RED.utils = (function() {
return { // Token to generate return { // Token to generate
type: 'description', // Should match "name" above type: 'description', // Should match "name" above
raw: match[0], // Text to consume from the source raw: match[0], // Text to consume from the source
dt: this.inlineTokens(match[1].trim()), // Additional custom properties dt: this.lexer.inlineTokens(match[1].trim()), // Additional custom properties
types: this.inlineTokens(match[2].trim()), types: this.lexer.inlineTokens(match[2].trim()),
dd: this.inlineTokens(match[3].trim()), dd: this.lexer.inlineTokens(match[3].trim()),
}; };
} }
}, },
renderer(token) { renderer(token) {
return `\n<dt>${this.parseInline(token.dt)}<span class="property-type">${this.parseInline(token.types)}</span></dt><dd>${this.parseInline(token.dd)}</dd>`; return `\n<dt>${this.parser.parseInline(token.dt)}<span class="property-type">${this.parser.parseInline(token.types)}</span></dt><dd>${this.parser.parseInline(token.dd)}</dd>`;
}, },
childTokens: ['dt', 'dd'], // Any child tokens to be visited by walkTokens childTokens: ['dt', 'dd'], // Any child tokens to be visited by walkTokens
walkTokens(token) { // Post-processing on the completed token tree walkTokens(token) { // Post-processing on the completed token tree
@@ -142,6 +142,8 @@ RED.utils = (function() {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('function'); result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('function');
} else if (value.hasOwnProperty('type') && (value.type === 'number' || value.type === 'bigint')) { } else if (value.hasOwnProperty('type') && (value.type === 'number' || value.type === 'bigint')) {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-number"></span>').text(value.data); result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-number"></span>').text(value.data);
} else if (value.hasOwnProperty('type') && value.type === 'regexp') {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-string"></span>').text(value.data);
} else { } else {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta">object</span>'); result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta">object</span>');
} }
@@ -440,6 +442,8 @@ RED.utils = (function() {
$('<span class="red-ui-debug-msg-type-null">undefined</span>').appendTo(entryObj); $('<span class="red-ui-debug-msg-type-null">undefined</span>').appendTo(entryObj);
} else if (obj.__enc__ && (obj.type === 'number' || obj.type === 'bigint')) { } else if (obj.__enc__ && (obj.type === 'number' || obj.type === 'bigint')) {
e = $('<span class="red-ui-debug-msg-type-number red-ui-debug-msg-object-header"></span>').text(obj.data).appendTo(entryObj); e = $('<span class="red-ui-debug-msg-type-number red-ui-debug-msg-object-header"></span>').text(obj.data).appendTo(entryObj);
} else if (typeHint === "regexp" || (obj.__enc__ && obj.type === 'regexp')) {
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').text((typeof obj === "string")?obj:obj.data).appendTo(entryObj);
} else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) { } else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) {
e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("function").appendTo(entryObj); e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("function").appendTo(entryObj);
} else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) { } else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
@@ -563,7 +567,8 @@ RED.utils = (function() {
expandPaths: expandPaths, expandPaths: expandPaths,
ontoggle: ontoggle, ontoggle: ontoggle,
exposeApi: exposeApi, exposeApi: exposeApi,
tools: tools // tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
} }
).appendTo(row); ).appendTo(row);
} }
@@ -592,7 +597,8 @@ RED.utils = (function() {
expandPaths: expandPaths, expandPaths: expandPaths,
ontoggle: ontoggle, ontoggle: ontoggle,
exposeApi: exposeApi, exposeApi: exposeApi,
tools: tools // tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
} }
).appendTo(row); ).appendTo(row);
} }
@@ -647,7 +653,8 @@ RED.utils = (function() {
expandPaths: expandPaths, expandPaths: expandPaths,
ontoggle: ontoggle, ontoggle: ontoggle,
exposeApi: exposeApi, exposeApi: exposeApi,
tools: tools // tools: tools // Do not pass tools down as we
// keep them attached to the top-level header
} }
).appendTo(row); ).appendTo(row);
} }

View File

@@ -3675,7 +3675,11 @@ RED.view = (function() {
nodeEl = document.getElementById(d.id); nodeEl = document.getElementById(d.id);
} }
if (nodeEl) { if (nodeEl) {
if (!showStatus || !d.status) { // Do not show node status if:
// - global flag set
// - node has no status
// - node is disabled
if (!showStatus || !d.status || d.d === true) {
nodeEl.__statusGroup__.style.display = "none"; nodeEl.__statusGroup__.style.display = "none";
} else { } else {
nodeEl.__statusGroup__.style.display = "inline"; nodeEl.__statusGroup__.style.display = "inline";
@@ -4006,10 +4010,10 @@ RED.view = (function() {
var labelParts; var labelParts;
if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label || this.__outputs__.length !== d.outputs) { if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label || this.__outputs__.length !== d.outputs) {
labelParts = getLabelParts(label, "red-ui-flow-node-label"); labelParts = getLabelParts(label, "red-ui-flow-node-label");
this.__label__ = label; if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
if (labelParts.lines.length !== this.__labelLineCount__) {
d.resize = true; d.resize = true;
} }
this.__label__ = label;
this.__labelLineCount__ = labelParts.lines.length; this.__labelLineCount__ = labelParts.lines.length;
if (hideLabel) { if (hideLabel) {
@@ -4421,6 +4425,9 @@ RED.view = (function() {
n.selected = true; n.selected = true;
n.dirty = true; n.dirty = true;
movingSet.add(n); movingSet.add(n);
if (targets.length === 1) {
RED.view.reveal(n.id);
}
}); });
updateSelection(); updateSelection();
redraw(); redraw();
@@ -4957,7 +4964,7 @@ RED.view = (function() {
counts.push(RED._("clipboard.group",{count:newGroupCount})); counts.push(RED._("clipboard.group",{count:newGroupCount}));
} }
if (newConfigNodeCount > 0) { if (newConfigNodeCount > 0) {
counts.push(RED._("clipboard.configNode",{count:newNodeCount})); counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount}));
} }
if (new_subflows.length > 0) { if (new_subflows.length > 0) {
counts.push(RED._("clipboard.subflow",{count:new_subflows.length})); counts.push(RED._("clipboard.subflow",{count:new_subflows.length}));
@@ -5029,6 +5036,7 @@ RED.view = (function() {
delete node.d; delete node.d;
} }
node.dirty = true; node.dirty = true;
node.dirtyStatus = true;
node.changed = true; node.changed = true;
RED.events.emit("nodes:change",node); RED.events.emit("nodes:change",node);
} }

View File

@@ -218,55 +218,64 @@ RED.workspaces = (function() {
scrollable: true, scrollable: true,
addButton: "core:add-flow", addButton: "core:add-flow",
addButtonCaption: RED._("workspace.addFlow"), addButtonCaption: RED._("workspace.addFlow"),
menu: [ menu: function() {
{ var menuItems = [
id:"red-ui-tabs-menu-option-search-flows", {
label: RED._("workspace.listFlows"), id:"red-ui-tabs-menu-option-search-flows",
onselect: "core:list-flows" label: RED._("workspace.listFlows"),
}, onselect: "core:list-flows"
{ },
id:"red-ui-tabs-menu-option-search-subflows", {
label: RED._("workspace.listSubflows"), id:"red-ui-tabs-menu-option-search-subflows",
onselect: "core:list-subflows" label: RED._("workspace.listSubflows"),
}, onselect: "core:list-subflows"
null, },
{ null,
id:"red-ui-tabs-menu-option-add-flow", {
label: RED._("workspace.addFlow"), id:"red-ui-tabs-menu-option-add-flow",
onselect: "core:add-flow" label: RED._("workspace.addFlow"),
}, onselect: "core:add-flow"
{ },
id:"red-ui-tabs-menu-option-add-flow-right", {
label: RED._("workspace.addFlowToRight"), id:"red-ui-tabs-menu-option-add-flow-right",
onselect: "core:add-flow-to-right" label: RED._("workspace.addFlowToRight"),
}, onselect: "core:add-flow-to-right"
null, },
{ null,
id:"red-ui-tabs-menu-option-add-hide-flows", {
label: RED._("workspace.hideFlow"), id:"red-ui-tabs-menu-option-add-hide-flows",
onselect: "core:hide-flow" label: RED._("workspace.hideFlow"),
}, onselect: "core:hide-flow"
{ },
id:"red-ui-tabs-menu-option-add-hide-other-flows", {
label: RED._("workspace.hideOtherFlows"), id:"red-ui-tabs-menu-option-add-hide-other-flows",
onselect: "core:hide-other-flows" label: RED._("workspace.hideOtherFlows"),
}, onselect: "core:hide-other-flows"
{ },
id:"red-ui-tabs-menu-option-add-show-all-flows", {
label: RED._("workspace.showAllFlows"), id:"red-ui-tabs-menu-option-add-show-all-flows",
onselect: "core:show-all-flows" label: RED._("workspace.showAllFlows"),
}, onselect: "core:show-all-flows"
{ },
id:"red-ui-tabs-menu-option-add-hide-all-flows", {
label: RED._("workspace.hideAllFlows"), id:"red-ui-tabs-menu-option-add-hide-all-flows",
onselect: "core:hide-all-flows" label: RED._("workspace.hideAllFlows"),
}, onselect: "core:hide-all-flows"
{ },
id:"red-ui-tabs-menu-option-add-show-last-flow", {
label: RED._("workspace.showLastHiddenFlow"), id:"red-ui-tabs-menu-option-add-show-last-flow",
onselect: "core:show-last-hidden-flow" label: RED._("workspace.showLastHiddenFlow"),
onselect: "core:show-last-hidden-flow"
}
]
if (hideStack.length > 0) {
menuItems.unshift({
label: RED._("workspace.hiddenFlows",{count: hideStack.length}),
onselect: "core:list-hidden-flows"
})
} }
] return menuItems;
}
}); });
workspaceTabCount = 0; workspaceTabCount = 0;
} }
@@ -406,7 +415,9 @@ RED.workspaces = (function() {
} }
} }
}) })
RED.actions.add("core:list-hidden-flows",function() {
RED.actions.invoke("core:search","is:hidden ");
})
RED.actions.add("core:list-flows",function() { RED.actions.add("core:list-flows",function() {
RED.actions.invoke("core:search","type:tab "); RED.actions.invoke("core:search","type:tab ");
}) })
@@ -450,7 +461,7 @@ RED.workspaces = (function() {
var changes = { disabled: workspace.disabled }; var changes = { disabled: workspace.disabled };
workspace.disabled = disabled; workspace.disabled = disabled;
$("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
if (id === activeWorkspace) { if (!id || (id === activeWorkspace)) {
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
} }
var historyEvent = { var historyEvent = {
@@ -536,6 +547,9 @@ RED.workspaces = (function() {
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs)); RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
} }
}, },
isHidden: function(id) {
return hideStack.includes(id)
},
show: function(id,skipStack,unhideOnly) { show: function(id,skipStack,unhideOnly) {
if (!workspace_tabs.contains(id)) { if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id); var sf = RED.nodes.subflow(id);

View File

@@ -197,6 +197,7 @@ $view-select-mode-background: $secondary-background-selected;
$view-grid-color: #eee; $view-grid-color: #eee;
$node-label-color: #333; $node-label-color: #333;
$node-port-label-color: #888;
$node-border: #999; $node-border: #999;
$node-border-unknown: #f33; $node-border-unknown: #f33;
$node-border-placeholder: #aaa; $node-border-placeholder: #aaa;

View File

@@ -43,12 +43,24 @@
border-bottom: 1px solid $secondary-border-color; border-bottom: 1px solid $secondary-border-color;
box-shadow: 0 2px 6px $shadow; box-shadow: 0 2px 6px $shadow;
} }
.red-ui-debug-filter-row { #red-ui-sidebar-debug-filter-node-list-row {
.red-ui-nodeList { .red-ui-treeList-label.disabled {
margin: 10px 0; font-style: italic;
color: $secondary-text-color-disabled;
}
.red-ui-treeList-label {
&.selected, &.selected .red-ui-treeList-sublabel-text {
background: inherit;
}
&.selected, &.selected .red-ui-treeList-sublabel-text {
background: inherit;
}
&.focus, &.focus .red-ui-treeList-sublabel-text {
background: $list-item-background-hover !important;
}
} }
} }
.red-ui-debug-msg { .red-ui-debug-msg {
position: relative; position: relative;
border-bottom: 1px solid $debug-message-border; border-bottom: 1px solid $debug-message-border;

View File

@@ -47,7 +47,7 @@
.red-ui-flow-port-label { .red-ui-flow-port-label {
stroke-width: 0; stroke-width: 0;
fill: $secondary-text-color; fill: $node-port-label-color;
font-size: 16px; font-size: 16px;
dominant-baseline: middle; dominant-baseline: middle;
text-anchor: middle; text-anchor: middle;

View File

@@ -187,7 +187,7 @@
ul.red-ui-menu-dropdown { ul.red-ui-menu-dropdown {
background: $header-menu-background; background: $header-menu-background;
border: 1px solid $header-menu-background; border: 1px solid $header-menu-background;
width: 250px !important; width: 260px !important;
margin-top: 0; margin-top: 0;
li a { li a {
color: $header-menu-color; color: $header-menu-color;

View File

@@ -137,10 +137,10 @@
padding: 0; padding: 0;
border: 1px solid $form-input-border-color; border: 1px solid $form-input-border-color;
} }
.ui-spinner input[type=text] { .ui-spinner input {
background: $form-input-background; background: $form-input-background;
margin: 0 17px 0 0; margin: 0 17px 0 0;
padding: 8px; padding: 6px;
border: none; border: none;
border-top-right-radius: 0px; border-top-right-radius: 0px;
border-bottom-right-radius: 0px; border-bottom-right-radius: 0px;

View File

@@ -73,13 +73,13 @@
.red-ui-projects-dialog-screen-start { .red-ui-projects-dialog-screen-start {
.red-ui-projects-dialog-screen-start-hero { .red-ui-projects-dialog-screen-start-hero {
text-align: center; text-align: center;
font-size: 2em; font-size: 1.4em;
padding: 10px; padding: 10px;
min-height: 60px; min-height: 40px;
color: $primary-text-color; color: $primary-text-color;
} }
.red-ui-projects-dialog-screen-start-body { .red-ui-projects-dialog-screen-start-body {
min-height: 400px; min-height: 300px;
line-height: 1.6em; line-height: 1.6em;
p { p {
font-size: 1.1em; font-size: 1.1em;
@@ -92,7 +92,7 @@
} }
button.red-ui-button.red-ui-projects-dialog-button { button.red-ui-button.red-ui-projects-dialog-button {
width: calc(50% - 80px); width: calc(50% - 80px);
margin: 20px; margin: 10px 20px;
height: auto; height: auto;
line-height: 2em; line-height: 2em;
padding: 10px; padding: 10px;

View File

@@ -16,6 +16,7 @@
.red-ui-sidebar-info { .red-ui-sidebar-info {
height: 100%; height: 100%;
overflow: hidden;
} }
.red-ui-sidebar-info hr { .red-ui-sidebar-info hr {
margin: 10px 0; margin: 10px 0;
@@ -433,16 +434,19 @@ div.red-ui-info-table {
} }
.red-ui-info-outline-item-controls { .red-ui-info-outline-item-controls {
position: absolute; position: absolute;
top:0; top:1px;
bottom: 0; bottom: 1px;
right: 0px; right: 1px;
padding: 2px 3px 0 1px; padding: 1px 2px 0 1px;
text-align: right; text-align: right;
background: $list-item-background; background: $list-item-background;
.red-ui-treeList-label:hover & { .red-ui-treeList-label:hover & {
background: $list-item-background-hover; background: $list-item-background-hover;
} }
.red-ui-treeList-label.focus & {
background: $list-item-background-hover;
}
.red-ui-treeList-label.selected & { .red-ui-treeList-label.selected & {
background: $list-item-background-selected; background: $list-item-background-selected;
} }

View File

@@ -389,7 +389,19 @@ i.red-ui-tab-icon {
vertical-align: top; vertical-align: top;
} }
.red-ui-tab-hide {
.fa-eye-slash {
display: none;
}
&:hover {
.fa-eye-slash {
display: inline
}
.fa-eye {
display: none
}
}
}
.red-ui-tab-close { .red-ui-tab-close {
display: none; display: none;
background: $tab-background-inactive; background: $tab-background-inactive;

View File

@@ -89,6 +89,12 @@
color: $list-item-color; color: $list-item-color;
text-decoration: none; text-decoration: none;
} }
&.focus, &.focus .red-ui-treeList-sublabel-text {
background: $list-item-background-hover;
outline: 1px solid $form-input-focus-color !important;
outline-offset: -1px;
color: $list-item-color;
}
&.selected, &.selected .red-ui-treeList-sublabel-text { &.selected, &.selected .red-ui-treeList-sublabel-text {
background: $list-item-background-selected; background: $list-item-background-selected;
outline: none; outline: none;
@@ -102,6 +108,8 @@
} }
.red-ui-treeList-label-text { .red-ui-treeList-label-text {
margin-left: 4px; margin-left: 4px;
overflow: hidden;
text-overflow: ellipsis;
&:empty { &:empty {
min-height: 20px; min-height: 20px;
} }
@@ -121,6 +129,7 @@
.red-ui-treeList-icon { .red-ui-treeList-icon {
display: inline-block; display: inline-block;
flex-shrink: 0;
width: 20px; width: 20px;
text-align: center; text-align: center;
} }

View File

@@ -1,14 +1,23 @@
export default { export default {
steps: [ steps: [
{ {
title: "Create your first flow", title: {
'en-US': 'Create your first flow',
'ja': 'はじめてのフローを作成'
},
width: 400, width: 400,
description: 'This tutorial will guide you through creating your first flow', description: {
'en-US': 'This tutorial will guide you through creating your first flow',
'ja': '本チュートリアルでは、はじめてのフローを作成する方法について説明します。'
},
nextButton: 'start' nextButton: 'start'
}, },
{ {
element: "#red-ui-workspace .red-ui-tab-button.red-ui-tabs-add", element: "#red-ui-workspace .red-ui-tab-button.red-ui-tabs-add",
description: 'To add a new tab, click the <i class="fa fa-plus"></i> button', description: {
'en-US': 'To add a new tab, click the <i class="fa fa-plus"></i> button',
'ja': '新しいタブを追加するため、 <i class="fa fa-plus"></i> ボタンをクリックします。'
},
wait: { wait: {
type: "dom-event", type: "dom-event",
event: "click", event: "click",
@@ -18,7 +27,10 @@ export default {
{ {
element: '.red-ui-palette-node[data-palette-type="inject"]', element: '.red-ui-palette-node[data-palette-type="inject"]',
direction: 'right', direction: 'right',
description: 'The palette lists all of the nodes available to use. Drag a new Inject node into the workspace.', description: {
'en-US': 'The palette lists all of the nodes available to use. Drag a new Inject node into the workspace.',
'ja': 'パレットには、利用できる全てのードが一覧表示されます。injectードをワークスペースにドラッグします。'
},
fallback: 'inset-bottom-right', fallback: 'inset-bottom-right',
wait: { wait: {
type: "nr-event", type: "nr-event",
@@ -38,7 +50,10 @@ export default {
{ {
element: '.red-ui-palette-node[data-palette-type="debug"]', element: '.red-ui-palette-node[data-palette-type="debug"]',
direction: 'right', direction: 'right',
description: 'Next, drag a new Debug node into the workspace.', description: {
'en-US': 'Next, drag a new Debug node into the workspace.',
'ja': '次に、debugードをワークスペースにドラッグします。'
},
fallback: 'inset-bottom-right', fallback: 'inset-bottom-right',
wait: { wait: {
type: "nr-event", type: "nr-event",
@@ -57,7 +72,10 @@ export default {
}, },
{ {
element: function() { return $("#"+this.injectNode.id+" .red-ui-flow-port") }, element: function() { return $("#"+this.injectNode.id+" .red-ui-flow-port") },
description: 'Add a wire from the output of the Inject node to the input of the Debug node', description: {
'en-US': 'Add a wire from the output of the Inject node to the input of the Debug node',
'ja': 'injectードの出力から、debugードの入力へワイヤーで接続します。'
},
fallback: 'inset-bottom-right', fallback: 'inset-bottom-right',
wait: { wait: {
type: "nr-event", type: "nr-event",
@@ -69,7 +87,10 @@ export default {
}, },
{ {
element: "#red-ui-header-button-deploy", element: "#red-ui-header-button-deploy",
description: 'Deploy your changes so the flow is active in the runtime', description: {
'en-US': 'Deploy your changes so the flow is active in the runtime',
'ja': 'フローをランタイムで実行させるため、変更をデプロイします。'
},
width: 200, width: 200,
wait: { wait: {
type: "dom-event", type: "dom-event",

View File

@@ -3,48 +3,79 @@ export default {
steps: [ steps: [
{ {
titleIcon: "fa fa-map-o", titleIcon: "fa fa-map-o",
title: { "en-US": "Welcome to Node-RED 2.1!" }, title: {
description: { "en-US": "Let's take a moment to discover the new features in this release." } "en-US": "Welcome to Node-RED 2.1!",
"ja": "Node-RED 2.1へようこそ!"
},
description: {
"en-US": "Let's take a moment to discover the new features in this release.",
"ja": "本リリースの新機能を見つけてみましょう。"
}
}, },
{ {
title: { "en-US": "A new Tour Guide" }, title: {
description: { "en-US": "<p>First, as you've already found, we now have this tour of new features. We'll only show the tour the first time you open the editor for each new version of Node-RED.</p>"+ "en-US": "A new Tour Guide",
"<p>You can choose not to see this tour in the future by disabling it under the View tab of User Settings.</p>" } "ja": "新しいツアーガイド"
},
description: {
"en-US": "<p>First, as you've already found, we now have this tour of new features. We'll only show the tour the first time you open the editor for each new version of Node-RED.</p>" +
"<p>You can choose not to see this tour in the future by disabling it under the View tab of User Settings.</p>",
"ja": "<p>最初に、既に見つけている様に、新機能の本ツアーがあります。本ツアーは、新バージョンのNode-REDフローエディタを初めて開いた時のみ表示されます。</p>" +
"<p>ユーザ設定の表示タブの中で、この機能を無効化することで、本ツアーを表示しないようにすることもできます。</p>"
}
}, },
{ {
title: { "en-US": "New Edit menu" }, title: {
"en-US": "New Edit menu",
"ja": "新しい編集メニュー"
},
prepare() { prepare() {
$("#red-ui-header-button-sidemenu").trigger("click"); $("#red-ui-header-button-sidemenu").trigger("click");
$("#menu-item-edit-menu").parent().addClass("open") $("#menu-item-edit-menu").parent().addClass("open");
}, },
complete() { complete() {
$("#menu-item-edit-menu").parent().removeClass("open") $("#menu-item-edit-menu").parent().removeClass("open");
}, },
element: "#menu-item-edit-menu-submenu", element: "#menu-item-edit-menu-submenu",
interactive: false, interactive: false,
direction: "left", direction: "left",
description: { "en-US": "<p>The main menu has been updated with a new 'Edit' section. This includes all of the familar options, like cut/paste and undo/redo.</p>"+ description: {
"<p>The menu now displays keyboard shortcuts for the options.</p>" } "en-US": "<p>The main menu has been updated with a new 'Edit' section. This includes all of the familar options, like cut/paste and undo/redo.</p>" +
"<p>The menu now displays keyboard shortcuts for the options.</p>",
"ja": "<p>メインメニューに「編集」セクションが追加されました。本セクションには、切り取り/貼り付けや、変更操作を戻す/やり直しの様な使い慣れたオプションが含まれています。</p>" +
"<p>本メニューには、オプションのためのキーボードショートカットも表示されるようになりました。</p>"
}
}, },
{ {
title: { "en-US": "Arranging nodes" }, title: {
"en-US": "Arranging nodes",
"ja": "ノードの配置"
},
prepare() { prepare() {
$("#red-ui-header-button-sidemenu").trigger("click"); $("#red-ui-header-button-sidemenu").trigger("click");
$("#menu-item-arrange-menu").parent().addClass("open") $("#menu-item-arrange-menu").parent().addClass("open");
}, },
complete() { complete() {
$("#menu-item-arrange-menu").parent().removeClass("open") $("#menu-item-arrange-menu").parent().removeClass("open");
}, },
element: "#menu-item-arrange-menu-submenu", element: "#menu-item-arrange-menu-submenu",
interactive: false, interactive: false,
direction: "left", direction: "left",
description: { "en-US": "<p>The new 'Arrange' section of the menu provides new options to help arrange your nodes. You can align them to a common edge, spread them out evenly or change their order.</p>" }, description: {
"en-US": "<p>The new 'Arrange' section of the menu provides new options to help arrange your nodes. You can align them to a common edge, spread them out evenly or change their order.</p>",
"ja": "<p>メニューの新しい「配置」セクションには、ノードの配置を助ける新しいオプションが提供されています。ノードの端を揃えたり、均等に配置したり、表示順序を変更したりできます。</p>"
}
}, },
{ {
title: { "en-US": "Hiding tabs" }, title: {
"en-US": "Hiding tabs",
"ja": "タブの非表示"
},
element: "#red-ui-workspace-tabs > li.active", element: "#red-ui-workspace-tabs > li.active",
description: { "en-US": '<p>Tabs can now be hidden by clicking their <i class="fa fa-times"></i> icon.</p><p>The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.' }, description: {
"en-US": '<p>Tabs can now be hidden by clicking their <i class="fa fa-times"></i> icon.</p><p>The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.',
"ja": '<p><i class="fa fa-times"></i> アイコンをクリックすることで、タブを非表示にできます。</p><p>情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。'
},
interactive: false, interactive: false,
prepare() { prepare() {
$("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","block"); $("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","block");
@@ -54,9 +85,15 @@ export default {
} }
}, },
{ {
title: { "en-US": "Tab menu" }, title: {
"en-US": "Tab menu",
"ja": "タブメニュー"
},
element: "#red-ui-workspace-tabs-menu", element: "#red-ui-workspace-tabs-menu",
description: { "en-US": '<p>The new tab menu also provides lots of new options for your tabs.</p>' }, description: {
"en-US": "<p>The new tab menu also provides lots of new options for your tabs.</p>",
"ja": "<p>新しいタブメニューには、タブに関する沢山の新しいオプションが提供されています。</p>"
},
interactive: false, interactive: false,
direction: "left", direction: "left",
prepare() { prepare() {
@@ -67,10 +104,16 @@ export default {
} }
}, },
{ {
title: { "en-US": "Flow and Group level environment variables" }, title: {
"en-US": "Flow and Group level environment variables",
"ja": "フローとグループの環境変数"
},
element: "#red-ui-workspace-tabs > li.active", element: "#red-ui-workspace-tabs > li.active",
interactive: false, interactive: false,
description: { "en-US": "<p>Flows and Groups can now have their own environment variables that can be referenced by nodes inside them.</p>" }, description: {
"en-US": "<p>Flows and Groups can now have their own environment variables that can be referenced by nodes inside them.</p>",
"ja": "<p>フローとグループには、内部のノードから参照できる環境変数を設定できるようになりました。</p>"
}
}, },
{ {
prepare(done) { prepare(done) {
@@ -78,44 +121,86 @@ export default {
setTimeout(done,700); setTimeout(done,700);
}, },
element: "#red-ui-tab-editor-tab-envProperties-link-button", element: "#red-ui-tab-editor-tab-envProperties-link-button",
description: { "en-US": "<p>Their edit dialogs have a new Environment Variables section.</p>" }, description: {
"en-US": "<p>Their edit dialogs have a new Environment Variables section.</p>",
"ja": "<p>編集ダイアログに環境変数セクションが追加されました。</p>"
}
}, },
{ {
element: ".node-input-env-container-row", element: ".node-input-env-container-row",
direction: "left", direction: "left",
description: { "en-US": '<p>The environment variables are listed in this table and new ones can be added by clicking the <i class="fa fa-plus"></i> button.</p>' }, description: {
"en-US": '<p>The environment variables are listed in this table and new ones can be added by clicking the <i class="fa fa-plus"></i> button.</p>',
"ja": '<p>この表に環境変数が一覧表示されており、<i class="fa fa-plus"></i>ボタンをクリックすることで新しい変数を追加できます。</p>'
},
complete(done) { complete(done) {
$("#node-dialog-cancel").trigger("click"); $("#node-dialog-cancel").trigger("click");
setTimeout(done,500); setTimeout(done,500);
} }
}, },
{ {
title: {"en-US":"Link Call node added"}, title: {
"en-US": "Link Call node added",
"ja": "Link Callードを追加"
},
prepare(done) { prepare(done) {
this.paletteWasClosed = $("#red-ui-main-container").hasClass("red-ui-palette-closed");
RED.actions.invoke("core:toggle-palette",true)
$('[data-palette-type="link call"]')[0].scrollIntoView({block:"center"}) $('[data-palette-type="link call"]')[0].scrollIntoView({block:"center"})
setTimeout(done,100); setTimeout(done,100);
}, },
element: '[data-palette-type="link call"]', element: '[data-palette-type="link call"]',
direction: "right", direction: "right",
description: { "en-US": '<p>The <code>Link Call</code> node lets you call another flow that begins with a <code>Link In</code> node and get the result back when the message reaches a <code>Link Out</code> node.</p>' }, description: {
"en-US": "<p>The <code>Link Call</code> node lets you call another flow that begins with a <code>Link In</code> node and get the result back when the message reaches a <code>Link Out</code> node.</p>",
"ja": "<p><code>Link Call</code>ノードを用いることで、<code>Link In</code>ノードから始まるフローを呼び出し、<code>Link Out</code>ノードに到達した時に、結果を取得できます。</p>"
}
}, },
{ {
title: {"en-US":"File nodes renamed"}, title: {
"en-US": "MQTT nodes support dynamic connections",
"ja": "MQTTードが動的接続をサポート"
},
prepare(done) { prepare(done) {
$('[data-palette-type="file"]')[0].scrollIntoView({block:"center"}) $('[data-palette-type="mqtt out"]')[0].scrollIntoView({block:"center"})
setTimeout(done,100); setTimeout(done,100);
}, },
element: '[data-palette-type="mqtt out"]',
direction: "right",
description: {
"en-US": '<p>The <code>MQTT</code> nodes now support creating their connections and subscriptions dynamically.</p>',
"ja": '<p><code>MQTT</code>ノードは、動的な接続や購読ができるようになりました。</p>'
},
},
{
title: {
"en-US": "File nodes renamed",
"ja": "ファイルノードの名前変更"
},
prepare(done) {
$('[data-palette-type="file"]')[0].scrollIntoView({block:"center"});
setTimeout(done,100);
},
complete() {
if (this.paletteWasClosed) {
RED.actions.invoke("core:toggle-palette",false)
}
},
element: '[data-palette-type="file"]', element: '[data-palette-type="file"]',
direction: "right", direction: "right",
description: { "en-US": '<p>The file nodes have been renamed to make it clearer which node does what.</p>' }, description: {
"en-US": "<p>The file nodes have been renamed to make it clearer which node does what.</p>",
"ja": "<p>fileードの名前が変更され、どのードが何を行うかが明確になりました。</p>"
}
}, },
{ {
title: {"en-US":"Deep copy option on Change node"}, title: {
"en-US": "Deep copy option on Change node",
"ja": "Changeードのディープコピーオプション"
},
prepare(done) { prepare(done) {
var def = RED.nodes.getType('change') var def = RED.nodes.getType('change');
RED.editor.edit({id:"test",type:"change",rules:[{t:'set',p:'payload',pt:'msg', tot:'msg',to:"anotherProperty"}],_def:def, _:def._}) RED.editor.edit({id:"test",type:"change",rules:[{t:"set",p:"payload",pt:"msg", tot:"msg",to:"anotherProperty"}],_def:def, _:def._});
setTimeout(done,700); setTimeout(done,700);
}, },
complete(done) { complete(done) {
@@ -123,13 +208,22 @@ export default {
setTimeout(done,500); setTimeout(done,500);
}, },
element: function() { element: function() {
return $(".node-input-rule-property-deepCopy").next() return $(".node-input-rule-property-deepCopy").next();
}, },
description: { "en-US": '<p>The Set rule has a new option to create a deep copy of the value. This ensures a complete copy is made, rather than using a reference.</p>' }, description: {
"en-US": "<p>The Set rule has a new option to create a deep copy of the value. This ensures a complete copy is made, rather than using a reference.</p>",
"ja": "<p>値を代入に、値のディープコピーを作成するオプションが追加されました。これによって参照ではなく、完全なコピーが作成されます。</p>"
}
}, },
{ {
title: { "en-US": "And that's not all..." }, title: {
description: { "en-US": "<p>There are many more smaller changes, including:</p><ul><li>Auto-complete suggestions in the <code>msg</code> TypedInput.</li><li>Support for <code>msg.resetTimeout</code> in the <code>Join</code> node.</li><li>Pushing messages to the front of the queue in the <code>Delay</code> node's rate limiting mode.</li><li>An optional second output on the <code>Delay</code> node for rate limited messages.</li></ul>" } "en-US": "And that's not all...",
"ja": "これが全てではありません..."
},
description: {
"en-US": "<p>There are many more smaller changes, including:</p><ul><li>Auto-complete suggestions in the <code>msg</code> TypedInput.</li><li>Support for <code>msg.resetTimeout</code> in the <code>Join</code> node.</li><li>Pushing messages to the front of the queue in the <code>Delay</code> node's rate limiting mode.</li><li>An optional second output on the <code>Delay</code> node for rate limited messages.</li></ul>",
"ja": "<p>以下の様な小さな変更が沢山あります:</p><ul><li><code>msg</code> TypedInputの自動補完提案</li><li><code>Join</code>ノードで<code>msg.resetTimeout</code>のサポート</li><li><code>Delay</code>ノードの流量制御モードにおいて先頭メッセージをキューに追加</li><li><code>Delay</code>ードで流量制限されたメッセージ向けの任意の2つ目の出力</li></ul>"
}
} }
] ]
} }

View File

@@ -7,7 +7,7 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<!-- <!--
Copyright JS Foundation and other contributors, http://js.foundation Copyright OpenJS Foundation and other contributors, https://openjsf.org/
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -24,24 +24,24 @@
<title>{{ page.title }}</title> <title>{{ page.title }}</title>
<link rel="icon" type="image/png" href="{{ page.favicon }}"> <link rel="icon" type="image/png" href="{{ page.favicon }}">
<link rel="mask-icon" href="{{ page.tabicon.icon }}" color="{{ page.tabicon.colour }}"> <link rel="mask-icon" href="{{ page.tabicon.icon }}" color="{{ page.tabicon.colour }}">
<link rel="stylesheet" href="vendor/jquery/css/base/jquery-ui.min.css"> <link rel="stylesheet" href="vendor/jquery/css/base/jquery-ui.min.css?v={{ page.version }}">
<link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css?v={{ page.version }}">
<link rel="stylesheet" href="red/style.min.css"> <link rel="stylesheet" href="red/style.min.css?v={{ page.version }}">
{{#page.css}} {{#page.css}}
<link rel="stylesheet" href="{{.}}"> <link rel="stylesheet" href="{{.}}">
{{/page.css}} {{/page.css}}
{{#asset.vendorMonaco}} {{#asset.vendorMonaco}}
<link rel="stylesheet" href="vendor/monaco/style.css"> <link rel="stylesheet" href="vendor/monaco/style.css?v={{ page.version }}">
{{/asset.vendorMonaco}} {{/asset.vendorMonaco}}
</head> </head>
<body spellcheck="false"> <body spellcheck="false">
<div id="red-ui-editor"></div> <div id="red-ui-editor"></div>
<script src="vendor/vendor.js"></script> <script src="vendor/vendor.js?v={{ page.version }}"></script>
{{#asset.vendorMonaco}} {{#asset.vendorMonaco}}
<script src="{{ asset.vendorMonaco }}"></script> <script src="{{ asset.vendorMonaco }}?v={{ page.version }}"></script>
{{/asset.vendorMonaco}} {{/asset.vendorMonaco}}
<script src="{{ asset.red }}"></script> <script src="{{ asset.red }}?v={{ page.version }}"></script>
<script src="{{ asset.main }}"></script> <script src="{{ asset.main }}?v={{ page.version }}"></script>
{{# page.scripts }} {{# page.scripts }}
<script src="{{.}}"></script> <script src="{{.}}"></script>
{{/ page.scripts }} {{/ page.scripts }}

View File

@@ -143,7 +143,8 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
.inject-time-count { .inject-time-count {
width: 40px !important; padding-left: 3px !important;
width: 80px !important;
} }
</style> </style>
@@ -352,14 +353,16 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var node = this; var node = this;
var payloadType = node.payloadType;
if (node.payloadType == null) { if (node.payloadType == null) {
if (node.payload == "") { if (node.payload == "") {
node.payloadType = "date"; payloadType = "date";
} else { } else {
node.payloadType = "str"; payloadType = "str";
} }
} else if (node.payloadType === 'string' || node.payloadType === 'none') { } else if (node.payloadType === 'string' || node.payloadType === 'none') {
node.payloadType = "str"; payloadType = "str";
} }
$("#inject-time-type-select").on("change", function() { $("#inject-time-type-select").on("change", function() {
@@ -538,12 +541,10 @@
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"}) var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
.css("width","calc(70% - 30px)") .css("width","calc(70% - 30px)")
.appendTo(row) .appendTo(row)
.typedInput({default:'str',types:['flow','global','str','num','bool','json','bin','date','jsonata','env','msg']}); .typedInput({default:prop.vt || 'str',types:['flow','global','str','num','bool','json','bin','date','jsonata','env','msg']});
propertyName.typedInput('value',prop.p); propertyName.typedInput('value',prop.p);
propertyValue.typedInput('value',prop.v); propertyValue.typedInput('value',prop.v);
propertyValue.typedInput('type',prop.vt);
}, },
removable: true, removable: true,
sortable: true sortable: true
@@ -558,12 +559,12 @@
var payload = { var payload = {
p:'payload', p:'payload',
v: node.payload ? node.payload : '', v: node.payload ? node.payload : '',
vt:node.payloadType ? node.payloadType : 'date' vt:payloadType ? payloadType : 'date'
}; };
var topic = { var topic = {
p:'topic', p:'topic',
v: node.topic ? node.topic : '', v: node.topic ? node.topic : '',
vt:'string' vt:'str'
} }
node.props = [payload,topic]; node.props = [payload,topic];
} }
@@ -574,11 +575,16 @@
if (newProp.v === undefined) { if (newProp.v === undefined) {
if (prop.p === 'payload') { if (prop.p === 'payload') {
newProp.v = node.payload ? node.payload : ''; newProp.v = node.payload ? node.payload : '';
newProp.vt = node.payloadType ? node.payloadType : 'date'; newProp.vt = payloadType ? payloadType : 'date';
} else if (prop.p === 'topic' && prop.vt === "str") { } else if (prop.p === 'topic' && prop.vt === "str") {
newProp.v = node.topic ? node.topic : ''; newProp.v = node.topic ? node.topic : '';
} }
} }
if (newProp.vt === "string") {
// Fix bug in pre 2.1 where an old Inject node might have
// a migrated rule with type 'string' not 'str'
newProp.vt = "str";
}
eList.editableList('addItem',newProp); eList.editableList('addItem',newProp);
} }

View File

@@ -292,6 +292,7 @@
}; };
RED.events.on("project:change", this.clearMessageList); RED.events.on("project:change", this.clearMessageList);
RED.actions.add("core:clear-debug-messages", function() { RED.debug.clearMessageList(true) }); RED.actions.add("core:clear-debug-messages", function() { RED.debug.clearMessageList(true) });
RED.actions.add("core:clear-filtered-debug-messages", function() { RED.debug.clearMessageList(true, true) });
RED.actions.add("core:activate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(true), true); }); RED.actions.add("core:activate-selected-debug-nodes", function() { setDebugNodeState(getSelectedDebugNodes(true), true); });
RED.actions.add("core:activate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, true),true); }); RED.actions.add("core:activate-all-debug-nodes", function() { setDebugNodeState(getMatchingDebugNodes(true, true),true); });

View File

@@ -280,6 +280,18 @@ module.exports = function(RED) {
root: path.join(__dirname,"lib","debug"), root: path.join(__dirname,"lib","debug"),
dotfiles: 'deny' dotfiles: 'deny'
}; };
res.sendFile(req.params[0], options); try {
res.sendFile(
req.params[0],
options,
err => {
if (err) {
res.sendStatus(404);
}
}
)
} catch(err) {
res.sendStatus(404);
}
}); });
}; };

View File

@@ -52,7 +52,7 @@
treeList = $("<div>") treeList = $("<div>")
.css({width: "100%", height: "100%"}) .css({width: "100%", height: "100%"})
.appendTo(".node-input-link-row") .appendTo(".node-input-link-row")
.treeList({}) .treeList({autoSelect:false})
.on('treelistitemmouseover',function(e,item) { .on('treelistitemmouseover',function(e,item) {
if (item.node) { if (item.node) {
item.node.highlighted = true; item.node.highlighted = true;
@@ -68,6 +68,8 @@
} }
}); });
var candidateNodes = RED.nodes.filterNodes({type:targetType}); var candidateNodes = RED.nodes.filterNodes({type:targetType});
var candidateNodesCount = 0;
var search = $("#node-input-link-target-filter").searchBox({ var search = $("#node-input-link-target-filter").searchBox({
style: "compact", style: "compact",
delay: 300, delay: 300,
@@ -80,7 +82,7 @@
var count = treeList.treeList("filter", function(item) { var count = treeList.treeList("filter", function(item) {
return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1) return item.label.toLowerCase().indexOf(val) > -1 || (item.node && item.node.type.toLowerCase().indexOf(val) > -1)
}); });
search.searchBox("count",count+" / "+candidateNodes.length); search.searchBox("count",count+" / "+candidateNodesCount);
} }
} }
}); });
@@ -113,6 +115,11 @@
candidateNodes.forEach(function(n) { candidateNodes.forEach(function(n) {
if (flowMap[n.z]) { if (flowMap[n.z]) {
if (targetType === "link out" && n.mode === 'return') {
// Link In nodes looking for Link Out nodes should not
// include return-mode nodes.
return
}
var isChecked = false; var isChecked = false;
isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1; isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
if (isChecked) { if (isChecked) {
@@ -126,6 +133,7 @@
checkbox: node.type !== "link call", checkbox: node.type !== "link call",
radio: node.type === "link call" radio: node.type === "link call"
}) })
candidateNodesCount++;
} }
}); });
flows = flows.filter(function(f) { return f.children.length > 0 }) flows = flows.filter(function(f) { return f.children.length > 0 })
@@ -149,7 +157,7 @@
function onEditSave(node) { function onEditSave(node) {
var flows = treeList.treeList('data'); var flows = treeList.treeList('data');
node.links = []; node.links = [];
if (node.type !== "link out" || $("node-input-mode").val() === 'link') { if (node.type !== "link out" || $("#node-input-mode").val() === 'link') {
flows.forEach(function(f) { flows.forEach(function(f) {
f.children.forEach(function(n) { f.children.forEach(function(n) {
if (n.selected) { if (n.selected) {
@@ -234,6 +242,12 @@
}, },
oneditsave: function() { oneditsave: function() {
onEditSave(this); onEditSave(this);
// In case the name has changed, ensure any link call nodes on this
// tab are redrawn with the updated name
var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
localCallNodes.forEach(function(node) {
node.dirty = true;
});
}, },
onadd: onAdd, onadd: onAdd,
oneditresize: resizeNodeList oneditresize: resizeNodeList
@@ -259,12 +273,12 @@
} }
if (this.links.length > 0) { if (this.links.length > 0) {
var targetNode = RED.nodes.node(this.links[0]); var targetNode = RED.nodes.node(this.links[0]);
return targetNode && (targetNode.name || targetNode.id); return targetNode && (targetNode.name || this._("link.linkCall"));
} }
return this._("link.linkCall"); return this._("inject.none");
}, },
labelStyle: function() { labelStyle: function() {
return (this.name || this.links.length > 0)?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
onEditPrepare(this,"link in"); onEditPrepare(this,"link in");
@@ -275,7 +289,6 @@
oneditresize: resizeNodeList oneditresize: resizeNodeList
}); });
RED.nodes.registerType('link out',{ RED.nodes.registerType('link out',{
category: 'common', category: 'common',
color:"#ddd",//"#87D8CF", color:"#ddd",//"#87D8CF",

View File

@@ -31,24 +31,22 @@ RED.debug = (function() {
var activeWorkspace; var activeWorkspace;
var numMessages = 100; // Hardcoded number of message to show in debug window scrollback var numMessages = 100; // Hardcoded number of message to show in debug window scrollback
var filterVisible = false; var debugNodeTreeList;
var debugNodeList;
var debugNodeListExpandedFlows = {};
function init(_config) { function init(_config) {
config = _config; config = _config;
var content = $("<div>").css({"position":"relative","height":"100%"}); var content = $("<div>").css({"position":"relative","height":"100%"});
var toolbar = $('<div class="red-ui-sidebar-header">'+ var toolbar = $('<div class="red-ui-sidebar-header">'+
'<span class="button-group"><a id="red-ui-sidebar-debug-filter" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span></a></span>'+ '<span class="button-group">'+
'<span class="button-group"><a id="red-ui-sidebar-debug-clear" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-trash"></i></a></span></div>').appendTo(content); '<a id="red-ui-sidebar-debug-filter" style="padding-right: 5px" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span> <i style="padding-left: 5px;" class="fa fa-caret-down"></i></a>'+
'</span>'+
'<span class="button-group">'+
'<a id="red-ui-sidebar-debug-clear" style="border-right: none; padding-right: 6px" class="red-ui-sidebar-header-button" href="#" data-clear-type="all"><i class="fa fa-trash"></i> <span>all</span></a>' +
'<a id="red-ui-sidebar-debug-clear-opts" style="padding: 5px; border-left: none;" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></div>').appendTo(content);
var footerToolbar = $('<div>'+ var footerToolbar = $('<div>'+
// '<span class="button-group">'+
// '<a class="red-ui-footer-button-toggle text-button selected" id="red-ui-sidebar-debug-view-list" href="#"><span data-i18n="">list</span></a>'+
// '<a class="red-ui-footer-button-toggle text-button" id="red-ui-sidebar-debug-view-table" href="#"><span data-i18n="">table</span></a> '+
// '</span>'+
'<span class="button-group"><a id="red-ui-sidebar-debug-open" class="red-ui-footer-button" href="#"><i class="fa fa-desktop"></i></a></span> ' + '<span class="button-group"><a id="red-ui-sidebar-debug-open" class="red-ui-footer-button" href="#"><i class="fa fa-desktop"></i></a></span> ' +
'</div>'); '</div>');
@@ -56,85 +54,100 @@ RED.debug = (function() {
sbc = messageList[0]; sbc = messageList[0];
messageTable = $('<div class="red-ui-debug-content red-ui-debug-content-table hide"/>').appendTo(content); messageTable = $('<div class="red-ui-debug-content red-ui-debug-content-table hide"/>').appendTo(content);
var filterDialog = $('<div class="red-ui-debug-filter-box hide">'+ var filterDialogCloseTimeout;
'<div class="red-ui-debug-filter-row">'+ var filterDialogShown = false;
'<span class="button-group">'+ var filterDialog = $('<div class="red-ui-debug-filter-box hide"></div>').appendTo(toolbar);//content);
'<a class="red-ui-sidebar-header-button-toggle red-ui-sidebar-debug-filter-option selected" id="red-ui-sidebar-debug-filterAll" href="#"><span data-i18n="node-red:debug.sidebar.filterAll"></span></a>'+ filterDialog.on('mouseleave' ,function(evt) {
'<a class="red-ui-sidebar-header-button-toggle red-ui-sidebar-debug-filter-option" id="red-ui-sidebar-debug-filterSelected" href="#"><span data-i18n="node-red:debug.sidebar.filterSelected"></span></a>'+ if (filterDialogShown) {
'<a class="red-ui-sidebar-header-button-toggle red-ui-sidebar-debug-filter-option" id="red-ui-sidebar-debug-filterCurrent" href="#"><span data-i18n="node-red:debug.sidebar.filterCurrent"></span></a> '+ filterDialogCloseTimeout = setTimeout(function() {
filterDialog.slideUp(200);
filterDialogShown = false;
},500)
}
})
filterDialog.on('mouseenter' ,function(evt) {
clearTimeout(filterDialogCloseTimeout)
})
var filterToolbar = $('<div style="margin-bottom: 3px; display: flex;">'+
'<span style="flex-grow:1; text-align: left;">'+
'<span class="button-group"><button type="button" id="red-ui-sidebar-filter-select-all" class="red-ui-sidebar-header-button red-ui-button-small" data-i18n="node-red:debug.sidebar.selectAll"></button></span>' +
'<span class="button-group"><button type="button" id="red-ui-sidebar-filter-select-none" class="red-ui-sidebar-header-button red-ui-button-small" data-i18n="node-red:debug.sidebar.selectNone"></button></span>' +
'</span>'+ '</span>'+
'</div>'+ '<span class="button-group"><button type="button" id="red-ui-sidebar-filter-select-close" class="red-ui-sidebar-header-button red-ui-button-small"><i class="fa fa-times"></i></button></span>'+
'</div>').appendTo(toolbar);//content); '</div>').appendTo(filterDialog);
// var filterTypeRow = $('<div class="red-ui-debug-filter-row"></div>').appendTo(filterDialog); filterToolbar.find("#red-ui-sidebar-filter-select-close").on('click', function(evt) {
// $('<select><option>Show all debug nodes</option><option>Show selected debug nodes</option><option>Show current flow only</option></select>').appendTo(filterTypeRow); clearTimeout(filterDialogCloseTimeout)
filterDialogShown = false;
filterDialog.slideUp(200);
})
var debugNodeListRow = $('<div class="red-ui-debug-filter-row hide" id="red-ui-sidebar-debug-filter-node-list-row"></div>').appendTo(filterDialog); filterToolbar.find("#red-ui-sidebar-filter-select-all").on('click', function(evt) {
var flowCheckboxes = {}; evt.preventDefault();
var debugNodeListHeader = $('<div><span data-i18n="node-red:debug.sidebar.debugNodes"></span><span></span></div>'); var data = debugNodeTreeList.treeList('data');
var headerCheckbox = $('<input type="checkbox">').appendTo(debugNodeListHeader.find("span")[1]).checkboxSet(); data.forEach(function(flow) {
if (!flow.selected) {
debugNodeList = $('<ol>',{style:"text-align: left; min-height: 250px; max-height: 250px"}).appendTo(debugNodeListRow).editableList({ if (flow.treeList.checkbox) {
header: debugNodeListHeader, flow.treeList.checkbox.trigger('click')
class: 'red-ui-nodeList',
addItem: function(container,i,node) {
var row = $("<div>").appendTo(container);
row.attr('id','debug-filter-node-list-node-'+node.id.replace(/\./g,"_"));
if (node.type === 'tab') {
container.parent().addClass('red-ui-editableList-section-header');
if (!debugNodeListExpandedFlows.hasOwnProperty(node.id)) {
debugNodeListExpandedFlows[node.id] = true;
} }
var chevron = $('<i class="fa fa-angle-right"></i>').appendTo(row);
$('<span>').text(RED.utils.getNodeLabel(node,node.id)).appendTo(row);
var muteControl = $('<input type="checkbox">').appendTo($('<span class="meta">').appendTo(row));
muteControl.checkboxSet({
parent: headerCheckbox
});
flowCheckboxes[node.id] = muteControl;
row.on("click", function(e) {
e.stopPropagation();
debugNodeListExpandedFlows[node.id] = !debugNodeListExpandedFlows[node.id];
row.toggleClass('expanded',debugNodeListExpandedFlows[node.id]);
debugNodeList.editableList('filter');
})
row.addClass("expandable");
if (node.disabled) {
container.addClass('disabled');
muteControl.checkboxSet('disable');
debugNodeListExpandedFlows[node.id] = false;
}
row.toggleClass('expanded',debugNodeListExpandedFlows[node.id]);
} else { } else {
$('<span>',{style: "margin-left: 20px"}).text(RED.utils.getNodeLabel(node,node.id)).appendTo(row); flow.children.forEach(function(item) {
row.on("mouseenter",function() { if (!item.selected) {
config.messageMouseEnter(node.id); item.treeList.select();
}); }
row.on("mouseleave",function() { })
config.messageMouseLeave(node.id); }
}); });
var muteControl = $('<input type="checkbox">').prop('checked',!filteredNodes[node.id]).appendTo($('<span class="meta">').appendTo(row)); refreshMessageList();
muteControl.checkboxSet({ })
parent: flowCheckboxes[node.z]
}).on("change", function(e) { filterToolbar.find("#red-ui-sidebar-filter-select-none").on('click', function(evt) {
filteredNodes[node.id] = !$(this).prop('checked'); evt.preventDefault();
$(".red-ui-debug-msg-node-"+node.id.replace(/\./g,"_")).toggleClass('hide',filteredNodes[node.id]); debugNodeTreeList.treeList('clearSelection');
}); var data = debugNodeTreeList.treeList('data');
if ((node.hasOwnProperty("active") && !node.active) || RED.nodes.workspace(node.z).disabled) { data.forEach(function(flow) {
container.addClass('disabled'); if (flow.children) {
muteControl.checkboxSet('disable'); flow.children.forEach(function(item) {
filteredNodes[item.node.id] = true;
})
}
});
RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes))
refreshMessageList();
})
var debugNodeListRow = $('<div class="red-ui-debug-filter-row" id="red-ui-sidebar-debug-filter-node-list-row"></div>').appendTo(filterDialog);
debugNodeTreeList = $("<div></div>").appendTo(debugNodeListRow).css({width: "100%", height: "300px"})
.treeList({autoSelect: false}).on("treelistitemmouseover", function(e, item) {
if (item.node) {
item.node.highlighted = true;
item.node.dirty = true;
RED.view.redraw();
}
}).on("treelistitemmouseout", function(e, item) {
if (item.node) {
item.node.highlighted = false;
item.node.dirty = true;
RED.view.redraw();
}
}).on("treelistselect", function(e, item) {
if (item.children) {
item.children.forEach(function(child) {
if (child.checkbox) {
child.treeList.select(item.selected)
}
})
} else {
if (item.node) {
if (item.selected) {
delete filteredNodes[item.node.id]
} else {
filteredNodes[item.node.id] = true;
}
RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes))
refreshMessageList();
} }
} }
}, })
addButton: false,
scrollOnAdd: false,
filter: function(node) {
return (node.type === 'tab' || debugNodeListExpandedFlows[node.z] )
},
sort: function(A,B) {
}
});
try { try {
content.i18n(); content.i18n();
@@ -144,85 +157,95 @@ RED.debug = (function() {
toolbar.find('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll')); toolbar.find('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll'));
var filterButtonHandler = function(type) {
return function(e) {
e.preventDefault();
if (filterType !== type) {
$('.red-ui-sidebar-debug-filter-option').removeClass('selected');
$(this).addClass('selected');
if (filterType === 'filterSelected') {
debugNodeListRow.slideUp();
}
filterType = type;
if (filterType === 'filterSelected') {
debugNodeListRow.slideDown();
}
$('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType));
refreshMessageList();
}
}
}
filterDialog.find('#red-ui-sidebar-debug-filterAll').on("click",filterButtonHandler('filterAll'));
filterDialog.find('#red-ui-sidebar-debug-filterSelected').on("click",filterButtonHandler('filterSelected'));
filterDialog.find('#red-ui-sidebar-debug-filterCurrent').on("click",filterButtonHandler('filterCurrent'));
// $('#red-ui-sidebar-debug-view-list').on("click",function(e) {
// e.preventDefault();
// if (!$(this).hasClass('selected')) {
// $(this).addClass('selected');
// $('#red-ui-sidebar-debug-view-table').removeClass('selected');
// showMessageList();
// }
// });
// $('#red-ui-sidebar-debug-view-table').on("click",function(e) {
// e.preventDefault();
// if (!$(this).hasClass('selected')) {
// $(this).addClass('selected');
// $('#red-ui-sidebar-debug-view-list').removeClass('selected');
// showMessageTable();
// }
// });
var hideFilterTimeout;
toolbar.on('mouseleave',function() {
if ($('#red-ui-sidebar-debug-filter').hasClass('selected')) {
clearTimeout(hideFilterTimeout);
hideFilterTimeout = setTimeout(function() {
filterVisible = false;
$('#red-ui-sidebar-debug-filter').removeClass('selected');
filterDialog.slideUp(200);
},300);
}
});
toolbar.on('mouseenter',function() {
if ($('#red-ui-sidebar-debug-filter').hasClass('selected')) {
clearTimeout(hideFilterTimeout);
}
})
toolbar.find('#red-ui-sidebar-debug-filter').on("click",function(e) { toolbar.find('#red-ui-sidebar-debug-filter').on("click",function(e) {
e.preventDefault(); e.preventDefault();
if ($(this).hasClass('selected')) { var options = [
filterVisible = false; { label: $('<span data-i18n="[append]node-red:debug.sidebar.filterAll"><input type="radio" value="filterAll" name="filter-type" style="margin-top:0"> </span>').i18n() , value: "filterAll" },
$(this).removeClass('selected'); { label: $('<span><span data-i18n="[append]node-red:debug.sidebar.filterSelected"><input type="radio" value="filterSelected" name="filter-type" style="margin-top:0"> </span>...</span>').i18n(), value: "filterSelected" },
clearTimeout(hideFilterTimeout); { label: $('<span data-i18n="[append]node-red:debug.sidebar.filterCurrent"><input type="radio" value="filterCurrent" name="filter-type" style="margin-top:0"> </span>').i18n(), value: "filterCurrent" }
filterDialog.slideUp(200); ]
} else { var menu = RED.popover.menu({
$(this).addClass('selected'); options: options,
filterVisible = true; onselect: function(item) {
refreshDebugNodeList(); if (item.value !== filterType) {
filterDialog.slideDown(200); filterType = item.value;
} $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType));
refreshMessageList();
RED.settings.set("debug.filter",filterType)
}
if (filterType === 'filterSelected') {
refreshDebugNodeList();
filterDialog.slideDown(200);
filterDialogShown = true;
debugNodeTreeList.focus();
}
}
});
menu.show({
target: $("#red-ui-sidebar-debug-filter"),
align: "left",
offset: [$("#red-ui-sidebar-debug-filter").outerWidth()-2, -1]
})
$('input[name="filter-type"][value="'+RED.settings.get("debug.filter","filterAll")+'"]').prop("checked", true)
}); });
RED.popover.tooltip(toolbar.find('#red-ui-sidebar-debug-filter'),RED._('node-red:debug.sidebar.filterLog')); RED.popover.tooltip(toolbar.find('#red-ui-sidebar-debug-filter'),RED._('node-red:debug.sidebar.filterLog'));
toolbar.find("#red-ui-sidebar-debug-clear").on("click", function(e) { toolbar.find("#red-ui-sidebar-debug-clear").on("click", function(e) {
e.preventDefault(); e.preventDefault();
clearMessageList(false); var action = RED.settings.get("debug.clearType","all")
clearMessageList(false, action === 'filtered');
}); });
RED.popover.tooltip(toolbar.find("#red-ui-sidebar-debug-clear"),RED._('node-red:debug.sidebar.clearLog'),"core:clear-debug-messages"); var clearTooltip = RED.popover.tooltip(toolbar.find("#red-ui-sidebar-debug-clear"),RED._('node-red:debug.sidebar.clearLog'),"core:clear-debug-messages");
toolbar.find("#red-ui-sidebar-debug-clear-opts").on("click", function(e) {
e.preventDefault();
var options = [
{ label: $('<span data-i18n="[append]node-red:debug.sidebar.clearLog"><input type="radio" value="all" name="clear-type" style="margin-top:0"> </span>').i18n() , value: "all" },
{ label: $('<span data-i18n="[append]node-red:debug.sidebar.clearFilteredLog"><input type="radio" value="filtered" name="clear-type" style="margin-top:0"> </span>').i18n(), value: "filtered" }
]
var menu = RED.popover.menu({
options: options,
onselect: function(item) {
if (item.value === "all") {
$("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.all'));
clearTooltip.setAction("core:clear-debug-messages");
clearTooltip.setContent(RED._('node-red:debug.sidebar.clearLog'))
RED.settings.set("debug.clearType","all")
} else {
$("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.filtered'));
clearTooltip.setAction("core:clear-filtered-debug-messages");
clearTooltip.setContent(RED._('node-red:debug.sidebar.clearFilteredLog'))
RED.settings.set("debug.clearType","filtered")
}
}
});
menu.show({
target: $("#red-ui-sidebar-debug-clear-opts"),
align: "left",
offset: [$("#red-ui-sidebar-debug-clear-opts").outerWidth()-2, -1]
})
$('input[name="clear-type"][value="'+RED.settings.get("debug.clearType","all")+'"]').prop("checked", true)
})
var clearType = RED.settings.get("debug.clearType","all");
if (clearType === "all") {
toolbar.find("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.all'));
clearTooltip.setAction("core:clear-debug-messages");
clearTooltip.setContent(RED._('node-red:debug.sidebar.clearLog'))
} else {
toolbar.find("#red-ui-sidebar-debug-clear > span").text(RED._('node-red:debug.sidebar.filtered'));
clearTooltip.setAction("core:clear-filtered-debug-messages");
clearTooltip.setContent(RED._('node-red:debug.sidebar.clearFilteredLog'))
}
filterType = RED.settings.get("debug.filter","filterAll")
var filteredNodeList = RED.settings.get("debug.filteredNodes",[]);
filteredNodes = {}
filteredNodeList.forEach(function(id) {
filteredNodes[id] = true
})
toolbar.find('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType));
refreshMessageList();
return { return {
content: content, content: content,
@@ -254,8 +277,6 @@ RED.debug = (function() {
function refreshDebugNodeList() { function refreshDebugNodeList() {
debugNodeList.editableList('empty');
var workspaceOrder = RED.nodes.getWorkspaceOrder(); var workspaceOrder = RED.nodes.getWorkspaceOrder();
var workspaceOrderMap = {}; var workspaceOrderMap = {};
workspaceOrder.forEach(function(ws,i) { workspaceOrder.forEach(function(ws,i) {
@@ -320,15 +341,45 @@ RED.debug = (function() {
return labelA.localeCompare(labelB); return labelA.localeCompare(labelB);
}); });
var currentWs = null; var currentWs = null;
var nodeList = []; var data = [];
var currentFlow;
var currentSelectedCount = 0;
candidateNodes.forEach(function(node) { candidateNodes.forEach(function(node) {
if (currentWs !== node.z) { if (currentWs !== node.z) {
if (currentFlow && currentFlow.checkbox) {
currentFlow.selected = currentSelectedCount === currentFlow.children.length
}
currentSelectedCount = 0;
currentWs = node.z; currentWs = node.z;
nodeList.push(RED.nodes.workspace(node.z)); var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs);
currentFlow = {
label: RED.utils.getNodeLabel(parent, currentWs),
}
if (!parent.disabled) {
currentFlow.children = [];
currentFlow.checkbox = true;
} else {
currentFlow.class = "disabled"
}
data.push(currentFlow);
}
if (currentFlow.children) {
if (!filteredNodes[node.id]) {
currentSelectedCount++;
}
currentFlow.children.push({
label: RED.utils.getNodeLabel(node,node.id),
node: node,
checkbox: true,
selected: !filteredNodes[node.id]
});
} }
nodeList.push(node);
}); });
debugNodeList.editableList('addItems',nodeList); if (currentFlow && currentFlow.checkbox) {
currentFlow.selected = currentSelectedCount === currentFlow.children.length
}
debugNodeTreeList.treeList("data", data);
} }
function getTimestamp() { function getTimestamp() {
@@ -340,7 +391,16 @@ RED.debug = (function() {
return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
} }
var refreshTimeout;
function refreshMessageList(_activeWorkspace) { function refreshMessageList(_activeWorkspace) {
if (refreshTimeout) {
clearTimeout(refreshTimeout);
}
refreshTimeout = setTimeout(function() {
_refreshMessageList(_activeWorkspace);
},200);
}
function _refreshMessageList(_activeWorkspace) {
if (_activeWorkspace) { if (_activeWorkspace) {
activeWorkspace = _activeWorkspace.replace(/\./g,"_"); activeWorkspace = _activeWorkspace.replace(/\./g,"_");
} }
@@ -415,6 +475,7 @@ RED.debug = (function() {
}); });
delete filteredNodes[sourceId]; delete filteredNodes[sourceId];
$("#red-ui-sidebar-debug-filterSelected").trigger("click"); $("#red-ui-sidebar-debug-filterSelected").trigger("click");
RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes))
refreshMessageList(); refreshMessageList();
}}, }},
{id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){
@@ -601,8 +662,12 @@ RED.debug = (function() {
} }
} }
function clearMessageList(clearFilter) { function clearMessageList(clearFilter, filteredOnly) {
$(".red-ui-debug-msg").remove(); if (!filteredOnly) {
$(".red-ui-debug-msg").remove();
} else {
$(".red-ui-debug-msg:not(.hide)").remove();
}
config.clear(); config.clear();
if (!!clearFilter) { if (!!clearFilter) {
clearFilterSettings(); clearFilterSettings();
@@ -613,10 +678,9 @@ RED.debug = (function() {
function clearFilterSettings() { function clearFilterSettings() {
filteredNodes = {}; filteredNodes = {};
filterType = 'filterAll'; filterType = 'filterAll';
$('.red-ui-sidebar-debug-filter-option').removeClass('selected'); RED.settings.set("debug.filter",filterType);
$('#red-ui-sidebar-debug-filterAll').addClass('selected'); RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes))
$('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll')); $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.filterAll'));
$('#red-ui-sidebar-debug-filter-node-list-row').slideUp();
} }
return { return {

View File

@@ -15,18 +15,22 @@ $(function() {
} }
} }
var uiComponents = RED.debug.init(options); try {
var uiComponents = RED.debug.init(options);
$(".red-ui-debug-window").append(uiComponents.content);
$(".red-ui-debug-window").append(uiComponents.content); window.addEventListener('message',function(evt) {
if (evt.data.event === "message") {
RED.debug.handleDebugMessage(evt.data.msg);
} else if (evt.data.event === "workspaceChange") {
RED.debug.refreshMessageList(evt.data.activeWorkspace);
} else if (evt.data.event === "projectChange") {
RED.debug.clearMessageList(true);
}
},false);
} catch(err) {
console.error(err)
}
window.addEventListener('message',function(evt) {
if (evt.data.event === "message") {
RED.debug.handleDebugMessage(evt.data.msg);
} else if (evt.data.event === "workspaceChange") {
RED.debug.refreshMessageList(evt.data.activeWorkspace);
} else if (evt.data.event === "projectChange") {
RED.debug.clearMessageList(true);
}
},false);
}) })
}); });

View File

@@ -117,30 +117,35 @@
return r; return r;
} }
function createValueField(row){ function createValueField(row, defaultType){
return $('<input/>',{class:"node-input-rule-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata','env',previousValueType]}); return $('<input/>',{class:"node-input-rule-value",type:"text",style:"width: 100%;"}).appendTo(row)
.typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
} }
function createNumValueField(row){ function createNumValueField(row, defaultType){
return $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata','env']}); return $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"width: 100%;"}).appendTo(row)
.typedInput({default:defaultType||'num',types:['flow','global','num','jsonata','env']});
} }
function createExpValueField(row){ function createExpValueField(row){
return $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:'jsonata',types:['jsonata']}); return $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"width: 100%;"}).appendTo(row)
.typedInput({default:'jsonata',types:['jsonata']});
} }
function createBtwnValueField(row){ function createBtwnValueField(row, defaultType){
return $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]}); return $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"width: 100%;"}).appendTo(row)
.typedInput({default:defaultType||'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
} }
function createBtwnValue2Field(row3, andLabel){ function createBtwnValue2Field(row3, andLabel, defaultType){
$('<div/>',{class:"node-input-rule-btwn-label", style:"width: 120px; text-align: right;"}).text(" "+andLabel+" ").appendTo(row3); $('<div/>',{class:"node-input-rule-btwn-label", style:"width: 120px; text-align: right;"}).text(" "+andLabel+" ").appendTo(row3);
var row3InputCell = $('<div/>',{style:"flex-grow:1; margin-left: 5px;"}).appendTo(row3); var row3InputCell = $('<div/>',{style:"flex-grow:1; margin-left: 5px;"}).appendTo(row3);
return $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 100%"}).appendTo(row3InputCell).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]}); return $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 100%"}).appendTo(row3InputCell)
.typedInput({default:defaultType||'num',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
} }
function createTypeValueField(){ function createTypeValueField(row, defaultType){
return $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:'string',types:[ return $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"width: 100%;"}).appendTo(row).typedInput({default:defaultType || 'string',types:[
{value:"string",label:RED._("common.type.string"),hasValue:false,icon:"red/images/typedInput/az.png"}, {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:"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:"boolean",label:RED._("common.type.boolean"),hasValue:false,icon:"red/images/typedInput/bool.png"},
@@ -211,6 +216,7 @@
var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1); var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1);
var exportedRule = exportRule(lastRule.element); var exportedRule = exportRule(lastRule.element);
opt.r.vt = exportedRule.vt; opt.r.vt = exportedRule.vt;
opt.r.v = "";
// We could copy the value over as well and preselect it (see the 'activeElement' code below) // We could copy the value over as well and preselect it (see the 'activeElement' code below)
// But not sure that feels right. Is copying over the last value 'expected' behaviour? // But not sure that feels right. Is copying over the last value 'expected' behaviour?
// It would make sense for an explicit 'copy' action, but not sure where the copy button would // It would make sense for an explicit 'copy' action, but not sure where the copy button would
@@ -278,24 +284,12 @@
selectField.on("change", function() { selectField.on("change", function() {
var fieldToFocus; var fieldToFocus;
var type = selectField.val(); var type = selectField.val();
if (valueField){ if (valueField) { valueField.typedInput('hide'); }
valueField.typedInput('hide'); if (expValueField) { expValueField.typedInput('hide'); }
} if (numValueField) { numValueField.typedInput('hide'); }
if (expValueField){ if (typeValueField) { typeValueField.typedInput('hide'); }
expValueField.typedInput('hide'); if (btwnValueField) { btwnValueField.typedInput('hide'); }
} if (btwnValue2Field) { btwnValue2Field.typedInput('hide'); }
if (numValueField){
numValueField.typedInput('hide');
}
if (typeValueField){
typeValueField.typedInput('hide');
}
if (btwnValueField){
btwnValueField.typedInput('hide');
}
if (btwnValue2Field){
btwnValue2Field.typedInput('hide');
}
if ((type === "btwn") || (type === "index")) { if ((type === "btwn") || (type === "index")) {
if (!btwnValueField){ if (!btwnValueField){
@@ -318,7 +312,7 @@
} else if (type === "istype") { } else if (type === "istype") {
if (!typeValueField){ if (!typeValueField){
typeValueField = createTypeValueField(); typeValueField = createTypeValueField(rowInputCell);
} }
typeValueField.typedInput('show'); typeValueField.typedInput('show');
fieldToFocus = typeValueField; fieldToFocus = typeValueField;
@@ -351,7 +345,9 @@
} else { } else {
selectField.width("auto") selectField.width("auto")
} }
fieldToFocus.typedInput("focus"); if (fieldToFocus) {
fieldToFocus.typedInput("focus");
}
// Preselect the contents of the element // Preselect the contents of the element
// if (focusValueField && document.activeElement) { // if (focusValueField && document.activeElement) {
// document.activeElement.selectionStart = 0; // document.activeElement.selectionStart = 0;
@@ -359,48 +355,26 @@
// } // }
}); });
selectField.val(rule.t); selectField.val(rule.t);
if ((rule.t == "btwn") || (rule.t == "index")) {
if (!btwnValueField){
btwnValueField = createBtwnValueField(rowInputCell);
}
btwnValueField.typedInput('value',rule.v);
btwnValueField.typedInput('type',rule.vt||'num');
if (!btwnValue2Field){ if ((rule.t == "btwn") || (rule.t == "index")) {
btwnValue2Field = createBtwnValue2Field(row3, andLabel); btwnValueField = createBtwnValueField(rowInputCell,rule.vt||'num');
} btwnValueField.typedInput('value',rule.v);
btwnValue2Field = createBtwnValue2Field(row3, andLabel,rule.v2t||'num');
btwnValue2Field.typedInput('value',rule.v2); btwnValue2Field.typedInput('value',rule.v2);
btwnValue2Field.typedInput('type',rule.v2t||'num');
} else if ((rule.t === "head") || (rule.t === "tail")) { } else if ((rule.t === "head") || (rule.t === "tail")) {
if (!numValueField){ numValueField = createNumValueField(rowInputCell,rule.vt||'num');
numValueField = createNumValueField(row);
}
numValueField.typedInput('value',rule.v); numValueField.typedInput('value',rule.v);
numValueField.typedInput('type',rule.vt||'num');
} else if (rule.t === "istype") { } else if (rule.t === "istype") {
if (!typeValueField){ typeValueField = createTypeValueField(rowInputCell,rule.vt);
typeValueField =createTypeValueField();
}
typeValueField.typedInput('value',rule.vt); typeValueField.typedInput('value',rule.vt);
typeValueField.typedInput('type',rule.vt);
} else if (rule.t === "jsonata_exp") { } else if (rule.t === "jsonata_exp") {
if (!expValueField){ expValueField = createExpValueField(rowInputCell,rule.vt||'jsonata');
expValueField = createExpValueField(row);
}
expValueField.typedInput('value',rule.v); expValueField.typedInput('value',rule.v);
expValueField.typedInput('type',rule.vt||'jsonata');
} else if (typeof rule.v != "undefined") { } else if (typeof rule.v != "undefined") {
if (!valueField){ valueField = createValueField(rowInputCell,rule.vt||'str');
valueField = createValueField(rowInputCell);
}
valueField.typedInput('value',rule.v); valueField.typedInput('value',rule.v);
valueField.typedInput('type',rule.vt||'str');
}
if (rule.case) {
caseSensitive.prop('checked',true);
} else {
caseSensitive.prop('checked',false);
} }
caseSensitive.prop('checked',!!rule.case);
selectField.change(); selectField.change();
var currentOutputs = JSON.parse(outputCount.val()||"{}"); var currentOutputs = JSON.parse(outputCount.val()||"{}");

View File

@@ -115,12 +115,12 @@
var regex = this._("change.label.regex"); var regex = this._("change.label.regex");
var deepCopyLabel = this._("change.label.deepCopy"); var deepCopyLabel = this._("change.label.deepCopy");
function createPropertyValue(row2_1,row2_2) { function createPropertyValue(row2_1, row2_2, defaultType) {
var propValInput = $('<input/>',{class:"node-input-rule-property-value",type:"text"}) var propValInput = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2_1) .appendTo(row2_1)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']}); .typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
var dcLabel = $('<label style="padding-left: 130px; display: flex; align-items: center "></label>').appendTo(row2_2); var dcLabel = $('<label style="padding-left: 130px;"></label>').appendTo(row2_2);
var deepCopy = $('<input type="checkbox" class="node-input-rule-property-deepCopy" style="width: auto; margin: 0 6px 0 0">').appendTo(dcLabel) var deepCopy = $('<input type="checkbox" class="node-input-rule-property-deepCopy" style="width: auto; margin: 0 6px 0 0">').appendTo(dcLabel)
$('<span>').text(deepCopyLabel).appendTo(dcLabel) $('<span>').text(deepCopyLabel).appendTo(dcLabel)
@@ -129,20 +129,20 @@
}) })
return [propValInput, deepCopy]; return [propValInput, deepCopy];
} }
function createFromValue(row3_1) { function createFromValue(row3_1, defaultType) {
return $('<input/>',{class:"node-input-rule-property-search-value",type:"text"}) return $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
.appendTo(row3_1) .appendTo(row3_1)
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']}); .typedInput({default:defaultType||'str',types:['msg','flow','global','str','re','num','bool','env']});
} }
function createToValue(row3_2) { function createToValue(row3_2, defaultType) {
return $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"}) return $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
.appendTo(row3_2) .appendTo(row3_2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']}); .typedInput({default:defaultType||'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
} }
function createMoveValue(row4) { function createMoveValue(row4, defaultType) {
return $('<input/>',{class:"node-input-rule-property-move-value",type:"text"}) return $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
.appendTo(row4) .appendTo(row4)
.typedInput({default:'msg',types:['msg','flow','global']}); .typedInput({default:defaultType||'msg',types:['msg','flow','global']});
} }
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({ $('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
@@ -268,33 +268,22 @@
propertyName.typedInput('value',rule.p); propertyName.typedInput('value',rule.p);
propertyName.typedInput('type',rule.pt); propertyName.typedInput('type',rule.pt);
if (rule.t == "set") { if (rule.t == "set") {
if(!propertyValue) { var parts = createPropertyValue(row2_1, row2_2, rule.tot);
var parts = createPropertyValue(row2_1, row2_2); propertyValue = parts[0];
propertyValue = parts[0]; deepCopy = parts[1];
deepCopy = parts[1];
}
propertyValue.typedInput('value',rule.to); propertyValue.typedInput('value',rule.to);
propertyValue.typedInput('type',rule.tot);
deepCopy.prop("checked", !!rule.dc); deepCopy.prop("checked", !!rule.dc);
} }
if (rule.t == "move") { if (rule.t == "move") {
if(!moveValue) { moveValue = createMoveValue(row4,rule.tot);
moveValue = createMoveValue(row4);
}
moveValue.typedInput('value',rule.to); moveValue.typedInput('value',rule.to);
moveValue.typedInput('type',rule.tot);
} }
if (rule.t == "change") { if (rule.t == "change") {
if(!fromValue) { fromValue = createFromValue(row3_1, rule.fromt);
fromValue = createFromValue(row3_1);
}
fromValue.typedInput('value',rule.from); fromValue.typedInput('value',rule.from);
fromValue.typedInput('type',rule.fromt);
if (!toValue) { toValue = createToValue(row3_2,rule.tot);
toValue = createToValue(row3_2);
}
toValue.typedInput('value',rule.to); toValue.typedInput('value',rule.to);
toValue.typedInput('type',rule.tot);
} }
selectField.change(); selectField.change();
container[0].appendChild(fragment); container[0].appendChild(fragment);

View File

@@ -372,6 +372,7 @@ module.exports = function(RED) {
hit = false; hit = false;
for (var b in node.buffer) { // check if already in queue for (var b in node.buffer) { // check if already in queue
if (msg.topic === node.buffer[b].msg.topic) { if (msg.topic === node.buffer[b].msg.topic) {
if (node.outputs === 2) { send([null,node.buffer[b].msg]) }
node.buffer[b].done(); node.buffer[b].done();
node.buffer[b] = {msg, send, done}; // if so - replace existing entry node.buffer[b] = {msg, send, done}; // if so - replace existing entry
hit = true; hit = true;

View File

@@ -54,6 +54,18 @@
width: 15px; width: 15px;
height: 15px; height: 15px;
} }
.form-row-mqtt5 {
display: none;
}
.form-row-mqtt5.form-row-mqtt5-active:not(.form-row-mqtt-static-disabled) {
display: block
}
.form-row-mqtt-static-disabled {
display: none;
/* opacity: 0.3;
pointer-events: none; */
}
</style> </style>
<script type="text/html" data-template-name="mqtt in"> <script type="text/html" data-template-name="mqtt in">
@@ -62,10 +74,18 @@
<input type="text" id="node-input-broker"> <input type="text" id="node-input-broker">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-topicType" data-i18n="mqtt.label.action"></label>
<select id="node-input-topicType" style="width: 70%">
<option value="topic" data-i18n="mqtt.label.staticTopic"></option>
<option value="dynamic" data-i18n="mqtt.label.dynamicTopic"></option>
</select>
<input type="hidden" id="node-input-inputs">
</div>
<div class="form-row form-row-mqtt-static">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label> <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"> <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div> </div>
<div class="form-row"> <div class="form-row form-row-mqtt-static">
<label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label> <label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-input-qos" style="width:125px !important"> <select id="node-input-qos" style="width:125px !important">
<option value="0">0</option> <option value="0">0</option>
@@ -73,17 +93,7 @@
<option value="2">2</option> <option value="2">2</option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row mqtt-flags-row form-row-mqtt5 form-row-mqtt-static">
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="mqtt.label.output"></span></label>
<select id="node-input-datatype" style="width:70%;">
<option value="auto" data-i18n="mqtt.output.auto"></option>
<option value="buffer" data-i18n="mqtt.output.buffer"></option>
<option value="utf8" data-i18n="mqtt.output.string"></option>
<option value="json" data-i18n="mqtt.output.json"></option>
<option value="base64" data-i18n="mqtt.output.base64"></option>
</select>
</div>
<div class="form-row mqtt-flags-row mqtt5">
<label for="node-input-nl" ><i class="fa fa-flag"></i> <span data-i18n="mqtt.label.flags">Flags</span></label> <label for="node-input-nl" ><i class="fa fa-flag"></i> <span data-i18n="mqtt.label.flags">Flags</span></label>
<div class="mqtt-flags"> <div class="mqtt-flags">
<div class="mqtt-flag"> <div class="mqtt-flag">
@@ -100,7 +110,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-row mqtt5"> <div class="form-row form-row-mqtt5 form-row-mqtt-static">
<label for="node-input-rh" style="width:100%"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.rh"></span></label> <label for="node-input-rh" style="width:100%"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.rh"></span></label>
<select id="node-input-rh" style="margin-left: 104px; width: 70%"> <select id="node-input-rh" style="margin-left: 104px; width: 70%">
<option value="0" data-i18n="mqtt.label.rh0"></option> <option value="0" data-i18n="mqtt.label.rh0"></option>
@@ -108,6 +118,16 @@
<option value="2" data-i18n="mqtt.label.rh2"></option> <option value="2" data-i18n="mqtt.label.rh2"></option>
</select> </select>
</div> </div>
<div class="form-row">
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="mqtt.label.output"></span></label>
<select id="node-input-datatype" style="width:70%;">
<option value="auto" data-i18n="mqtt.output.auto"></option>
<option value="buffer" data-i18n="mqtt.output.buffer"></option>
<option value="utf8" data-i18n="mqtt.output.string"></option>
<option value="json" data-i18n="mqtt.output.json"></option>
<option value="base64" data-i18n="mqtt.output.base64"></option>
</select>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <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"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -185,6 +205,10 @@
<label for="node-config-input-port" style="margin-left:20px; width:43px; "> <span data-i18n="mqtt.label.port"></span></label> <label for="node-config-input-port" style="margin-left:20px; width:43px; "> <span data-i18n="mqtt.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px"> <input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px">
</div> </div>
<div class="form-row" style="margin-bottom:0">
<input type="checkbox" id="node-config-input-autoConnect" style="margin: 0 5px 0 104px; display: inline-block; width: auto;">
<label for="node-config-input-autoConnect" style="width: auto"><span data-i18n="mqtt.label.auto-connect"></span></label>
</div>
<div class="form-row" style="height: 34px;"> <div class="form-row" style="height: 34px;">
<input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label> <label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
@@ -434,6 +458,7 @@
return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0; return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
} }
}}, }},
autoConnect: {value: true},
usetls: {value: false}, usetls: {value: false},
verifyservercert: { value: false}, verifyservercert: { value: false},
compatmode: { value: false}, compatmode: { value: false},
@@ -558,6 +583,10 @@
this.usetls = false; this.usetls = false;
$("#node-config-input-usetls").prop("checked",false); $("#node-config-input-usetls").prop("checked",false);
} }
if (typeof this.autoConnect === 'undefined') {
this.autoConnect = true;
$("#node-config-input-autoConnect").prop("checked",true);
}
if (this.compatmode === 'true' || this.compatmode === true) { if (this.compatmode === 'true' || this.compatmode === true) {
delete this.compatmode; delete this.compatmode;
this.protocolVersion = 4; this.protocolVersion = 4;
@@ -704,11 +733,22 @@
} }
}); });
RED.nodes.registerType('mqtt in',{ RED.nodes.registerType('mqtt in',{
category: 'network', category: 'network',
defaults: { defaults: {
name: {value:""}, name: {value:""},
topic: {value:"",required:true,validate: RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)}, topic: {
value:"",
validate: function(v) {
var isDynamic = this.inputs === 1;
var topicTypeSelect = $("#node-input-topicType");
if (topicTypeSelect.length) {
isDynamic = topicTypeSelect.val()==='dynamic'
}
return isDynamic || ((!!v) && RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)(v));
}
},
qos: {value: "2"}, qos: {value: "2"},
datatype: {value:"auto",required:true}, datatype: {value:"auto",required:true},
broker: {type:"mqtt-broker", required:true}, broker: {type:"mqtt-broker", required:true},
@@ -716,33 +756,64 @@
nl: {value:false}, nl: {value:false},
rap: {value:true}, rap: {value:true},
rh: {value:0}, rh: {value:0},
inputs: {value:0},
}, },
color:"#d8bfd8", color:"#d8bfd8",
inputs:0, inputs:0,
outputs:1, outputs:1,
icon: "bridge.svg", icon: "bridge.svg",
label: function() { label: function() {
return this.name||this.topic||"mqtt"; var label = "mqtt";
if(this.topicType !== "dynamic" && this.topic) {
label = this.topic;
}
return this.name || label;
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
$("#node-input-broker").on("change",function(d){ const node = this;
const isV5Broker = function() {
var confNode = RED.nodes.node($("#node-input-broker").val()); var confNode = RED.nodes.node($("#node-input-broker").val());
var v5 = confNode && confNode.protocolVersion == "5"; return confNode && confNode.protocolVersion === "5";
if(v5) { }
$("div.form-row.mqtt5").show(); const isDynamic = function() {
} else { return $('#node-input-topicType').val() === "dynamic";
$("div.form-row.mqtt5").hide(); }
} const updateVisibility = function() {
var v5 = isV5Broker();
var dynamic = isDynamic();
$("div.form-row-mqtt5").toggleClass("form-row-mqtt5-active",!!v5);
$("div.form-row.form-row-mqtt-static").toggleClass("form-row-mqtt-static-disabled", !!dynamic)
}
$("#node-input-broker").on("change",function(d){
updateVisibility();
}); });
$('#node-input-topicType').on("change", function () {
$("#node-input-inputs").val(isDynamic() ? 1 : 0);
updateVisibility();
});
if (this.inputs === 1) {
$('#node-input-topicType').val('dynamic')
} else {
$('#node-input-topicType').val('topic')
}
$('#node-input-topicType').trigger("change");
if (this.qos === undefined) { if (this.qos === undefined) {
$("#node-input-qos").val("2"); $("#node-input-qos").val("2");
} }
if (this.datatype === undefined) { if (this.datatype === undefined) {
$("#node-input-datatype").val("auto"); $("#node-input-datatype").val("auto");
} }
},
oneditsave: function() {
if ($('#node-input-topicType').val() === "dynamic") {
$('#node-input-topic').val("");
}
} }
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -298,18 +298,14 @@ in your Node-RED user directory (${RED.settings.userDir}).
} }
if (Object.keys(this.credentials).length != 0) { if (Object.keys(this.credentials).length != 0) {
if (this.authType === "basic") { if (this.authType === "basic") {
// Workaround for https://github.com/sindresorhus/got/issues/1169 // Workaround for https://github.com/sindresorhus/got/issues/1169 (fixed in got v12)
var cred = "" // var cred = ""
if (this.credentials.user) { if (this.credentials.user || this.credentials.password) {
// opts.username = this.credentials.user; // cred = `${this.credentials.user}:${this.credentials.password}`;
cred = this.credentials.user opts.headers.Authorization = "Basic " + Buffer.from(`${this.credentials.user}:${this.credentials.password}`).toString("base64");
}
if (this.credentials.password) {
// opts.password = this.credentials.password;
cred += ":" + this.credentials.password
} }
// build own basic auth header // build own basic auth header
opts.headers.Authorization = "Basic " + Buffer.from(cred).toString("base64"); // opts.headers.Authorization = "Basic " + Buffer.from(cred).toString("base64");
} else if (this.authType === "digest") { } else if (this.authType === "digest") {
let digestCreds = this.credentials; let digestCreds = this.credentials;
let sentCreds = false; let sentCreds = false;

View File

@@ -196,6 +196,13 @@
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label> <label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:54%;">
<option value="buffer" data-i18n="tcpin.output.buffer"></option>
<option value="string" data-i18n="tcpin.output.string"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-out"> </label>
<select type="text" id="node-input-out" style="width:54%;"> <select type="text" id="node-input-out" style="width:54%;">
<option value="time" data-i18n="tcpin.return.timeout"></option> <option value="time" data-i18n="tcpin.return.timeout"></option>
<option value="char" data-i18n="tcpin.return.character"></option> <option value="char" data-i18n="tcpin.return.character"></option>
@@ -220,6 +227,7 @@
server: {value:""}, server: {value:""},
port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)}, port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)},
out: {value:"time",required:true}, out: {value:"time",required:true},
ret: {value:"buffer"},
splitc: {value:"0",required:true}, splitc: {value:"0",required:true},
name: {value:""} name: {value:""}
}, },
@@ -234,6 +242,10 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var previous = null; var previous = null;
if ($("#node-input-ret").val() == undefined) {
$("#node-input-ret").val("buffer");
this.ret = "buffer";
}
$("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() { $("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() {
$("#node-input-splitc").show(); $("#node-input-splitc").show();
if (previous === null) { previous = $("#node-input-out").val(); } if (previous === null) { previous = $("#node-input-out").val(); }

View File

@@ -311,7 +311,7 @@ module.exports = function(RED) {
} }
setupTcpClient(); setupTcpClient();
node.on("input", function(msg,nodeSend,nodeDone) { node.on("input", function(msg, nodeSend, nodeDone) {
if (node.connected && msg.payload != null) { if (node.connected && msg.payload != null) {
if (Buffer.isBuffer(msg.payload)) { if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload); client.write(msg.payload);
@@ -444,6 +444,7 @@ module.exports = function(RED) {
this.server = n.server; this.server = n.server;
this.port = Number(n.port); this.port = Number(n.port);
this.out = n.out; this.out = n.out;
this.ret = n.ret || "buffer";
this.splitc = n.splitc; this.splitc = n.splitc;
if (this.out === "immed") { this.splitc = -1; this.out = "time"; } if (this.out === "immed") { this.splitc = -1; this.out = "time"; }
@@ -488,7 +489,7 @@ module.exports = function(RED) {
connected: false, connected: false,
connecting: false connecting: false
}; };
enqueue(clients[connection_id].msgQueue, {msg:msg,nodeSend:nodeSend, nodeDone: nodeDone}); enqueue(clients[connection_id].msgQueue, {msg:msg, nodeSend:nodeSend, nodeDone:nodeDone});
clients[connection_id].lastMsg = msg; clients[connection_id].lastMsg = msg;
if (!clients[connection_id].connecting && !clients[connection_id].connected) { if (!clients[connection_id].connecting && !clients[connection_id].connected) {
@@ -532,8 +533,12 @@ module.exports = function(RED) {
if (node.out === "sit") { // if we are staying connected just send the buffer if (node.out === "sit") { // if we are staying connected just send the buffer
if (clients[connection_id]) { if (clients[connection_id]) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = data; msg.payload = RED.util.cloneMessage(data);
nodeSend(RED.util.cloneMessage(msg)); if (node.ret === "string") {
try { msg.payload = msg.payload.toString(); }
catch(e) { node.error("Failed to create string", msg); }
}
nodeSend(msg);
} }
} }
// else if (node.splitc === 0) { // else if (node.splitc === 0) {
@@ -556,6 +561,10 @@ module.exports = function(RED) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i+1); msg.payload = Buffer.alloc(i+1);
buf.copy(msg.payload,0,0,i+1); buf.copy(msg.payload,0,0,i+1);
if (node.ret === "string") {
try { msg.payload = msg.payload.toString(); }
catch(e) { node.error("Failed to create string", msg); }
}
nodeSend(msg); nodeSend(msg);
if (clients[connection_id].client) { if (clients[connection_id].client) {
node.status({}); node.status({});
@@ -578,6 +587,10 @@ module.exports = function(RED) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i); msg.payload = Buffer.alloc(i);
buf.copy(msg.payload,0,0,i); buf.copy(msg.payload,0,0,i);
if (node.ret === "string") {
try { msg.payload = msg.payload.toString(); }
catch(e) { node.error("Failed to create string", msg); }
}
nodeSend(msg); nodeSend(msg);
if (clients[connection_id].client) { if (clients[connection_id].client) {
node.status({}); node.status({});
@@ -597,6 +610,10 @@ module.exports = function(RED) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = Buffer.alloc(i); msg.payload = Buffer.alloc(i);
buf.copy(msg.payload,0,0,i); buf.copy(msg.payload,0,0,i);
if (node.ret === "string") {
try { msg.payload = msg.payload.toString(); }
catch(e) { node.error("Failed to create string", msg); }
}
nodeSend(msg); nodeSend(msg);
if (clients[connection_id].client) { if (clients[connection_id].client) {
node.status({}); node.status({});

View File

@@ -49,7 +49,11 @@ module.exports = function(RED) {
} }
var value = RED.util.getMessageProperty(msg,node.property); var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) { if (value !== undefined) {
if (typeof value === "string") { if (typeof value === "string" || Buffer.isBuffer(value)) {
// if (Buffer.isBuffer(value) && node.action !== "obj") {
// node.warn(RED._("json.errors.dropped")); done();
// }
// else
if (node.action === "" || node.action === "obj") { if (node.action === "" || node.action === "obj") {
try { try {
RED.util.setMessageProperty(msg,node.property,JSON.parse(value)); RED.util.setMessageProperty(msg,node.property,JSON.parse(value));

View File

@@ -25,7 +25,7 @@
<dd>Optional nutzbare Nachrichten-Eigenschaft.</dd> <dd>Optional nutzbare Nachrichten-Eigenschaft.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>Der <span style="background-color:Gainsboro">inject</span>-Node kann einen Flow mit einstellbaren Nutzdaten (Payload) starten. <p>Der inject-Node kann einen Flow mit einstellbaren Nutzdaten (Payload) starten.
Der voreingestellte Payload ist die aktuelle Zeit als Zeitstempel in Millisekunden Der voreingestellte Payload ist die aktuelle Zeit als Zeitstempel in Millisekunden
seit Beginn der Unix-Zeitrechnung (1. Januar 1970 UTC).</p> seit Beginn der Unix-Zeitrechnung (1. Januar 1970 UTC).</p>
<p>Der Node unterstützt auch die Injektion von Zeichenfolgen, Zahlenwerten, Booleschen Werten, <p>Der Node unterstützt auch die Injektion von Zeichenfolgen, Zahlenwerten, Booleschen Werten,
@@ -34,7 +34,7 @@
Er kann auch in regelmäßigen Intervallen oder nach einem Zeitplan injizieren.</p> Er kann auch in regelmäßigen Intervallen oder nach einem Zeitplan injizieren.</p>
<p>Er kann auch so eingestellt werden, dass er jedes Mal einen Wert injiziert, wenn der Flow gestartet wird.</p> <p>Er kann auch so eingestellt werden, dass er jedes Mal einen Wert injiziert, wenn der Flow gestartet wird.</p>
<p>Das maximal einstellbare Intervall beträgt etwa 596 Stunden bzw. 24 Tage. <p>Das maximal einstellbare Intervall beträgt etwa 596 Stunden bzw. 24 Tage.
Wenn jedoch Intervalle größer als 24h benötigt werden, sollte ein <span style="background-color:Gainsboro">scheduler</span>-Node verwendet werden, Wenn jedoch Intervalle größer als 24h benötigt werden, sollte ein scheduler-Node verwendet werden,
der mit Stromausfällen und Neustarts besser umgehen kann.</p> der mit Stromausfällen und Neustarts besser umgehen kann.</p>
<p><b>Hinweis</b>: Die Optionen <i>"Intervall zwischen Uhrzeiten"</i> und <i>"Täglicher Zeitpunkt"</i> <p><b>Hinweis</b>: Die Optionen <i>"Intervall zwischen Uhrzeiten"</i> und <i>"Täglicher Zeitpunkt"</i>
verwenden das Standard-Cron-System.</p> verwenden das Standard-Cron-System.</p>

View File

@@ -23,10 +23,10 @@
<p>JavaScript-Objekte und -Arrays können nach Bedarf ein- und ausgeblendet werden. <p>JavaScript-Objekte und -Arrays können nach Bedarf ein- und ausgeblendet werden.
Binäre Puffer-Objekte (buffer objects) können nach Möglichkeit als Rohdaten oder als Zeichenfolge (string) angezeigt werden.</p> Binäre Puffer-Objekte (buffer objects) können nach Möglichkeit als Rohdaten oder als Zeichenfolge (string) angezeigt werden.</p>
<p>Neben der eigentlichen Nachricht werden im Debug-Tab auch der Empfangszeitpunkt, <p>Neben der eigentlichen Nachricht werden im Debug-Tab auch der Empfangszeitpunkt,
der empfangende <span style="background-color:Gainsboro">debug</span>-Node, sowie Name und Typ der Nachricht protokolliert. der empfangende debug-Node, sowie Name und Typ der Nachricht protokolliert.
Durch Klicken auf die Node-ID wird der entsprechende <span style="background-color:Gainsboro">debug</span>-Node im Arbeitsbereich angezeigt.</p> Durch Klicken auf die Node-ID wird der entsprechende debug-Node im Arbeitsbereich angezeigt.</p>
<p>Die Schaltfläche des <span style="background-color:Gainsboro">debug</span>-Nodes kann verwendet werden, um die Debug-Ausgabe ein- und auszuschalten. <p>Die Schaltfläche des debug-Nodes kann verwendet werden, um die Debug-Ausgabe ein- und auszuschalten.
Es ist empfehlenswert, alle nicht verwendeten <span style="background-color:Gainsboro">debug</span>-Nodes zu deaktivieren oder gleich zu entfernen.</p> Es ist empfehlenswert, alle nicht verwendeten debug-Nodes zu deaktivieren oder gleich zu entfernen.</p>
<p>Der <span style="background-color:Gainsboro">debug</span>-Node kann auch so eingestellt werden, dass außerdem alle Nachrichten in der Systemkonsole ausgegeben und/oder <p>Der debug-Node kann auch so eingestellt werden, dass außerdem alle Nachrichten in der Systemkonsole ausgegeben und/oder
als kurze Statustexte (max. 32 Zeichen) unter dem <span style="background-color:Gainsboro">debug</span>-Node angezeigt werden.</p> als kurze Statustexte (max. 32 Zeichen) unter dem debug-Node angezeigt werden.</p>
</script> </script>

View File

@@ -18,11 +18,11 @@
<p>Anstoß eines weiteren Flows, wenn ein anderer Node seine Nachrichtenbearbeitung abgeschlossen hat.</p> <p>Anstoß eines weiteren Flows, wenn ein anderer Node seine Nachrichtenbearbeitung abgeschlossen hat.</p>
<h3>Details</h3> <h3>Details</h3>
<p>Wenn ein Node die Bearbeitung seiner Nachrichten abgeschlossen hat, <p>Wenn ein Node die Bearbeitung seiner Nachrichten abgeschlossen hat,
kann der <span style="background-color:Gainsboro">complete</span>-Node dazu benutzt werden, einen weiteren Flow anzustoßen.</p> kann der complete-Node dazu benutzt werden, einen weiteren Flow anzustoßen.</p>
<p>Der Node kann z.B. mit einem anderen Node ohne Ausgang verknüpft werden <p>Der Node kann z.B. mit einem anderen Node ohne Ausgang verknüpft werden
(z.B. E-Mail-Sende-Node), um den Flow fortzusetzen.</p> (z.B. E-Mail-Sende-Node), um den Flow fortzusetzen.</p>
<p>Im Node werden dazu die zu überwachenden Nodes des selben Flows ausgewählt. <p>Im Node werden dazu die zu überwachenden Nodes des selben Flows ausgewählt.
Im Gegensatz zum <span style="background-color:Gainsboro">catch</span>-Node besteht hier jedoch nicht die Auswahlmöglichkeit aller Nodes des Flows.</p> Im Gegensatz zum catch-Node besteht hier jedoch nicht die Auswahlmöglichkeit aller Nodes des Flows.</p>
<p>Nicht alle Nodes können diesen Node anstoßen. <p>Nicht alle Nodes können diesen Node anstoßen.
Es hängt davon ab, ob die auslösenden Knoten diese Funktion unterstützen, Es hängt davon ab, ob die auslösenden Knoten diese Funktion unterstützen,
welche erst mit Node-RED 1.0 eingeführt wurde.</p> welche erst mit Node-RED 1.0 eingeführt wurde.</p>

View File

@@ -29,13 +29,13 @@
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>Wenn ein Node bei der Verarbeitung einer Nachricht einen Fehler verursacht, wird der Flow in der Regel angehalten. <p>Wenn ein Node bei der Verarbeitung einer Nachricht einen Fehler verursacht, wird der Flow in der Regel angehalten.
Der <span style="background-color:Gainsboro">catch</span>-Node kann verwendet werden, um diese Fehler abzufangen und sie mit einem dedizierten Flow zu bearbeiten.</p> Der catch-Node kann verwendet werden, um diese Fehler abzufangen und sie mit einem dedizierten Flow zu bearbeiten.</p>
<p>Der Node fängt standardmäßig die Fehler aller Nodes im selben Flow ab. <p>Der Node fängt standardmäßig die Fehler aller Nodes im selben Flow ab.
Alternativ kann er auch an bestimmte Nodes gebunden werden.</p> Alternativ kann er auch an bestimmte Nodes gebunden werden.</p>
<p>Wenn ein Fehler ausgelöst wird, empfangen alle angebundenen <span style="background-color:Gainsboro">catch</span>-Nodes die Fehlermeldung.</p> <p>Wenn ein Fehler ausgelöst wird, empfangen alle angebundenen catch-Nodes die Fehlermeldung.</p>
<p>Wenn ein Fehler in einem Subflow ausgelöst wird, wird der Fehler von einem <span style="background-color:Gainsboro">catch</span>-Node <p>Wenn ein Fehler in einem Subflow ausgelöst wird, wird der Fehler von einem catch-Node
innerhalb des Subflows abgefangen. innerhalb des Subflows abgefangen.
Wenn im Subflow keine <span style="background-color:Gainsboro">catch</span>-Nodes vorhanden sind, wird die Fehlermeldung eine Ebene höher zum Flow weitergereicht, Wenn im Subflow keine catch-Nodes vorhanden sind, wird die Fehlermeldung eine Ebene höher zum Flow weitergereicht,
in der sich die Subflow-Instanz befindet.</p> in der sich die Subflow-Instanz befindet.</p>
<p>Wenn die Nachricht bereits über eine <code>error</code>-Eigenschaft verfügt, wird sie nach <code>_error</code> kopiert.</p> <p>Wenn die Nachricht bereits über eine <code>error</code>-Eigenschaft verfügt, wird sie nach <code>_error</code> kopiert.</p>
</script> </script>

View File

@@ -17,7 +17,7 @@
<script type="text/html" data-help-name="link in"> <script type="text/html" data-help-name="link in">
<p>Erstellung virtueller Verbindungen (Links) zwischen Flows.</p> <p>Erstellung virtueller Verbindungen (Links) zwischen Flows.</p>
<h3>Details</h3> <h3>Details</h3>
<p>Der Node kann mit jedem beliebigen <span style="background-color:Gainsboro">link&nbsp;out</span>-Node in einen beliebigen Flow-Tab verlinkt werden. <p>Der Node kann mit jedem beliebigen link&nbsp;out-Node in einen beliebigen Flow-Tab verlinkt werden.
Sobald sie verlinkt sind, verhalten sie sich so, als wären sie direkt miteinander verbunden.</p> Sobald sie verlinkt sind, verhalten sie sich so, als wären sie direkt miteinander verbunden.</p>
<p>Die Links zwischen Link-Nodes werden nur angezeigt, wenn ein Link-Node ausgewählt ist. <p>Die Links zwischen Link-Nodes werden nur angezeigt, wenn ein Link-Node ausgewählt ist.
Wenn Links zu anderen Flow-Tabs vorhanden sind, werden virtuelle Link-Nodes als Gegenparts angezeigt, Wenn Links zu anderen Flow-Tabs vorhanden sind, werden virtuelle Link-Nodes als Gegenparts angezeigt,
@@ -28,7 +28,7 @@
<script type="text/html" data-help-name="link out"> <script type="text/html" data-help-name="link out">
<p>Erstellung virtueller Verbindungen (Links) zwischen Flows.</p> <p>Erstellung virtueller Verbindungen (Links) zwischen Flows.</p>
<h3>Details</h3> <h3>Details</h3>
<p>Der Node kann mit jedem beliebigen <span style="background-color:Gainsboro">link&nbsp;in</span>-Node in einen beliebigen Flow-Tab verlinkt werden. <p>Der Node kann mit jedem beliebigen link&nbsp;in-Node in einen beliebigen Flow-Tab verlinkt werden.
Sobald sie verlinkt sind, verhalten sie sich so, als wären sie direkt miteinander verbunden.</p> Sobald sie verlinkt sind, verhalten sie sich so, als wären sie direkt miteinander verbunden.</p>
<p>Die Links zwischen Link-Nodes werden nur angezeigt, wenn ein Link-Node ausgewählt ist. <p>Die Links zwischen Link-Nodes werden nur angezeigt, wenn ein Link-Node ausgewählt ist.
Wenn Links zu anderen Flow-Tabs vorhanden sind, werden virtuelle Link-Nodes als Gegenparts angezeigt, Wenn Links zu anderen Flow-Tabs vorhanden sind, werden virtuelle Link-Nodes als Gegenparts angezeigt,

View File

@@ -47,7 +47,7 @@
<li><code>node.error("Fehlermeldungstext")</code></li> <li><code>node.error("Fehlermeldungstext")</code></li>
</ul> </ul>
</p> </p>
<p>Der <span style="background-color:Gainsboro">catch</span>-Node kann auch zur Bearbeitung von Fehlern verwendet werden. <p>Der catch-Node kann auch zur Bearbeitung von Fehlern verwendet werden.
Er wird aufgerufen, indem <code>msg</code> als zweites Argument an <code>node.error</code> übergeben wird:</p> Er wird aufgerufen, indem <code>msg</code> als zweites Argument an <code>node.error</code> übergeben wird:</p>
<pre>node.error("Fehlermeldungstext" ,msg);</pre> <pre>node.error("Fehlermeldungstext" ,msg);</pre>
<h4><b>Zugriff auf Node-Informationen</b></h4> <h4><b>Zugriff auf Node-Informationen</b></h4>

View File

@@ -27,7 +27,7 @@
<ol> <ol>
<li><b>value rules</b>: Regeln werden hinsichtlich einer eingestellten Eigenschaft ausgewertet</li> <li><b>value rules</b>: Regeln werden hinsichtlich einer eingestellten Eigenschaft ausgewertet</li>
<li><b>sequence rules</b>: Regeln beziehen sich auf Nachrichtensequenzen, <li><b>sequence rules</b>: Regeln beziehen sich auf Nachrichtensequenzen,
wie sie beispielsweise durch den <span style="background-color:Gainsboro">split</span>-Node erzeugt werden</li> wie sie beispielsweise durch den split-Node erzeugt werden</li>
<li>Ein <b>JSONata-Ausdruck</b> kann die gesamte Eingangsnachricht auswerten und einen <code>true</code>-Wert zurückliefern, <li>Ein <b>JSONata-Ausdruck</b> kann die gesamte Eingangsnachricht auswerten und einen <code>true</code>-Wert zurückliefern,
um eine Regelerfüllung zu signalisieren</li> um eine Regelerfüllung zu signalisieren</li>
<li>Die <b>ansonsten</b>-Regel wird angewendet, wenn keine vorhergehende Regel übereinstimmt</li> <li>Die <b>ansonsten</b>-Regel wird angewendet, wenn keine vorhergehende Regel übereinstimmt</li>

View File

@@ -25,9 +25,9 @@
<dt class="optional">payload <span class="property-type">string</span></dt> <dt class="optional">payload <span class="property-type">string</span></dt>
<dd>Wird an auszuführenden Befehl angehängt, sofern im Node aktiviert.</dd> <dd>Wird an auszuführenden Befehl angehängt, sofern im Node aktiviert.</dd>
<dt class="optional">kill <span class="property-type">string</span></dt> <dt class="optional">kill <span class="property-type">string</span></dt>
<dd>Typ des Kill-Signals, das an den zu beendenden <span style="background-color:Gainsboro">exec</span>-Node-Prozess gesendet wird.</dd> <dd>Typ des Kill-Signals, das an den zu beendenden exec-Node-Prozess gesendet wird.</dd>
<dt class="optional">pid <span class="property-type">number | string</span></dt> <dt class="optional">pid <span class="property-type">number | string</span></dt>
<dd>Prozess-ID des zu beendenden <span style="background-color:Gainsboro">exec</span>-Node-Prozesses.</dd> <dd>Prozess-ID des zu beendenden exec-Node-Prozesses.</dd>
</dl> </dl>
<h3>Ausgangsdaten</h3> <h3>Ausgangsdaten</h3>
<ol class="node-ports"> <ol class="node-ports">
@@ -75,7 +75,7 @@
<p>Die zurückgegebenen Daten (Payload) sind in der Regel eine <i>Zeichenfolge (string)</i>, <p>Die zurückgegebenen Daten (Payload) sind in der Regel eine <i>Zeichenfolge (string)</i>,
außer es werden nicht UTF-8-Zeichen wie bei einem <i>binären Puffer (buffer)</i> erkannt.</p> außer es werden nicht UTF-8-Zeichen wie bei einem <i>binären Puffer (buffer)</i> erkannt.</p>
<p>Bei einem aktiven Node werden Status und die PID angezeigt. <p>Bei einem aktiven Node werden Status und die PID angezeigt.
Änderungen können mittels <span style="background-color:Gainsboro">status</span>-Node gelesen werden.</p> Änderungen können mittels status-Node gelesen werden.</p>
<h4><b>Prozesse beenden</b></h4> <h4><b>Prozesse beenden</b></h4>
<p>Durch Senden von <code>msg.kill</code> wird ein einzelner aktiver Prozess beendet. <p>Durch Senden von <code>msg.kill</code> wird ein einzelner aktiver Prozess beendet.
<code>msg.kill</code> sollte als Zeichenfolge (string) den Signaltyp enthalten, <code>msg.kill</code> sollte als Zeichenfolge (string) den Signaltyp enthalten,

View File

@@ -94,7 +94,7 @@
"label": { "label": {
"source": "Fehler abfangen von", "source": "Fehler abfangen von",
"selectAll": "Alles auswählen", "selectAll": "Alles auswählen",
"uncaught": "Fehler ignorieren, die von anderen <span style=\"background-color:Gainsboro\">catch</span>-Nodes behandelt wurden" "uncaught": "Fehler ignorieren, die von anderen catch-Nodes behandelt wurden"
}, },
"scope": { "scope": {
"all": "allen Nodes", "all": "allen Nodes",
@@ -475,12 +475,12 @@
"json": "Ein parsed JSON-Objekt", "json": "Ein parsed JSON-Objekt",
"tip": { "tip": {
"in": "Die URL ist relativ zu ", "in": "Die URL ist relativ zu ",
"res": "Die an diesen Node gesendeten Nachrichten <b>müssen</b> von einem <span style=\"background-color:Gainsboro\">http&nbsp;in</span>-Node stammen", "res": "Die an diesen Node gesendeten Nachrichten <b>müssen</b> von einem http&nbsp;in-Node stammen",
"req": "Tipp: Wenn die JSON-Syntax-Analyse fehlschlägt, wird die abgerufene Zeichenfolge zurückgegeben, wie sie ist." "req": "Tipp: Wenn die JSON-Syntax-Analyse fehlschlägt, wird die abgerufene Zeichenfolge zurückgegeben, wie sie ist."
}, },
"httpreq": "http request", "httpreq": "http request",
"errors": { "errors": {
"not-created": "<span style=\"background-color:Gainsboro\">http in</span>-Node kann nicht erstellt werden, wenn httpNodeRoot auf 'false' gesetzt ist.", "not-created": "http in-Node kann nicht erstellt werden, wenn httpNodeRoot auf 'false' gesetzt ist.",
"missing-path": "Fehlender Pfad", "missing-path": "Fehlender Pfad",
"no-response": "Kein Antwort-Objekt", "no-response": "Kein Antwort-Objekt",
"json-error": "JSON-Parse-Fehler", "json-error": "JSON-Parse-Fehler",
@@ -684,7 +684,7 @@
}, },
"errors": { "errors": {
"invalid-expr": "Ungültiger JSONata-Ausdruck: __error__", "invalid-expr": "Ungültiger JSONata-Ausdruck: __error__",
"too-many": "Zu viele anstehende Nachrichten im <span style=\"background-color:Gainsboro\">switch</span>-Node" "too-many": "Zu viele anstehende Nachrichten im switch-Node"
} }
}, },
"change": { "change": {
@@ -851,7 +851,6 @@
"outputas": "Ausgabe", "outputas": "Ausgabe",
"breakchunks": "In Chunks aufteilen", "breakchunks": "In Chunks aufteilen",
"breaklines": "In Linien aufteilen", "breaklines": "In Linien aufteilen",
"filelabel": "file",
"sendError": "Nachricht bei Fehler senden (herkömmlicher Modus)", "sendError": "Nachricht bei Fehler senden (herkömmlicher Modus)",
"encoding": "Kodierung", "encoding": "Kodierung",
"deletelabel": "lösche __file__", "deletelabel": "lösche __file__",
@@ -940,8 +939,8 @@
"afterTimeout": "Bei Zeitablauf nach erster Nachricht von", "afterTimeout": "Bei Zeitablauf nach erster Nachricht von",
"seconds": "Sekunden", "seconds": "Sekunden",
"complete": "Nach Nachricht mit <code>msg.complete</code>-Eigenschaft", "complete": "Nach Nachricht mit <code>msg.complete</code>-Eigenschaft",
"tip": "Dieser Modus setzt voraus, dass dieser Node entweder mit einem <span style=\"background-color:Gainsboro\">split</span>-Node kombiniert ist oder dass die empfangenen Nachrichten über eine ordnungsgemäß konfigurierte <code>msg.parts</code>-Eigenschaft verfügen.", "tip": "Dieser Modus setzt voraus, dass dieser Node entweder mit einem split-Node kombiniert ist oder dass die empfangenen Nachrichten über eine ordnungsgemäß konfigurierte <code>msg.parts</code>-Eigenschaft verfügen.",
"too-many": "Zu viele anstehende Nachrichten im <span style=\"background-color:Gainsboro\">join</span>-Node", "too-many": "Zu viele anstehende Nachrichten im join-Node",
"merge": { "merge": {
"topics-label": "Zusammengeführte Topics", "topics-label": "Zusammengeführte Topics",
"topics": "Topics", "topics": "Topics",
@@ -970,9 +969,9 @@
"ascending": "aufsteigend", "ascending": "aufsteigend",
"descending": "absteigend", "descending": "absteigend",
"as-number": "als Zahlenwert", "as-number": "als Zahlenwert",
"invalid-exp": "Ungültiger JSONata-Ausdruck in <span style=\"background-color:Gainsboro\">sort</span>-Node: __message__", "invalid-exp": "Ungültiger JSONata-Ausdruck in sort-Node: __message__",
"too-many": "Zu viele anstehende Nachrichten in <span style=\"background-color:Gainsboro\">sort</span>-Node", "too-many": "Zu viele anstehende Nachrichten in sort-Node",
"clear": "Anstehende Nachricht in <span style=\"background-color:Gainsboro\">sort</span>-Node löschen" "clear": "Anstehende Nachricht in sort-Node löschen"
}, },
"batch": { "batch": {
"batch": "batch", "batch": "batch",
@@ -997,7 +996,7 @@
"topics-label": "Topics", "topics-label": "Topics",
"topic": "Topic" "topic": "Topic"
}, },
"too-many": "Zu viele anstehende Nachrichten im <span style=\"background-color:Gainsboro\">batch</span>-Node", "too-many": "Zu viele anstehende Nachrichten im batch-Node",
"unexpected": "Unerwarteter Modus", "unexpected": "Unerwarteter Modus",
"no-parts": "Keine parts-Eigenschaft in Nachricht" "no-parts": "Keine parts-Eigenschaft in Nachricht"
}, },

View File

@@ -94,8 +94,8 @@
<script type="text/html" data-help-name="mqtt-broker"> <script type="text/html" data-help-name="mqtt-broker">
<p>Konfiguration der Verbindung zu einem MQTT-Broker.</p> <p>Konfiguration der Verbindung zu einem MQTT-Broker.</p>
<p>Diese Konfiguration erstellt eine einzelne Verbindung zu einem Broker, <p>Diese Konfiguration erstellt eine einzelne Verbindung zu einem Broker,
welche anschließend von den <span style="background-color:Gainsboro">mqtt&nbsp;in</span>- und welche anschließend von den mqtt&nbsp;in- und
<span style="background-color:Gainsboro">mqtt out</span>-Nodes verwendet werden.</p> mqtt out-Nodes verwendet werden.</p>
<p>Der Node generiert eine beliebige Client-ID, falls sie nicht vorgegeben ist und der <p>Der Node generiert eine beliebige Client-ID, falls sie nicht vorgegeben ist und der
Node eine bereinigte Sitzung (clean session) verwenden soll. Node eine bereinigte Sitzung (clean session) verwenden soll.
Wenn eine Client-ID vorgegeben wird, muss sie für den Broker eindeutig sein, zu dem die Verbindung hergestellt werden soll.</p> Wenn eine Client-ID vorgegeben wird, muss sie für den Broker eindeutig sein, zu dem die Verbindung hergestellt werden soll.</p>

View File

@@ -36,7 +36,7 @@
<dt>res <span class="property-type">object</span></dt> <dt>res <span class="property-type">object</span></dt>
<dd>HTTP-Antwortobjekt.<br/> <dd>HTTP-Antwortobjekt.<br/>
Diese Eigenschaft sollte nicht direkt verwendet werden. Diese Eigenschaft sollte nicht direkt verwendet werden.
Im <span style="background-color:Gainsboro">http&nbsp;response</span>-Node ist dokumentiert, wie auf eine Anforderung reagiert wird. Im http&nbsp;response-Node ist dokumentiert, wie auf eine Anforderung reagiert wird.
Diese Eigenschaft muss an der Nachricht angehängt bleiben, die an den Antwort-Node übergeben wird.</dd> Diese Eigenschaft muss an der Nachricht angehängt bleiben, die an den Antwort-Node übergeben wird.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
@@ -50,12 +50,12 @@
<p>Wenn der Inhaltstyp der Anforderung ermittelt werden kann, wird der Hauptteil als passender Typ analysiert. <p>Wenn der Inhaltstyp der Anforderung ermittelt werden kann, wird der Hauptteil als passender Typ analysiert.
Z.B. <code>application/json</code> wird zu einem JavaScript-Objekt analysiert.</p> Z.B. <code>application/json</code> wird zu einem JavaScript-Objekt analysiert.</p>
<p><b>Hinweis</b>: Dieser Node sendet keine Antwort an die Anforderung. <p><b>Hinweis</b>: Dieser Node sendet keine Antwort an die Anforderung.
Der Flow muss einen <span style="background-color:Gainsboro">http&nbsp;response</span>-Node enthalten, Der Flow muss einen http&nbsp;response-Node enthalten,
um die Anforderung zu vervollständigen.</p> um die Anforderung zu vervollständigen.</p>
</script> </script>
<script type="text/html" data-help-name="http response"> <script type="text/html" data-help-name="http response">
<p>Senden von Antworten auf Anforderungen, die von einem <span style="background-color:Gainsboro">http&nbsp;in</span>-Node empfangen wurden.</p> <p>Senden von Antworten auf Anforderungen, die von einem http&nbsp;in-Node empfangen wurden.</p>
<h3>Eingangsdaten</h3> <h3>Eingangsdaten</h3>
<dl class="message-properties"> <dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt> <dt>payload <span class="property-type">string</span></dt>

View File

@@ -34,17 +34,17 @@
</ul> </ul>
</dd> </dd>
<dt>schemaError <span class="property-type">array</span></dt> <dt>schemaError <span class="property-type">array</span></dt>
<dd>Wenn die JSON-Schema-Validierung fehlschlägt, wird für den <span style="background-color:Gainsboro">catch</span>-Node eine <code>schemaError</code>-Eigenschaft erstellt, <dd>Wenn die JSON-Schema-Validierung fehlschlägt, wird für den catch-Node eine <code>schemaError</code>-Eigenschaft erstellt,
die ein Array von Fehlern enthält.</dd> die ein Array von Fehlern enthält.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>Standardmäßig verarbeitet der Node <code>msg.payload</code>, <p>Standardmäßig verarbeitet der Node <code>msg.payload</code>,
kann aber auch eine beliebige Nachrichteneigenschaft konvertieren.</p> kann aber auch eine beliebige Nachrichteneigenschaft konvertieren.</p>
<p>Die Konvertierungsrichtung kann im Node auch vorgegeben werden, um eine bestimmte Ziel-Kodierung sicherzustellen. <p>Die Konvertierungsrichtung kann im Node auch vorgegeben werden, um eine bestimmte Ziel-Kodierung sicherzustellen.
Dies kann z.B. zusammen mit dem <span style="background-color:Gainsboro">http&nbsp;in</span>-Node benutzt werden, um sicherzustellen, Dies kann z.B. zusammen mit dem http&nbsp;in-Node benutzt werden, um sicherzustellen,
dass die Nutzdaten (Payload) ein analysiertes (parsed) Objekt ist, dass die Nutzdaten (Payload) ein analysiertes (parsed) Objekt ist,
auch wenn eine eingehende Anfrage seinen Inhaltstyp nicht korrekt eingestellt hat, auch wenn eine eingehende Anfrage seinen Inhaltstyp nicht korrekt eingestellt hat,
damit der <span style="background-color:Gainsboro">http&nbsp;in</span>-Node die Konvertierung durchführen kann.</p> damit der http&nbsp;in-Node die Konvertierung durchführen kann.</p>
<p>Wenn der Node auf Zeichenfolgen-Eingang (string) eingestellt ist und es einen String empfängt, <p>Wenn der Node auf Zeichenfolgen-Eingang (string) eingestellt ist und es einen String empfängt,
werden keine weiteren Prüfungen der Eigenschaft durchgeführt. werden keine weiteren Prüfungen der Eigenschaft durchgeführt.
Der Node prüft weder, ob die Zeichenfolge (string) ein gültiges JSON enthält, noch wird er ihn neu formatieren, Der Node prüft weder, ob die Zeichenfolge (string) ein gültiges JSON enthält, noch wird er ihn neu formatieren,

View File

@@ -32,7 +32,7 @@
<dl class="message-properties"> <dl class="message-properties">
<dt>parts <span class="property-type">object</span></dt> <dt>parts <span class="property-type">object</span></dt>
<dd>Diese Eigenschaft enthält Informationen darüber, wie die Nachricht von der ursprünglichen Nachricht abgeteilt wurde. <dd>Diese Eigenschaft enthält Informationen darüber, wie die Nachricht von der ursprünglichen Nachricht abgeteilt wurde.
Bei Übergabe an ein <span style="background-color:Gainsboro">join</span>-Node kann die Sequenz wieder zu einer einzigen Nachricht zusammengeführt werden. Bei Übergabe an ein join-Node kann die Sequenz wieder zu einer einzigen Nachricht zusammengeführt werden.
Diese Eigenschaft hat die folgenden Eigenschaften: Diese Eigenschaft hat die folgenden Eigenschaften:
<ul> <ul>
<li><code>id</code>: Identifikator der Nachrichten-Gruppe</li> <li><code>id</code>: Identifikator der Nachrichten-Gruppe</li>
@@ -48,7 +48,7 @@
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>Dieser Node macht es einfach, einen Flow zu erstellen, der gemeinsame Aktionen über eine Sequenz von Nachrichten ausführt, <p>Dieser Node macht es einfach, einen Flow zu erstellen, der gemeinsame Aktionen über eine Sequenz von Nachrichten ausführt,
bevor die Sequenz mittels <span style="background-color:Gainsboro">join</span>-Node wieder zu einer einzigen Nachricht neu kombiniert wird.</p> bevor die Sequenz mittels join-Node wieder zu einer einzigen Nachricht neu kombiniert wird.</p>
<p>Der Node verwendet die <code>msg.parts</code>-Eigenschaft, um die einzelnen Sequenzteile nachzuverfolgen.</p> <p>Der Node verwendet die <code>msg.parts</code>-Eigenschaft, um die einzelnen Sequenzteile nachzuverfolgen.</p>
<h4><b>Streaming-Modus</b></h4> <h4><b>Streaming-Modus</b></h4>
<p>Der Node kann auch zum Aufbereiten eines Nachrichtenstroms verwendet werden. <p>Der Node kann auch zum Aufbereiten eines Nachrichtenstroms verwendet werden.
@@ -59,7 +59,7 @@
so wird es im Node aufbewahrt und der nächsten empfangenen Nachricht vorangestellt.</p> so wird es im Node aufbewahrt und der nächsten empfangenen Nachricht vorangestellt.</p>
<p>In diesem Modus wird die <code>msg.parts.count</code>-Eigenschaft nicht gesetzt, <p>In diesem Modus wird die <code>msg.parts.count</code>-Eigenschaft nicht gesetzt,
da die Anzahl der zu erwartenden Nachrichten im Stream unbekannt ist. da die Anzahl der zu erwartenden Nachrichten im Stream unbekannt ist.
Das bedeutet, dass ein nachfolgender <span style="background-color:Gainsboro">join</span>-Node nicht im Automatikmodus verwendet werden kann.</p> Das bedeutet, dass ein nachfolgender join-Node nicht im Automatikmodus verwendet werden kann.</p>
</script> </script>
<script type="text/html" data-help-name="join"> <script type="text/html" data-help-name="join">
@@ -67,7 +67,7 @@
<p>Es sind drei Modi verfügbar:</p> <p>Es sind drei Modi verfügbar:</p>
<dl> <dl>
<dt>Automatisch</dt> <dt>Automatisch</dt>
<dd>In Kombination mit dem <span style="background-color:Gainsboro">split</span>-Node verbindet es automatisch die Nachrichten, um die zuvor durchgeführte Aufteilung rückgängig zu machen.</dd> <dd>In Kombination mit dem split-Node verbindet es automatisch die Nachrichten, um die zuvor durchgeführte Aufteilung rückgängig zu machen.</dd>
<dt>Manuell</dt> <dt>Manuell</dt>
<dd>Die Nachrichtensequenzen können auf verschiedene Weisen verbunden werden.</dd> <dd>Die Nachrichtensequenzen können auf verschiedene Weisen verbunden werden.</dd>
<dt>Sequenz reduzieren</dt> <dt>Sequenz reduzieren</dt>
@@ -77,7 +77,7 @@
<dl class="message-properties"> <dl class="message-properties">
<dt class="optional">parts <span class="property-type">object</span></dt> <dt class="optional">parts <span class="property-type">object</span></dt>
<dd>Zur automatischen Verbindung einer Nachrichtensequenz sollten alle über diese Eigenschaft verfügen. <dd>Zur automatischen Verbindung einer Nachrichtensequenz sollten alle über diese Eigenschaft verfügen.
Der <span style="background-color:Gainsboro">split</span>-Node erzeugt diese Eigenschaft, sie kann aber auch manuell erstellt werden. Der split-Node erzeugt diese Eigenschaft, sie kann aber auch manuell erstellt werden.
Sie hat die folgenden Eigenschaften: Sie hat die folgenden Eigenschaften:
<ul> <ul>
<li><code>id</code>: Identifikator der Nachrichten-Gruppe</li> <li><code>id</code>: Identifikator der Nachrichten-Gruppe</li>
@@ -98,7 +98,7 @@
<h4><b>Automatischer Modus</b></h4> <h4><b>Automatischer Modus</b></h4>
<p>Der automatische Modus verwendet die <code>parts</code>-Eigenschaften der eingehenden Nachrichten, <p>Der automatische Modus verwendet die <code>parts</code>-Eigenschaften der eingehenden Nachrichten,
um die Sequenz in richtiger Reihenfolge zu verknüpften. um die Sequenz in richtiger Reihenfolge zu verknüpften.
Dies ermöglicht die Aufteilung des <span style="background-color:Gainsboro">split</span>-Nodes automatisch rückgängig zu machen.</p> Dies ermöglicht die Aufteilung des split-Nodes automatisch rückgängig zu machen.</p>
<h4><b>Manueller Modus</b></h4> <h4><b>Manueller Modus</b></h4>
<p>Im manuellen Modus werden Nachrichtensequenzen auf verschiedenen Arten zusammengefügt:</p> <p>Im manuellen Modus werden Nachrichtensequenzen auf verschiedenen Arten zusammengefügt:</p>

View File

@@ -26,8 +26,8 @@
<p>Für Zahlenwerte kann die numerische Sortierreihenfolge festgelegt werden.</p> <p>Für Zahlenwerte kann die numerische Sortierreihenfolge festgelegt werden.</p>
<p>Der Sortierschlüssel kann ein Elementwert oder ein JSONata-Ausdruck beim Sortieren einer Nachrichteneigenschaft <p>Der Sortierschlüssel kann ein Elementwert oder ein JSONata-Ausdruck beim Sortieren einer Nachrichteneigenschaft
bzw. eine Nachrichteneigenschaft oder ein JSONata-Ausdruck beim Sortieren einer Nachrichtensequenz sein.<p> bzw. eine Nachrichteneigenschaft oder ein JSONata-Ausdruck beim Sortieren einer Nachrichtensequenz sein.<p>
<p>Zum Sortieren einer Nachrichtensequenz benötigt der <span style="background-color:Gainsboro">sort</span>-Node die gesetzte <code>msg.parts</code>-Eigenschaft bei den empfangenen Nachrichten. <p>Zum Sortieren einer Nachrichtensequenz benötigt der sort-Node die gesetzte <code>msg.parts</code>-Eigenschaft bei den empfangenen Nachrichten.
Diese Eigenschaft wird vom <span style="background-color:Gainsboro">split</span>-Node erzeugt und kann aber auch manuell erzeugt werden. Diese Eigenschaft wird vom split-Node erzeugt und kann aber auch manuell erzeugt werden.
Sie hat die folgenden Eigenschaften:</p> Sie hat die folgenden Eigenschaften:</p>
<p> <p>
<ul> <ul>

View File

@@ -58,5 +58,5 @@
aber typischerweise 64k (Linux/Mac) oder 41k (Windows).</p> aber typischerweise 64k (Linux/Mac) oder 41k (Windows).</p>
<p>Bei Aufteilung in mehrere Nachrichten besitzt jede eine <code>parts</code>-Eigenschaft, <p>Bei Aufteilung in mehrere Nachrichten besitzt jede eine <code>parts</code>-Eigenschaft,
welche eine komplette Nachrichten-Sequenz bildet.</p> welche eine komplette Nachrichten-Sequenz bildet.</p>
<p>Fehler sollten mittels <span style="background-color:Gainsboro">catch</span>-Nodes abgefangen und behandelt werden.</p> <p>Fehler sollten mittels catch-Nodes abgefangen und behandelt werden.</p>
</script> </script>

View File

@@ -143,12 +143,17 @@
"filterSelected": "selected nodes", "filterSelected": "selected nodes",
"filterCurrent": "current flow", "filterCurrent": "current flow",
"debugNodes": "Debug nodes", "debugNodes": "Debug nodes",
"clearLog": "Clear log", "clearLog": "Clear messages",
"filterLog": "Filter log", "clearFilteredLog": "Clear filtered messages",
"filterLog": "Filter messages",
"openWindow": "Open in new window", "openWindow": "Open in new window",
"copyPath": "Copy path", "copyPath": "Copy path",
"copyPayload": "Copy value", "copyPayload": "Copy value",
"pinPath": "Pin open" "pinPath": "Pin open",
"selectAll": "select all",
"selectNone": "select none",
"all": "all",
"filtered": "filtered"
}, },
"messageMenu": { "messageMenu": {
"collapseAll": "Collapse all paths", "collapseAll": "Collapse all paths",
@@ -411,7 +416,11 @@
"maximumPacketSize": "Max Packet Size", "maximumPacketSize": "Max Packet Size",
"receiveMaximum": "Receive Max", "receiveMaximum": "Receive Max",
"session": "Session", "session": "Session",
"delay": "Delay" "delay": "Delay",
"action": "Action",
"staticTopic": "Subscribe to single topic",
"dynamicTopic": "Dynamic subscription",
"auto-connect": "Connect automatically"
}, },
"sections-label":{ "sections-label":{
"birth-message": "Message sent on connection (birth message)", "birth-message": "Message sent on connection (birth message)",
@@ -452,7 +461,10 @@
"invalid-topic": "Invalid topic specified", "invalid-topic": "Invalid topic specified",
"nonclean-missingclientid": "No client ID set, using clean session", "nonclean-missingclientid": "No client ID set, using clean session",
"invalid-json-string": "Invalid JSON string", "invalid-json-string": "Invalid JSON string",
"invalid-json-parse": "Failed to parse JSON string" "invalid-json-parse": "Failed to parse JSON string",
"invalid-action-action": "Invalid action specified",
"invalid-action-alreadyconnected": "Disconnect from broker before connecting",
"invalid-action-badsubscription": "msg.topic is missing or invalid"
} }
}, },
"httpin": { "httpin": {

View File

@@ -40,6 +40,38 @@
<p>This node requires a connection to a MQTT broker to be configured. This is configured by clicking <p>This node requires a connection to a MQTT broker to be configured. This is configured by clicking
the pencil icon.</p> the pencil icon.</p>
<p>Several MQTT nodes (in or out) can share the same broker connection if required.</p> <p>Several MQTT nodes (in or out) can share the same broker connection if required.</p>
<h4>Dynamic Subscription</h4>
The node can be configured to dynamically control the MQTT connection and its subscriptions. When
enabled, the node will have an input and can be controlled by passing it messages.
<h3>Inputs</h3>
<p>These only apply when the node has been configured for dynamic subscriptions.</p>
<dl class="message-properties">
<dt>action <span class="property-type">string</span></dt>
<dd>the name of the action the node should perform. Available actions are: <code>"connect"</code>,
<code>"disconnect"</code>, <code>"subscribe"</code> and <code>"unsubscribe"</code>.</dd>
<dt class="optional">topic <span class="property-type">string|object|array</span></dt>
<dd>For the <code>"subscribe"</code> and <code>"unsubscribe"</code> actions, this property
provides the topic. It can be set as either:<ul>
<li>a String continaing the topic filter</li>
<li>an Object containing <code>topic</code> and <code>qos</code> properties</li>
<li>an array of either strings or objects to handle multiple topics in one</li>
</ul>
</dd>
<dt class="optional">broker <span class="property-type">broker</span> </dt>
<dd>For the <code>"connect"</code> action, this property can override any
of the individual broker configuration settings, including: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - overrides broker/port to provide a complete connection url</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>If this property is set and the broker is already connected an error
will be logged unless it has the <code>force</code> property set - in which case it will
disconnect from the broker, apply the new settings and reconnect.</p>
</dd>
</dl>
</script> </script>
<script type="text/html" data-help-name="mqtt out"> <script type="text/html" data-help-name="mqtt out">
@@ -78,6 +110,30 @@
<p>This node requires a connection to a MQTT broker to be configured. This is configured by clicking <p>This node requires a connection to a MQTT broker to be configured. This is configured by clicking
the pencil icon.</p> the pencil icon.</p>
<p>Several MQTT nodes (in or out) can share the same broker connection if required.</p> <p>Several MQTT nodes (in or out) can share the same broker connection if required.</p>
<h4>Dynamic Control</h4>
The connection shared by the node can be controlled dynamically. If the node receives
one of the following control messages, it will not publish the message payload as well.
<h3>Inputs</h3>
<dl class="message-properties">
<dt>action <span class="property-type">string</span></dt>
<dd>the name of the action the node should perform. Available actions are: <code>"connect"</code>,
and <code>"disconnect"</code>.</dd>
<dt class="optional">broker <span class="property-type">broker</span> </dt>
<dd>For the <code>"connect"</code> action, this property can override any
of the individual broker configuration settings, including: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - overrides broker/port to provide a complete connection url</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>If this property is set and the broker is already connected an error
will be logged unless it has the <code>force</code> property set - in which case it will
disconnect from the broker, apply the new settings and reconnect.</p>
</dd>
</dl>
</script> </script>
<script type="text/html" data-help-name="mqtt-broker"> <script type="text/html" data-help-name="mqtt-broker">

View File

@@ -29,3 +29,12 @@
<p>linkード間のリンクはlinkードを選択した場合にのみ表示されます他のタブへのリンクがある場合には仮想的なノードを表示しますこの仮想的ノードをクリックすると対応するタブに移動できます</p> <p>linkード間のリンクはlinkードを選択した場合にのみ表示されます他のタブへのリンクがある場合には仮想的なノードを表示しますこの仮想的ノードをクリックすると対応するタブに移動できます</p>
<p><b>: </b></p> <p><b>: </b></p>
</script> </script>
<script type="text/html" data-help-name="link call">
<p><code>link in</code> </p>
<h3>詳細</h3>
<p>本ノードは任意のタブ内に存在する <code>link in</code> `` <code>link out</code> </p>
<p>本ノードはメッセージを受信するとメッセージを接続した <code>link in</code>
その後応答を待った後にメッセージを送信します</o>
<p>もし設定したタイムアウト(デフォルト30秒)以内に応答がない場合は<code>catch</code> </p>
</script>

View File

@@ -144,11 +144,16 @@
"filterCurrent": "現在のフロー", "filterCurrent": "現在のフロー",
"debugNodes": "debugード", "debugNodes": "debugード",
"clearLog": "ログを削除", "clearLog": "ログを削除",
"clearFilteredLog": "選択したメッセージを削除",
"filterLog": "ログのフィルタリング", "filterLog": "ログのフィルタリング",
"openWindow": "新しいウィンドウで開く", "openWindow": "新しいウィンドウで開く",
"copyPath": "パスをコピー", "copyPath": "パスをコピー",
"copyPayload": "値をコピー", "copyPayload": "値をコピー",
"pinPath": "展開を固定" "pinPath": "展開を固定",
"selectAll": "全てを選択",
"selectNone": "選択を外す",
"all": "全て",
"filtered": "選択したメッセージ"
}, },
"messageMenu": { "messageMenu": {
"collapseAll": "全パスを折りたたむ", "collapseAll": "全パスを折りたたむ",
@@ -159,7 +164,15 @@
}, },
"link": { "link": {
"linkIn": "link in", "linkIn": "link in",
"linkOut": "link out" "linkOut": "link out",
"linkCall": "link call",
"linkOutReturn": "link return",
"outMode": "モード",
"sendToAll": "接続された全てのlinkードへ送信",
"returnToCaller": "link callードへ返却",
"error": {
"missingReturn": "返却するノードの情報が存在しません"
}
}, },
"tls": { "tls": {
"tls": "TLS設定", "tls": "TLS設定",
@@ -282,7 +295,9 @@
"and": "回/", "and": "回/",
"rate": "流量", "rate": "流量",
"msgper": "メッセージ/", "msgper": "メッセージ/",
"queuemsg": "中間メッセージをキューに追加",
"dropmsg": "中間メッセージを削除", "dropmsg": "中間メッセージを削除",
"sendmsg": "2番目の出力で中間メッセージを送信",
"allowrate": "msg.rate(ミリ秒単位)で流量値を上書き", "allowrate": "msg.rate(ミリ秒単位)で流量値を上書き",
"label": { "label": {
"delay": "delay", "delay": "delay",
@@ -401,7 +416,11 @@
"maximumPacketSize": "最大パケット長", "maximumPacketSize": "最大パケット長",
"receiveMaximum": "最大受信数", "receiveMaximum": "最大受信数",
"session": "セッション", "session": "セッション",
"delay": "遅延" "delay": "遅延",
"action": "動作",
"staticTopic": "1つのトピックを購読",
"dynamicTopic": "動的な購読",
"auto-connect": "自動接続"
}, },
"sections-label": { "sections-label": {
"birth-message": "接続時の送信メッセージ(Birthメッセージ)", "birth-message": "接続時の送信メッセージ(Birthメッセージ)",
@@ -442,7 +461,10 @@
"invalid-topic": "不正なトピックが設定されています", "invalid-topic": "不正なトピックが設定されています",
"nonclean-missingclientid": "「セッションの初期化」使用時に、クライアントIDが設定されていません", "nonclean-missingclientid": "「セッションの初期化」使用時に、クライアントIDが設定されていません",
"invalid-json-string": "不正なJSON文字列", "invalid-json-string": "不正なJSON文字列",
"invalid-json-parse": "JSON文字列のパースに失敗しました" "invalid-json-parse": "JSON文字列のパースに失敗しました",
"invalid-action-action": "指定された動作が不正です",
"invalid-action-alreadyconnected": "接続する前にブローカから切断してください",
"invalid-action-badsubscription": "msg.topicが存在しないか不正です"
} }
}, },
"httpin": { "httpin": {
@@ -478,6 +500,7 @@
"proxy-config": "プロキシ設定", "proxy-config": "プロキシ設定",
"use-proxyauth": "プロキシ認証を使用", "use-proxyauth": "プロキシ認証を使用",
"noproxy-hosts": "例外ホスト", "noproxy-hosts": "例外ホスト",
"senderr": "2xx以外の応答をcatchードへ送信",
"utf8": "UTF8文字列", "utf8": "UTF8文字列",
"binary": "バイナリバッファ", "binary": "バイナリバッファ",
"json": "JSONオブジェクト", "json": "JSONオブジェクト",
@@ -703,13 +726,15 @@
"delete": "delete __property__", "delete": "delete __property__",
"move": "move __property__", "move": "move __property__",
"changeCount": "change: __count__ rules", "changeCount": "change: __count__ rules",
"regex": "正規表現を使用" "regex": "正規表現を使用",
"deepCopy": "値のディープコピー"
}, },
"action": { "action": {
"set": "値の代入", "set": "値の代入",
"change": "値の置換", "change": "値の置換",
"delete": "値の削除", "delete": "値の削除",
"move": "値の移動", "move": "値の移動",
"toValue": "対象の値",
"to": "対象の値", "to": "対象の値",
"search": "検索する文字列", "search": "検索する文字列",
"replace": "置換後の文字列" "replace": "置換後の文字列"
@@ -851,6 +876,8 @@
}, },
"file": { "file": {
"label": { "label": {
"write": "write file",
"read": "read file",
"filename": "ファイル名", "filename": "ファイル名",
"action": "動作", "action": "動作",
"addnewline": "メッセージの入力のたびに改行を追加", "addnewline": "メッセージの入力のたびに改行を追加",
@@ -858,7 +885,6 @@
"outputas": "出力形式", "outputas": "出力形式",
"breakchunks": "チャンクへ分割", "breakchunks": "チャンクへ分割",
"breaklines": "行へ分割", "breaklines": "行へ分割",
"filelabel": "file",
"sendError": "エラーメッセージを送信(互換モード)", "sendError": "エラーメッセージを送信(互換モード)",
"encoding": "文字コード", "encoding": "文字コード",
"deletelabel": "delete __file__", "deletelabel": "delete __file__",

View File

@@ -771,7 +771,6 @@
"outputas": "출력형식", "outputas": "출력형식",
"breakchunks": "청크로 분할", "breakchunks": "청크로 분할",
"breaklines": "행으로 분할", "breaklines": "행으로 분할",
"filelabel": "file",
"sendError": "에러메세지를 송신(호환모드)", "sendError": "에러메세지를 송신(호환모드)",
"deletelabel": "delete __file__", "deletelabel": "delete __file__",
"utf8String": "UTF8문자열", "utf8String": "UTF8문자열",

View File

@@ -816,7 +816,6 @@
"outputas": "Выход", "outputas": "Выход",
"breakchunks": "Разбить файл на части", "breakchunks": "Разбить файл на части",
"breaklines": "Разбить на строки", "breaklines": "Разбить на строки",
"filelabel": "файл",
"sendError": "Отправлять сообщение при ошибке (устаревший режим)", "sendError": "Отправлять сообщение при ошибке (устаревший режим)",
"encoding": "Кодировка", "encoding": "Кодировка",
"deletelabel": "удалить __file__", "deletelabel": "удалить __file__",

View File

@@ -804,7 +804,6 @@
"outputas": "输出", "outputas": "输出",
"breakchunks": "分拆成块", "breakchunks": "分拆成块",
"breaklines": "分拆成行", "breaklines": "分拆成行",
"filelabel": "文件",
"sendError": "发生错误时发送消息(传统模式)", "sendError": "发生错误时发送消息(传统模式)",
"encoding": "编码", "encoding": "编码",
"deletelabel": "删除 __file__", "deletelabel": "删除 __file__",

View File

@@ -809,7 +809,6 @@
"outputas": "輸出", "outputas": "輸出",
"breakchunks": "分拆成塊", "breakchunks": "分拆成塊",
"breaklines": "分拆成行", "breaklines": "分拆成行",
"filelabel": "文件",
"sendError": "發生錯誤時發送消息(傳統模式)", "sendError": "發生錯誤時發送消息(傳統模式)",
"encoding": "編碼", "encoding": "編碼",
"deletelabel": "刪除 __file__", "deletelabel": "刪除 __file__",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/nodes", "name": "@node-red/nodes",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -15,13 +15,13 @@
} }
], ],
"dependencies": { "dependencies": {
"acorn": "8.5.0", "acorn": "8.6.0",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.6.3", "ajv": "8.8.2",
"body-parser": "1.19.0", "body-parser": "1.19.0",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie-parser": "1.4.5", "cookie-parser": "1.4.6",
"cookie": "0.4.1", "cookie": "0.4.1",
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
@@ -29,7 +29,7 @@
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"got": "11.8.2", "got": "11.8.3",
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
@@ -40,7 +40,7 @@
"multer": "1.4.3", "multer": "1.4.3",
"mustache": "4.2.0", "mustache": "4.2.0",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"raw-body": "2.4.1", "raw-body": "2.4.2",
"tough-cookie": "4.0.0", "tough-cookie": "4.0.0",
"uuid": "8.3.2", "uuid": "8.3.2",
"ws": "7.5.1", "ws": "7.5.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "2.1.0-beta.1", "@node-red/util": "2.2.0-beta.1",
"clone": "2.1.2", "clone": "2.1.2",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"semver": "7.3.5", "semver": "7.3.5",
"tar": "6.1.11", "tar": "6.1.11",
"uglify-js": "3.14.2" "uglify-js": "3.14.4"
} }
} }

View File

@@ -439,8 +439,6 @@ class Flow {
} }
} }
return [env.name, env]; return [env.name, env];
return [env.name, env];
}); });
group._env = Object.fromEntries(entries); group._env = Object.fromEntries(entries);
} }
@@ -457,24 +455,24 @@ class Flow {
const val const val
= ((value === "true") || = ((value === "true") ||
(value === true)); (value === true));
return { return [{
val: val val: val
}; }, null];
} }
if (type === "cred") { if (type === "cred") {
return { return [{
val: value val: value
}; }, null];
} }
try { try {
var val = redUtil.evaluateNodeProperty(value, type, node, null, null); var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
return { return [{
val: val val: val
}; }, null];
} }
catch (e) { catch (e) {
this.error(e); this.error(e);
return null; return [null, null];
} }
} }
} }
@@ -488,7 +486,7 @@ class Flow {
return this.getGroupEnvSetting(node, parent, name); return this.getGroupEnvSetting(node, parent, name);
} }
} }
return null; return [null, name];
} }
@@ -545,6 +543,9 @@ class Flow {
} }
} }
} }
else {
key = key.substring(8);
}
} }
return this.parent.getSetting(key); return this.parent.getSetting(key);
} }

View File

@@ -373,10 +373,11 @@ class Subflow extends Flow {
const node = this.subflowInstance; const node = this.subflowInstance;
if (node.g) { if (node.g) {
const group = this.getGroupNode(node.g); const group = this.getGroupNode(node.g);
const result = this.getGroupEnvSetting(node, group, name); const [result, newName] = this.getGroupEnvSetting(node, group, name);
if (result) { if (result) {
return result.val; return result.val;
} }
name = newName;
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,8 +16,8 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "2.1.0-beta.1", "@node-red/registry": "2.2.0-beta.1",
"@node-red/util": "2.1.0-beta.1", "@node-red/util": "2.2.0-beta.1",
"async-mutex": "0.3.2", "async-mutex": "0.3.2",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.17.1", "express": "4.17.1",

View File

@@ -136,8 +136,6 @@ function getCurrentLocale() {
function init(settings) { function init(settings) {
if (!initPromise) { if (!initPromise) {
// Keep this as a 'when' promise as top-level red.js uses 'otherwise'
// and embedded users of NR may have copied that.
initPromise = new Promise((resolve,reject) => { initPromise = new Promise((resolve,reject) => {
i18n.use(MessageFileLoader); i18n.use(MessageFileLoader);
var opt = { var opt = {
@@ -146,6 +144,8 @@ function init(settings) {
defaultNS: "runtime", defaultNS: "runtime",
ns: [], ns: [],
fallbackLng: defaultLang, fallbackLng: defaultLang,
keySeparator: ".",
nsSeparator: ":",
interpolation: { interpolation: {
unescapeSuffix: 'HTML', unescapeSuffix: 'HTML',
escapeValue: false, escapeValue: false,

View File

@@ -526,10 +526,11 @@ function getSetting(node, name, flow_) {
if (flow) { if (flow) {
if (node && node.g) { if (node && node.g) {
const group = flow.getGroupNode(node.g); const group = flow.getGroupNode(node.g);
const result = flow.getGroupEnvSetting(node, group, name); const [result, newName] = flow.getGroupEnvSetting(node, group, name);
if (result) { if (result) {
return result.val; return result.val;
} }
name = newName;
} }
return flow.getSetting(name); return flow.getSetting(name);
} }
@@ -842,6 +843,9 @@ function encodeObject(msg,opts) {
length: msg.msg.size length: msg.msg.size
} }
needsStringify = true; needsStringify = true;
} else if (msg.msg && msg.msg.constructor.name === "RegExp") {
msg.format = 'regexp';
msg.msg = msg.msg.toString();
} }
if (needsStringify || (msg.format === "Object")) { if (needsStringify || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) { msg.msg = safeJSONStringify(msg.msg, function(key, value) {
@@ -907,6 +911,12 @@ function encodeObject(msg,opts) {
data: Object.fromEntries(Array.from(value.entries()).slice(0,debuglength)), data: Object.fromEntries(Array.from(value.entries()).slice(0,debuglength)),
length: value.size length: value.size
} }
} else if (value.constructor.name === "RegExp") {
value = {
__enc__: true,
type: "regexp",
data: value.toString()
}
} }
} else if (value === undefined) { } else if (value === undefined) {
value = { value = {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -16,10 +16,10 @@
], ],
"dependencies": { "dependencies": {
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"i18next": "21.2.4", "i18next": "21.5.4",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.5", "jsonata": "1.8.5",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"moment-timezone": "0.5.33" "moment-timezone": "0.5.34"
} }
} }

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/bin/sh
# #
# Copyright JS Foundation and other contributors, http://js.foundation # Copyright JS Foundation and other contributors, http://js.foundation
# #
@@ -29,15 +29,16 @@ do
done done
# Find the real location of this script # Find the real location of this script
CURRENT_PATH=`pwd` CURRENT_PATH=$(pwd)
SCRIPT_PATH="${BASH_SOURCE[0]}"; SCRIPT_PATH=$(readlink -f "$0")
while [ -h "${SCRIPT_PATH}" ]; do while [ -h "${SCRIPT_PATH}" ]; do
cd "`dirname "${SCRIPT_PATH}"`" cd "$(dirname "${SCRIPT_PATH}")" || exit 1
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; P=$(basename "${SCRIPT_PATH}")
SCRIPT_PATH=$(readlink "${P}")
done done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null cd "$(dirname "${SCRIPT_PATH}")" > /dev/null || exit 1
SCRIPT_PATH="`pwd`"; SCRIPT_PATH=$(pwd)
cd $CURRENT_PATH cd "$CURRENT_PATH" || exit 1
# Run Node-RED # Run Node-RED
exec /usr/bin/env node $OPTIONS $SCRIPT_PATH/../red.js $ARGS exec /usr/bin/env node ${OPTIONS} ${SCRIPT_PATH}/../red.js ${ARGS}

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "2.1.0-beta.1", "version": "2.2.0-beta.1",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -31,15 +31,15 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "2.1.0-beta.1", "@node-red/editor-api": "2.2.0-beta.1",
"@node-red/runtime": "2.1.0-beta.1", "@node-red/runtime": "2.2.0-beta.1",
"@node-red/util": "2.1.0-beta.1", "@node-red/util": "2.2.0-beta.1",
"@node-red/nodes": "2.1.0-beta.1", "@node-red/nodes": "2.2.0-beta.1",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.17.1", "express": "4.17.1",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"node-red-admin": "^2.2.0", "node-red-admin": "^2.2.1",
"nopt": "5.0.0", "nopt": "5.0.0",
"semver": "7.3.5" "semver": "7.3.5"
}, },

View File

@@ -194,6 +194,11 @@ if (process.env.NODE_RED_ENABLE_PROJECTS) {
settings.editorTheme.projects.enabled = !/^false$/i.test(process.env.NODE_RED_ENABLE_PROJECTS); settings.editorTheme.projects.enabled = !/^false$/i.test(process.env.NODE_RED_ENABLE_PROJECTS);
} }
if (process.env.NODE_RED_ENABLE_TOURS) {
settings.editorTheme = settings.editorTheme || {};
settings.editorTheme.tours = !/^false$/i.test(process.env.NODE_RED_ENABLE_TOURS);
}
var defaultServerSettings = { var defaultServerSettings = {
"x-powered-by": false "x-powered-by": false

View File

@@ -9,29 +9,36 @@ const LATEST = "2";
function generateScript() { function generateScript() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const packages = [ const packages = [
"node-red-util", "@node-red/util",
"node-red-runtime", "@node-red/runtime",
"node-red-registry", "@node-red/registry",
"node-red-nodes", "@node-red/nodes",
"node-red-editor-client", "@node-red/editor-client",
"node-red-editor-api", "@node-red/editor-api",
"node-red" "node-red"
]; ];
const rootPackage = require(path.join(__dirname,"..","package.json")); const rootPackage = require(path.join(__dirname,"..","package.json"));
const version = rootPackage.version; const version = rootPackage.version;
const versionParts = version.split("."); const versionParts = version.split(".");
let updateNextToLatest = false;
let tagArg = ""; let tagArg = "";
if (versionParts[0] !== LATEST) { if (versionParts[0] !== LATEST) {
tagArg = `--tag v${versionParts[0]}-maintenance` tagArg = `--tag v${versionParts[0]}-maintenance`
} else if (/-/.test(version)) { } else if (/-/.test(version)) {
tagArg = "--tag next" tagArg = "--tag next"
} else {
updateNextToLatest = true;
} }
const lines = []; const lines = [];
packages.forEach(name => { packages.forEach(name => {
lines.push(`npm publish ${name}-${version}.tgz ${tagArg}\n`); const tarName = name.replace(/@/,"").replace(/\//,"-")
lines.push(`npm publish ${tarName}-${version}.tgz ${tagArg}\n`);
if (updateNextToLatest) {
lines.push(`npm dist-tag add ${name}@${version} next\n`);
}
}) })
resolve(lines.join("")) resolve(lines.join(""))
}); });

View File

@@ -60,9 +60,9 @@ describe('TCP Request Node', function() {
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
if (typeof val1 === 'object') { if (typeof val1 === 'object') {
msg.should.have.properties(Object.assign({}, val1, {payload: Buffer(val1.payload)})); msg.should.have.properties(Object.assign({}, val1, {payload: Buffer.from(val1.payload)}));
} else { } else {
msg.should.have.property('payload', Buffer(val1)); msg.should.have.property('payload', Buffer.from(val1));
} }
done(); done();
} catch(err) { } catch(err) {
@@ -84,9 +84,9 @@ describe('TCP Request Node', function() {
n2.on("input", msg => { n2.on("input", msg => {
try { try {
if (typeof result === 'object') { if (typeof result === 'object') {
msg.should.have.properties(Object.assign({}, result, {payload: Buffer(result.payload)})); msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)}));
} else { } else {
msg.should.have.property('payload', Buffer(result)); msg.should.have.property('payload', Buffer.from(result));
} }
done(); done();
} catch(err) { } catch(err) {

View File

@@ -50,6 +50,24 @@ describe('JSON node', function() {
}); });
}); });
it('should convert a buffer of a valid json string to a javascript object', function(done) {
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.payload.should.have.property('employees');
msg.payload.employees[0].should.have.property('firstName', 'John');
msg.payload.employees[0].should.have.property('lastName', 'Smith');
done();
});
var jsonString = Buffer.from('{"employees":[{"firstName":"John", "lastName":"Smith"}]}');
jn1.receive({payload:jsonString,topic: "bar"});
});
});
it('should convert a javascript object to a json string', function(done) { it('should convert a javascript object to a json string', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
@@ -166,29 +184,55 @@ describe('JSON node', function() {
}); });
}); });
it('should log an error if asked to parse something thats not json or js', function(done) { it('should log an error if asked to parse an invalid json string in a buffer', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() { helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1"); try {
var jn2 = helper.getNode("jn2"); var jn1 = helper.getNode("jn1");
setTimeout(function() { var jn2 = helper.getNode("jn2");
try { jn1.receive({payload:Buffer.from('{"name":foo}'),topic: "bar"});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.toString().should.eql('json.errors.dropped-object'); logEvents.should.have.length(1);
done(); logEvents[0][0].should.have.a.property('msg');
} catch(err) { logEvents[0][0].msg.should.startWith("Unexpected token o");
done(err); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
} done();
},50); } catch(err) { done(err) }
jn1.receive({payload:Buffer.from("a")}); },20);
} catch(err) {
done(err);
}
}); });
}); });
// it('should log an error if asked to parse something thats not json or js and not in force object mode', function(done) {
// var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
// {id:"jn2", type:"helper"}];
// helper.load(jsonNode, flow, function() {
// var jn1 = helper.getNode("jn1");
// var jn2 = helper.getNode("jn2");
// setTimeout(function() {
// try {
// var logEvents = helper.log().args.filter(function(evt) {
// return evt[0].type == "json";
// });
// logEvents.should.have.length(1);
// logEvents[0][0].should.have.a.property('msg');
// logEvents[0][0].msg.toString().should.eql('json.errors.dropped');
// done();
// } catch(err) {
// done(err);
// }
// },50);
// jn1.receive({payload:Buffer.from("abcd")});
// });
// });
it('should pass straight through if no payload set', function(done) { it('should pass straight through if no payload set', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];

View File

@@ -1275,6 +1275,52 @@ describe('Flow', function() {
} }
}); });
it("can access environment variable property using $parent", function (done) {
try {
after(function() {
delete process.env.V0;
delete process.env.V1;
})
process.env.V0 = "gv0";
process.env.V1 = "gv1";
var config = flowUtils.parseConfig([
{id:"t1",type:"tab",env:[
{"name": "V0", value: "v0", type: "str"}
]},
{id:"g1",type:"group",z:"t1",env:[
{"name": "V0", value: "v1", type: "str"},
{"name": "V1", value: "v2", type: "str"}
]},
{id:"g2",type:"group",z:"t1",g:"g1",env:[
{"name": "V1", value: "v3", type: "str"}
]},
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"${$parent.V0}",wires:[]},
{id:"2",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"${$parent.V0}",wires:[]},
{id:"3",x:10,y:10,z:"t1",g:"g1",type:"test",foo:"${$parent.V1}",wires:[]},
{id:"4",x:10,y:10,z:"t1",g:"g2",type:"test",foo:"${$parent.V1}",wires:[]},
{id:"5",x:10,y:10,z:"t1",type:"test",foo:"${$parent.V1}",wires:[]},
]);
var flow = Flow.create({getSetting:v=>process.env[v]},config,config.flows["t1"]);
flow.start();
var activeNodes = flow.getActiveNodes();
activeNodes["1"].foo.should.equal("gv0");
activeNodes["2"].foo.should.equal("v0");
activeNodes["3"].foo.should.equal("gv1");
activeNodes["4"].foo.should.equal("v2");
activeNodes["5"].foo.should.equal("gv1");
flow.stop().then(function() {
done();
});
}
catch (e) {
console.log(e.stack);
done(e);
}
});
}); });